]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
1 | //! Reorder items. |
2 | //! | |
3 | //! `mod`, `extern crate` and `use` declarations are reordered in alphabetical | |
4 | //! order. Trait items are reordered in pre-determined order (associated types | |
5 | //! and constants comes before methods). | |
6 | ||
7 | // FIXME(#2455): Reorder trait items. | |
8 | ||
9 | use std::cmp::{Ord, Ordering}; | |
10 | ||
11 | use rustc_ast::ast; | |
12 | use rustc_span::{symbol::sym, Span}; | |
13 | ||
14 | use crate::config::{Config, GroupImportsTactic, ImportGranularity}; | |
15 | use crate::imports::{flatten_use_trees, merge_use_trees, SharedPrefix, UseSegment, UseTree}; | |
16 | use crate::items::{is_mod_decl, rewrite_extern_crate, rewrite_mod}; | |
17 | use crate::lists::{itemize_list, write_list, ListFormatting, ListItem}; | |
18 | use crate::rewrite::RewriteContext; | |
19 | use crate::shape::Shape; | |
20 | use crate::source_map::LineRangeUtils; | |
21 | use crate::spanned::Spanned; | |
22 | use crate::utils::{contains_skip, mk_sp}; | |
23 | use crate::visitor::FmtVisitor; | |
24 | ||
25 | /// Choose the ordering between the given two items. | |
26 | fn compare_items(a: &ast::Item, b: &ast::Item) -> Ordering { | |
27 | match (&a.kind, &b.kind) { | |
28 | (&ast::ItemKind::Mod(..), &ast::ItemKind::Mod(..)) => { | |
29 | a.ident.as_str().cmp(&b.ident.as_str()) | |
30 | } | |
31 | (&ast::ItemKind::ExternCrate(ref a_name), &ast::ItemKind::ExternCrate(ref b_name)) => { | |
32 | // `extern crate foo as bar;` | |
33 | // ^^^ Comparing this. | |
34 | let a_orig_name = a_name.map_or_else(|| a.ident.as_str(), rustc_span::Symbol::as_str); | |
35 | let b_orig_name = b_name.map_or_else(|| b.ident.as_str(), rustc_span::Symbol::as_str); | |
36 | let result = a_orig_name.cmp(&b_orig_name); | |
37 | if result != Ordering::Equal { | |
38 | return result; | |
39 | } | |
40 | ||
41 | // `extern crate foo as bar;` | |
42 | // ^^^ Comparing this. | |
43 | match (a_name, b_name) { | |
44 | (Some(..), None) => Ordering::Greater, | |
45 | (None, Some(..)) => Ordering::Less, | |
46 | (None, None) => Ordering::Equal, | |
47 | (Some(..), Some(..)) => a.ident.as_str().cmp(&b.ident.as_str()), | |
48 | } | |
49 | } | |
50 | _ => unreachable!(), | |
51 | } | |
52 | } | |
53 | ||
54 | fn wrap_reorderable_items( | |
55 | context: &RewriteContext<'_>, | |
56 | list_items: &[ListItem], | |
57 | shape: Shape, | |
58 | ) -> Option<String> { | |
59 | let fmt = ListFormatting::new(shape, context.config) | |
60 | .separator("") | |
61 | .align_comments(false); | |
62 | write_list(list_items, &fmt) | |
63 | } | |
64 | ||
65 | fn rewrite_reorderable_item( | |
66 | context: &RewriteContext<'_>, | |
67 | item: &ast::Item, | |
68 | shape: Shape, | |
69 | ) -> Option<String> { | |
70 | match item.kind { | |
71 | ast::ItemKind::ExternCrate(..) => rewrite_extern_crate(context, item, shape), | |
72 | ast::ItemKind::Mod(..) => rewrite_mod(context, item, shape), | |
73 | _ => None, | |
74 | } | |
75 | } | |
76 | ||
77 | /// Rewrite a list of items with reordering and/or regrouping. Every item | |
78 | /// in `items` must have the same `ast::ItemKind`. Whether reordering, regrouping, | |
79 | /// or both are done is determined from the `context`. | |
80 | fn rewrite_reorderable_or_regroupable_items( | |
81 | context: &RewriteContext<'_>, | |
82 | reorderable_items: &[&ast::Item], | |
83 | shape: Shape, | |
84 | span: Span, | |
85 | ) -> Option<String> { | |
86 | match reorderable_items[0].kind { | |
87 | // FIXME: Remove duplicated code. | |
88 | ast::ItemKind::Use(..) => { | |
89 | let mut normalized_items: Vec<_> = reorderable_items | |
90 | .iter() | |
91 | .filter_map(|item| UseTree::from_ast_with_normalization(context, item)) | |
92 | .collect(); | |
93 | let cloned = normalized_items.clone(); | |
94 | // Add comments before merging. | |
95 | let list_items = itemize_list( | |
96 | context.snippet_provider, | |
97 | cloned.iter(), | |
98 | "", | |
99 | ";", | |
100 | |item| item.span().lo(), | |
101 | |item| item.span().hi(), | |
102 | |_item| Some("".to_owned()), | |
103 | span.lo(), | |
104 | span.hi(), | |
105 | false, | |
106 | ); | |
107 | for (item, list_item) in normalized_items.iter_mut().zip(list_items) { | |
108 | item.list_item = Some(list_item.clone()); | |
109 | } | |
110 | normalized_items = match context.config.imports_granularity() { | |
111 | ImportGranularity::Crate => merge_use_trees(normalized_items, SharedPrefix::Crate), | |
112 | ImportGranularity::Module => { | |
113 | merge_use_trees(normalized_items, SharedPrefix::Module) | |
114 | } | |
115 | ImportGranularity::Item => flatten_use_trees(normalized_items), | |
116 | ImportGranularity::Preserve => normalized_items, | |
117 | }; | |
118 | ||
119 | let mut regrouped_items = match context.config.group_imports() { | |
120 | GroupImportsTactic::Preserve => vec![normalized_items], | |
121 | GroupImportsTactic::StdExternalCrate => group_imports(normalized_items), | |
122 | }; | |
123 | ||
124 | if context.config.reorder_imports() { | |
125 | regrouped_items.iter_mut().for_each(|items| items.sort()) | |
126 | } | |
127 | ||
128 | // 4 = "use ", 1 = ";" | |
129 | let nested_shape = shape.offset_left(4)?.sub_width(1)?; | |
130 | let item_vec: Vec<_> = regrouped_items | |
131 | .into_iter() | |
132 | .filter(|use_group| !use_group.is_empty()) | |
133 | .map(|use_group| { | |
134 | let item_vec: Vec<_> = use_group | |
135 | .into_iter() | |
136 | .map(|use_tree| ListItem { | |
137 | item: use_tree.rewrite_top_level(context, nested_shape), | |
138 | ..use_tree.list_item.unwrap_or_else(ListItem::empty) | |
139 | }) | |
140 | .collect(); | |
141 | wrap_reorderable_items(context, &item_vec, nested_shape) | |
142 | }) | |
143 | .collect::<Option<Vec<_>>>()?; | |
144 | ||
145 | let join_string = format!("\n\n{}", shape.indent.to_string(context.config)); | |
146 | Some(item_vec.join(&join_string)) | |
147 | } | |
148 | _ => { | |
149 | let list_items = itemize_list( | |
150 | context.snippet_provider, | |
151 | reorderable_items.iter(), | |
152 | "", | |
153 | ";", | |
154 | |item| item.span().lo(), | |
155 | |item| item.span().hi(), | |
156 | |item| rewrite_reorderable_item(context, item, shape), | |
157 | span.lo(), | |
158 | span.hi(), | |
159 | false, | |
160 | ); | |
161 | ||
162 | let mut item_pair_vec: Vec<_> = list_items.zip(reorderable_items.iter()).collect(); | |
163 | item_pair_vec.sort_by(|a, b| compare_items(a.1, b.1)); | |
164 | let item_vec: Vec<_> = item_pair_vec.into_iter().map(|pair| pair.0).collect(); | |
165 | ||
166 | wrap_reorderable_items(context, &item_vec, shape) | |
167 | } | |
168 | } | |
169 | } | |
170 | ||
171 | fn contains_macro_use_attr(item: &ast::Item) -> bool { | |
172 | crate::attr::contains_name(&item.attrs, sym::macro_use) | |
173 | } | |
174 | ||
175 | /// Divides imports into three groups, corresponding to standard, external | |
176 | /// and local imports. Sorts each subgroup. | |
177 | fn group_imports(uts: Vec<UseTree>) -> Vec<Vec<UseTree>> { | |
178 | let mut std_imports = Vec::new(); | |
179 | let mut external_imports = Vec::new(); | |
180 | let mut local_imports = Vec::new(); | |
181 | ||
182 | for ut in uts.into_iter() { | |
183 | if ut.path.is_empty() { | |
184 | external_imports.push(ut); | |
185 | continue; | |
186 | } | |
187 | match &ut.path[0] { | |
188 | UseSegment::Ident(id, _) => match id.as_ref() { | |
189 | "std" | "alloc" | "core" => std_imports.push(ut), | |
190 | _ => external_imports.push(ut), | |
191 | }, | |
192 | UseSegment::Slf(_) | UseSegment::Super(_) | UseSegment::Crate(_) => { | |
193 | local_imports.push(ut) | |
194 | } | |
195 | // These are probably illegal here | |
196 | UseSegment::Glob | UseSegment::List(_) => external_imports.push(ut), | |
197 | } | |
198 | } | |
199 | ||
200 | vec![std_imports, external_imports, local_imports] | |
201 | } | |
202 | ||
203 | /// A simplified version of `ast::ItemKind`. | |
204 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] | |
205 | enum ReorderableItemKind { | |
206 | ExternCrate, | |
207 | Mod, | |
208 | Use, | |
209 | /// An item that cannot be reordered. Either has an unreorderable item kind | |
210 | /// or an `macro_use` attribute. | |
211 | Other, | |
212 | } | |
213 | ||
214 | impl ReorderableItemKind { | |
215 | fn from(item: &ast::Item) -> Self { | |
216 | match item.kind { | |
217 | _ if contains_macro_use_attr(item) | contains_skip(&item.attrs) => { | |
218 | ReorderableItemKind::Other | |
219 | } | |
220 | ast::ItemKind::ExternCrate(..) => ReorderableItemKind::ExternCrate, | |
221 | ast::ItemKind::Mod(..) if is_mod_decl(item) => ReorderableItemKind::Mod, | |
222 | ast::ItemKind::Use(..) => ReorderableItemKind::Use, | |
223 | _ => ReorderableItemKind::Other, | |
224 | } | |
225 | } | |
226 | ||
227 | fn is_same_item_kind(self, item: &ast::Item) -> bool { | |
228 | ReorderableItemKind::from(item) == self | |
229 | } | |
230 | ||
231 | fn is_reorderable(self, config: &Config) -> bool { | |
232 | match self { | |
233 | ReorderableItemKind::ExternCrate => config.reorder_imports(), | |
234 | ReorderableItemKind::Mod => config.reorder_modules(), | |
235 | ReorderableItemKind::Use => config.reorder_imports(), | |
236 | ReorderableItemKind::Other => false, | |
237 | } | |
238 | } | |
239 | ||
240 | fn is_regroupable(self, config: &Config) -> bool { | |
241 | match self { | |
242 | ReorderableItemKind::ExternCrate | |
243 | | ReorderableItemKind::Mod | |
244 | | ReorderableItemKind::Other => false, | |
245 | ReorderableItemKind::Use => config.group_imports() != GroupImportsTactic::Preserve, | |
246 | } | |
247 | } | |
248 | ||
249 | fn in_group(self, config: &Config) -> bool { | |
250 | match self { | |
251 | ReorderableItemKind::ExternCrate | ReorderableItemKind::Mod => true, | |
252 | ReorderableItemKind::Use => config.group_imports() == GroupImportsTactic::Preserve, | |
253 | ReorderableItemKind::Other => false, | |
254 | } | |
255 | } | |
256 | } | |
257 | ||
258 | impl<'b, 'a: 'b> FmtVisitor<'a> { | |
259 | /// Format items with the same item kind and reorder them, regroup them, or | |
260 | /// both. If `in_group` is `true`, then the items separated by an empty line | |
261 | /// will not be reordered together. | |
262 | fn walk_reorderable_or_regroupable_items( | |
263 | &mut self, | |
264 | items: &[&ast::Item], | |
265 | item_kind: ReorderableItemKind, | |
266 | in_group: bool, | |
267 | ) -> usize { | |
268 | let mut last = self.parse_sess.lookup_line_range(items[0].span()); | |
269 | let item_length = items | |
270 | .iter() | |
271 | .take_while(|ppi| { | |
272 | item_kind.is_same_item_kind(&***ppi) | |
273 | && (!in_group || { | |
274 | let current = self.parse_sess.lookup_line_range(ppi.span()); | |
275 | let in_same_group = current.lo < last.hi + 2; | |
276 | last = current; | |
277 | in_same_group | |
278 | }) | |
279 | }) | |
280 | .count(); | |
281 | let items = &items[..item_length]; | |
282 | ||
283 | let at_least_one_in_file_lines = items | |
284 | .iter() | |
285 | .any(|item| !out_of_file_lines_range!(self, item.span)); | |
286 | ||
287 | if at_least_one_in_file_lines && !items.is_empty() { | |
288 | let lo = items.first().unwrap().span().lo(); | |
289 | let hi = items.last().unwrap().span().hi(); | |
290 | let span = mk_sp(lo, hi); | |
291 | let rw = rewrite_reorderable_or_regroupable_items( | |
292 | &self.get_context(), | |
293 | items, | |
294 | self.shape(), | |
295 | span, | |
296 | ); | |
297 | self.push_rewrite(span, rw); | |
298 | } else { | |
299 | for item in items { | |
300 | self.push_rewrite(item.span, None); | |
301 | } | |
302 | } | |
303 | ||
304 | item_length | |
305 | } | |
306 | ||
307 | /// Visits and format the given items. Items are reordered If they are | |
308 | /// consecutive and reorderable. | |
309 | pub(crate) fn visit_items_with_reordering(&mut self, mut items: &[&ast::Item]) { | |
310 | while !items.is_empty() { | |
311 | // If the next item is a `use`, `extern crate` or `mod`, then extract it and any | |
312 | // subsequent items that have the same item kind to be reordered within | |
313 | // `walk_reorderable_items`. Otherwise, just format the next item for output. | |
314 | let item_kind = ReorderableItemKind::from(items[0]); | |
315 | if item_kind.is_reorderable(self.config) || item_kind.is_regroupable(self.config) { | |
316 | let visited_items_num = self.walk_reorderable_or_regroupable_items( | |
317 | items, | |
318 | item_kind, | |
319 | item_kind.in_group(self.config), | |
320 | ); | |
321 | let (_, rest) = items.split_at(visited_items_num); | |
322 | items = rest; | |
323 | } else { | |
324 | // Reaching here means items were not reordered. There must be at least | |
325 | // one item left in `items`, so calling `unwrap()` here is safe. | |
326 | let (item, rest) = items.split_first().unwrap(); | |
327 | self.visit_item(item); | |
328 | items = rest; | |
329 | } | |
330 | } | |
331 | } | |
332 | } |