rustfmt_nightly/
reorder.rs

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