Skip to content

Commit 78b52ec

Browse files
studootnrc
authored andcommitted
Add use declaration re-ordering (#1104)
* Add config options for combinations of lines and items * Reordering of import lines implemented. * Changed nested matches to tuple pattern matching * Added ordering of path list items to the ordering of use declarations * Move `format_imports` and `format_import` methods to `imports.rs` * Add comment to explain how `use` declarations are split off while walking through a module * Change `ImportReordering` config option to separate boolean options
1 parent 9750fb7 commit 78b52ec

10 files changed

+287
-45
lines changed

src/config.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,8 @@ create_config! {
391391
chain_indent: BlockIndentStyle, BlockIndentStyle::Tabbed, "Indentation of chain";
392392
chains_overflow_last: bool, true, "Allow last call in method chain to break the line";
393393
reorder_imports: bool, false, "Reorder import statements alphabetically";
394+
reorder_imported_names: bool, false,
395+
"Reorder lists of names in import statements alphabetically";
394396
single_line_if_else_max_width: usize, 50, "Maximum line length for single line if-else \
395397
expressions. A value of zero means always break \
396398
if-else expressions.";

src/imports.rs

Lines changed: 167 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,120 @@
99
// except according to those terms.
1010

1111
use Indent;
12+
use utils;
13+
use syntax::codemap::{self, BytePos, Span};
1214
use codemap::SpanUtils;
1315
use lists::{write_list, itemize_list, ListItem, ListFormatting, SeparatorTactic, definitive_tactic};
1416
use types::rewrite_path;
1517
use rewrite::{Rewrite, RewriteContext};
18+
use visitor::FmtVisitor;
19+
use std::cmp::Ordering;
1620

17-
use syntax::ast;
18-
use syntax::codemap::Span;
21+
use syntax::{ast, ptr};
22+
23+
fn path_of(a: &ast::ViewPath_) -> &ast::Path {
24+
match a {
25+
&ast::ViewPath_::ViewPathSimple(_, ref p) => p,
26+
&ast::ViewPath_::ViewPathGlob(ref p) => p,
27+
&ast::ViewPath_::ViewPathList(ref p, _) => p,
28+
}
29+
}
30+
31+
fn compare_path_segments(a: &ast::PathSegment, b: &ast::PathSegment) -> Ordering {
32+
a.identifier.name.as_str().cmp(&b.identifier.name.as_str())
33+
}
34+
35+
fn compare_paths(a: &ast::Path, b: &ast::Path) -> Ordering {
36+
for segment in a.segments.iter().zip(b.segments.iter()) {
37+
let ord = compare_path_segments(segment.0, segment.1);
38+
if ord != Ordering::Equal {
39+
return ord;
40+
}
41+
}
42+
a.segments.len().cmp(&b.segments.len())
43+
}
44+
45+
fn compare_path_list_items(a: &ast::PathListItem, b: &ast::PathListItem) -> Ordering {
46+
let name_ordering = match a.node.name() {
47+
Some(a_name) => {
48+
match b.node.name() {
49+
Some(b_name) => a_name.name.as_str().cmp(&b_name.name.as_str()),
50+
None => Ordering::Greater,
51+
}
52+
}
53+
None => {
54+
match b.node.name() {
55+
Some(_) => Ordering::Less,
56+
None => Ordering::Equal,
57+
}
58+
}
59+
};
60+
if name_ordering == Ordering::Equal {
61+
match a.node.rename() {
62+
Some(a_rename) => {
63+
match b.node.rename() {
64+
Some(b_rename) => a_rename.name.as_str().cmp(&b_rename.name.as_str()),
65+
None => Ordering::Greater,
66+
}
67+
}
68+
None => {
69+
match b.node.name() {
70+
Some(_) => Ordering::Less,
71+
None => Ordering::Equal,
72+
}
73+
}
74+
}
75+
} else {
76+
name_ordering
77+
}
78+
}
79+
80+
fn compare_path_list_item_lists(a_items: &Vec<ast::PathListItem>,
81+
b_items: &Vec<ast::PathListItem>)
82+
-> Ordering {
83+
let mut a = a_items.clone();
84+
let mut b = b_items.clone();
85+
a.sort_by(|a, b| compare_path_list_items(a, b));
86+
b.sort_by(|a, b| compare_path_list_items(a, b));
87+
for comparison_pair in a.iter().zip(b.iter()) {
88+
let ord = compare_path_list_items(comparison_pair.0, comparison_pair.1);
89+
if ord != Ordering::Equal {
90+
return ord;
91+
}
92+
}
93+
a.len().cmp(&b.len())
94+
}
95+
96+
fn compare_view_path_types(a: &ast::ViewPath_, b: &ast::ViewPath_) -> Ordering {
97+
use syntax::ast::ViewPath_::*;
98+
match (a, b) {
99+
(&ViewPathSimple(..), &ViewPathSimple(..)) => Ordering::Equal,
100+
(&ViewPathSimple(..), _) => Ordering::Less,
101+
(&ViewPathGlob(_), &ViewPathSimple(..)) => Ordering::Greater,
102+
(&ViewPathGlob(_), &ViewPathGlob(_)) => Ordering::Equal,
103+
(&ViewPathGlob(_), &ViewPathList(..)) => Ordering::Less,
104+
(&ViewPathList(_, ref a_items), &ViewPathList(_, ref b_items)) => {
105+
compare_path_list_item_lists(a_items, b_items)
106+
}
107+
(&ViewPathList(..), _) => Ordering::Greater,
108+
}
109+
}
110+
111+
fn compare_view_paths(a: &ast::ViewPath_, b: &ast::ViewPath_) -> Ordering {
112+
match compare_paths(path_of(a), path_of(b)) {
113+
Ordering::Equal => compare_view_path_types(a, b),
114+
cmp => cmp,
115+
}
116+
}
117+
118+
fn compare_use_items(a: &ast::Item, b: &ast::Item) -> Option<Ordering> {
119+
match (&a.node, &b.node) {
120+
(&ast::ItemKind::Use(ref a_vp), &ast::ItemKind::Use(ref b_vp)) => {
121+
Some(compare_view_paths(&a_vp.node, &b_vp.node))
122+
}
123+
_ => None,
124+
}
125+
}
19126

20127
// TODO (some day) remove unused imports, expand globs, compress many single
21128
// imports into a list import.
@@ -50,6 +157,63 @@ impl Rewrite for ast::ViewPath {
50157
}
51158
}
52159

160+
impl<'a> FmtVisitor<'a> {
161+
pub fn format_imports(&mut self, use_items: &[ptr::P<ast::Item>]) {
162+
let mut last_pos =
163+
use_items.first().map(|p_i| p_i.span.lo - BytePos(1)).unwrap_or(self.last_pos);
164+
let prefix = codemap::mk_sp(self.last_pos, last_pos);
165+
let mut ordered_use_items = use_items.iter()
166+
.map(|p_i| {
167+
let new_item = (&*p_i, last_pos);
168+
last_pos = p_i.span.hi;
169+
new_item
170+
})
171+
.collect::<Vec<_>>();
172+
// Order the imports by view-path & other import path properties
173+
ordered_use_items.sort_by(|a, b| compare_use_items(a.0, b.0).unwrap());
174+
// First, output the span before the first import
175+
self.format_missing(prefix.hi);
176+
for ordered in ordered_use_items {
177+
// Fake out the formatter by setting `self.last_pos` to the appropriate location before
178+
// each item before visiting it.
179+
self.last_pos = ordered.1;
180+
self.visit_item(&ordered.0);
181+
}
182+
self.last_pos = last_pos;
183+
}
184+
185+
pub fn format_import(&mut self, vis: &ast::Visibility, vp: &ast::ViewPath, span: Span) {
186+
let vis = utils::format_visibility(vis);
187+
let mut offset = self.block_indent;
188+
offset.alignment += vis.len() + "use ".len();
189+
// 1 = ";"
190+
match vp.rewrite(&self.get_context(),
191+
self.config.max_width - offset.width() - 1,
192+
offset) {
193+
Some(ref s) if s.is_empty() => {
194+
// Format up to last newline
195+
let prev_span = codemap::mk_sp(self.last_pos, source!(self, span).lo);
196+
let span_end = match self.snippet(prev_span).rfind('\n') {
197+
Some(offset) => self.last_pos + BytePos(offset as u32),
198+
None => source!(self, span).lo,
199+
};
200+
self.format_missing(span_end);
201+
self.last_pos = source!(self, span).hi;
202+
}
203+
Some(ref s) => {
204+
let s = format!("{}use {};", vis, s);
205+
self.format_missing_with_indent(source!(self, span).lo);
206+
self.buffer.push_str(&s);
207+
self.last_pos = source!(self, span).hi;
208+
}
209+
None => {
210+
self.format_missing_with_indent(source!(self, span).lo);
211+
self.format_missing(source!(self, span).hi);
212+
}
213+
}
214+
}
215+
}
216+
53217
fn rewrite_single_use_list(path_str: String, vpi: &ast::PathListItem) -> String {
54218
let path_item_str = if let ast::PathListItemKind::Ident { name, .. } = vpi.node {
55219
// A name.
@@ -138,7 +302,7 @@ pub fn rewrite_use_list(width: usize,
138302
let has_self = move_self_to_front(&mut items);
139303
let first_index = if has_self { 0 } else { 1 };
140304

141-
if context.config.reorder_imports {
305+
if context.config.reorder_imported_names {
142306
items[1..].sort_by(|a, b| a.item.cmp(&b.item));
143307
}
144308

src/utils.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,13 @@ macro_rules! msg {
224224
)
225225
}
226226

227+
// For format_missing and last_pos, need to use the source callsite (if applicable).
228+
// Required as generated code spans aren't guaranteed to follow on from the last span.
229+
macro_rules! source {
230+
($this:ident, $sp: expr) => {
231+
$this.codemap.source_callsite($sp)
232+
}
233+
}
227234

228235
// Wraps string-like values in an Option. Returns Some when the string adheres
229236
// to the Rewrite constraints defined for the Rewrite trait and else otherwise.

src/visitor.rs

Lines changed: 24 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11-
use syntax::{ast, visit};
11+
use syntax::{ast, ptr, visit};
1212
use syntax::codemap::{self, CodeMap, Span, BytePos};
1313
use syntax::parse::ParseSess;
1414

@@ -23,11 +23,10 @@ use comment::rewrite_comment;
2323
use macros::rewrite_macro;
2424
use items::{rewrite_static, rewrite_associated_type, rewrite_type_alias, format_impl, format_trait};
2525

26-
// For format_missing and last_pos, need to use the source callsite (if applicable).
27-
// Required as generated code spans aren't guaranteed to follow on from the last span.
28-
macro_rules! source {
29-
($this:ident, $sp: expr) => {
30-
$this.codemap.source_callsite($sp)
26+
fn is_use_item(item: &ast::Item) -> bool {
27+
match item.node {
28+
ast::ItemKind::Use(_) => true,
29+
_ => false,
3130
}
3231
}
3332

@@ -191,7 +190,7 @@ impl<'a> FmtVisitor<'a> {
191190
self.visit_block(b)
192191
}
193192

194-
fn visit_item(&mut self, item: &ast::Item) {
193+
pub fn visit_item(&mut self, item: &ast::Item) {
195194
// This is where we bail out if there is a skip attribute. This is only
196195
// complex in the module case. It is complex because the module could be
197196
// in a seperate file and there might be attributes in both files, but
@@ -502,8 +501,24 @@ impl<'a> FmtVisitor<'a> {
502501
}
503502

504503
fn walk_mod_items(&mut self, m: &ast::Mod) {
505-
for item in &m.items {
506-
self.visit_item(&item);
504+
let mut items_left: &[ptr::P<ast::Item>] = &m.items;
505+
while !items_left.is_empty() {
506+
// If the next item is a `use` declaration, then extract it and any subsequent `use`s
507+
// to be potentially reordered within `format_imports`. Otherwise, just format the
508+
// next item for output.
509+
if self.config.reorder_imports && is_use_item(&*items_left[0]) {
510+
let use_item_length =
511+
items_left.iter().take_while(|ppi| is_use_item(&***ppi)).count();
512+
let (use_items, rest) = items_left.split_at(use_item_length);
513+
self.format_imports(use_items);
514+
items_left = rest;
515+
} else {
516+
// `unwrap()` is safe here because we know `items_left`
517+
// has elements from the loop condition
518+
let (item, rest) = items_left.split_first().unwrap();
519+
self.visit_item(&item);
520+
items_left = rest;
521+
}
507522
}
508523
}
509524

@@ -547,37 +562,6 @@ impl<'a> FmtVisitor<'a> {
547562
self.format_missing(filemap.end_pos);
548563
}
549564

550-
fn format_import(&mut self, vis: &ast::Visibility, vp: &ast::ViewPath, span: Span) {
551-
let vis = utils::format_visibility(vis);
552-
let mut offset = self.block_indent;
553-
offset.alignment += vis.len() + "use ".len();
554-
// 1 = ";"
555-
match vp.rewrite(&self.get_context(),
556-
self.config.max_width - offset.width() - 1,
557-
offset) {
558-
Some(ref s) if s.is_empty() => {
559-
// Format up to last newline
560-
let prev_span = codemap::mk_sp(self.last_pos, source!(self, span).lo);
561-
let span_end = match self.snippet(prev_span).rfind('\n') {
562-
Some(offset) => self.last_pos + BytePos(offset as u32),
563-
None => source!(self, span).lo,
564-
};
565-
self.format_missing(span_end);
566-
self.last_pos = source!(self, span).hi;
567-
}
568-
Some(ref s) => {
569-
let s = format!("{}use {};", vis, s);
570-
self.format_missing_with_indent(source!(self, span).lo);
571-
self.buffer.push_str(&s);
572-
self.last_pos = source!(self, span).hi;
573-
}
574-
None => {
575-
self.format_missing_with_indent(source!(self, span).lo);
576-
self.format_missing(source!(self, span).hi);
577-
}
578-
}
579-
}
580-
581565
pub fn get_context(&self) -> RewriteContext {
582566
RewriteContext {
583567
parse_session: self.parse_session,
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// rustfmt-reorder_imports: true
2+
// rustfmt-reorder_imported_names: true
3+
4+
use std::str;
5+
use std::cmp::{d, c, b, a};
6+
use std::ddd::aaa;
7+
use std::ddd::{d as p, c as g, b, a};
8+
// This comment should stay with `use std::ddd:bbb;`
9+
use std::ddd::bbb;

tests/source/imports-reorder-lines.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// rustfmt-reorder_imports: true
2+
3+
use std::str;
4+
use std::cmp::{d, c, b, a};
5+
use std::cmp::{b, e, g, f};
6+
use std::ddd::aaa;
7+
// This comment should stay with `use std::ddd;`
8+
use std::ddd;
9+
use std::ddd::bbb;
10+
11+
mod test {
12+
}
13+
14+
use aaa::bbb;
15+
use aaa;
16+
use aaa::*;
17+
18+
mod test {}
19+
// If item names are equal, order by rename
20+
21+
use test::{a as bb, b};
22+
use test::{a as aa, c};
23+
24+
mod test {}
25+
// If item names are equal, order by rename - no rename comes before a rename
26+
27+
use test::{a as bb, b};
28+
use test::{a, c};
29+
30+
mod test {}
31+
// `self` always comes first
32+
33+
use test::{a as aa, c};
34+
use test::{self as bb, b};

tests/source/imports-reorder.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// rustfmt-reorder_imports: true
1+
// rustfmt-reorder_imported_names: true
22

33
use path::{C,/*A*/ A, B /* B */, self /* self */};
44

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// rustfmt-reorder_imports: true
2+
// rustfmt-reorder_imported_names: true
3+
4+
use std::cmp::{a, b, c, d};
5+
use std::ddd::{a, b, c as g, d as p};
6+
use std::ddd::aaa;
7+
// This comment should stay with `use std::ddd:bbb;`
8+
use std::ddd::bbb;
9+
use std::str;

0 commit comments

Comments
 (0)