Skip to content

Commit f16cf12

Browse files
committed
Merge pull request #113 from marcusklaas/import-comments
Update import list formatting
2 parents 8cef678 + 482f200 commit f16cf12

File tree

14 files changed

+176
-62
lines changed

14 files changed

+176
-62
lines changed

src/comment.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,16 @@ pub fn rewrite_comment(orig: &str, block_style: bool, width: usize, offset: usiz
4646
line = &line[..(line.len() - 2)];
4747
}
4848

49-
line.trim_right_matches(' ')
49+
line.trim_right()
5050
})
5151
.map(left_trim_comment_line)
52+
.map(|line| {
53+
if line_breaks == 0 {
54+
line.trim_left()
55+
} else {
56+
line
57+
}
58+
})
5259
.fold((true, opener.to_owned()), |(first, mut acc), line| {
5360
if !first {
5461
acc.push('\n');
@@ -98,6 +105,8 @@ fn format_comments() {
98105
* men\n \
99106
* t */";
100107
assert_eq!(expected_output, rewrite_comment(input, true, 9, 69));
108+
109+
assert_eq!("/* trimmed */", rewrite_comment("/* trimmed */", true, 100, 100));
101110
}
102111

103112

@@ -156,6 +165,7 @@ fn test_find_uncommented() {
156165
check("/*sup yo? \n sup*/ sup", "p", Some(20));
157166
check("hel/*lohello*/lo", "hello", None);
158167
check("acb", "ab", None);
168+
check(",/*A*/ ", ",", Some(0));
159169
}
160170

161171
// Returns the first byte position after the first comment. The given string

src/config.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ pub struct Config {
2929
pub enum_trailing_comma: bool,
3030
pub report_todo: ReportTactic,
3131
pub report_fixme: ReportTactic,
32+
pub reorder_imports: bool, // Alphabetically, case sensitive.
3233
}
3334

3435
impl Config {

src/default.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ struct_lit_trailing_comma = "Vertical"
1111
enum_trailing_comma = true
1212
report_todo = "Always"
1313
report_fixme = "Never"
14+
reorder_imports = false

src/imports.rs

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

1111
use visitor::FmtVisitor;
12-
use lists::{write_list, ListItem, ListFormatting, SeparatorTactic, ListTactic};
13-
use utils::format_visibility;
12+
use lists::{write_list, itemize_list, ListItem, ListFormatting, SeparatorTactic, ListTactic};
13+
use utils::{span_after, format_visibility};
1414

1515
use syntax::ast;
1616
use syntax::parse::token;
1717
use syntax::print::pprust;
18+
use syntax::codemap::Span;
1819

1920
// TODO (some day) remove unused imports, expand globs, compress many single imports into a list import
2021

@@ -40,13 +41,14 @@ fn rewrite_single_use_list(path_str: String, vpi: ast::PathListItem, vis: &str)
4041
impl<'a> FmtVisitor<'a> {
4142
// Basically just pretty prints a multi-item import.
4243
// Returns None when the import can be removed.
43-
pub fn rewrite_use_list(&mut self,
44+
pub fn rewrite_use_list(&self,
4445
block_indent: usize,
4546
one_line_budget: usize, // excluding indentation
4647
multi_line_budget: usize,
4748
path: &ast::Path,
4849
path_list: &[ast::PathListItem],
49-
visibility: ast::Visibility) -> Option<String> {
50+
visibility: ast::Visibility,
51+
span: Span) -> Option<String> {
5052
let path_str = pprust::path_to_string(path);
5153
let vis = format_visibility(visibility);
5254

@@ -78,34 +80,53 @@ impl<'a> FmtVisitor<'a> {
7880
ends_with_newline: true,
7981
};
8082

81-
// TODO handle any comments inbetween items.
82-
// If `self` is in the list, put it first.
83-
let head = if path_list.iter().any(|vpi|
84-
if let ast::PathListItem_::PathListMod{ .. } = vpi.node {
85-
true
86-
} else {
87-
false
88-
}
89-
) {
90-
Some(ListItem::from_str("self"))
91-
} else {
92-
None
93-
};
83+
let mut items = itemize_list(self.codemap,
84+
vec![ListItem::from_str("")], // Dummy value, explanation below
85+
path_list.iter(),
86+
",",
87+
"}",
88+
|vpi| vpi.span.lo,
89+
|vpi| vpi.span.hi,
90+
|vpi| match vpi.node {
91+
ast::PathListItem_::PathListIdent{ name, .. } => {
92+
token::get_ident(name).to_string()
93+
}
94+
ast::PathListItem_::PathListMod{ .. } => {
95+
"self".to_owned()
96+
}
97+
},
98+
span_after(span, "{", self.codemap),
99+
span.hi);
100+
101+
// We prefixed the item list with a dummy value so that we can
102+
// potentially move "self" to the front of the vector without touching
103+
// the rest of the items.
104+
// FIXME: Make more efficient by using a linked list? That would
105+
// require changes to the signatures of itemize_list and write_list.
106+
let has_self = move_self_to_front(&mut items);
107+
let first_index = if has_self { 0 } else { 1 };
108+
109+
if self.config.reorder_imports {
110+
items.tail_mut().sort_by(|a, b| a.item.cmp(&b.item));
111+
}
94112

95-
let items: Vec<_> = head.into_iter().chain(path_list.iter().filter_map(|vpi| {
96-
match vpi.node {
97-
ast::PathListItem_::PathListIdent{ name, .. } => {
98-
Some(ListItem::from_str(token::get_ident(name).to_string()))
99-
}
100-
// Skip `self`, because we added it above.
101-
ast::PathListItem_::PathListMod{ .. } => None,
102-
}
103-
})).collect();
113+
let list = write_list(&items[first_index..], &fmt);
104114

105115
Some(if path_str.len() == 0 {
106-
format!("{}use {{{}}};", vis, write_list(&items, &fmt))
116+
format!("{}use {{{}}};", vis, list)
107117
} else {
108-
format!("{}use {}::{{{}}};", vis, path_str, write_list(&items, &fmt))
118+
format!("{}use {}::{{{}}};", vis, path_str, list)
109119
})
110120
}
111121
}
122+
123+
// Returns true when self item was found.
124+
fn move_self_to_front(items: &mut Vec<ListItem>) -> bool {
125+
match items.iter().position(|item| item.item == "self") {
126+
Some(pos) => {
127+
items[0] = items.remove(pos);
128+
true
129+
},
130+
None => false
131+
}
132+
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#![feature(rustc_private)]
1212
#![feature(str_escape)]
1313
#![feature(str_char)]
14+
#![feature(slice_extras)]
1415

1516
// TODO we're going to allocate a whole bunch of temp Strings, is it worth
1617
// keeping some scratch mem for this and running our own StrPool?

src/lists.rs

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ use syntax::codemap::{self, CodeMap, BytePos};
1414

1515
use utils::{round_up_to_power_of_two, make_indent};
1616
use comment::{FindUncommented, rewrite_comment, find_comment_end};
17-
use string::before;
1817

1918
#[derive(Eq, PartialEq, Debug, Copy, Clone)]
2019
pub enum ListTactic {
@@ -66,6 +65,10 @@ impl ListItem {
6665
self.post_comment.as_ref().map(|s| s.contains('\n')).unwrap_or(false)
6766
}
6867

68+
pub fn has_line_pre_comment(&self) -> bool {
69+
self.pre_comment.as_ref().map_or(false, |comment| comment.starts_with("//"))
70+
}
71+
6972
pub fn from_str<S: Into<String>>(s: S) -> ListItem {
7073
ListItem {
7174
pre_comment: None,
@@ -115,7 +118,7 @@ pub fn write_list<'b>(items: &[ListItem], formatting: &ListFormatting<'b>) -> St
115118
}
116119

117120
// Switch to vertical mode if we find non-block comments.
118-
if items.iter().any(has_line_pre_comment) {
121+
if items.iter().any(ListItem::has_line_pre_comment) {
119122
tactic = ListTactic::Vertical;
120123
}
121124

@@ -223,13 +226,6 @@ pub fn write_list<'b>(items: &[ListItem], formatting: &ListFormatting<'b>) -> St
223226
result
224227
}
225228

226-
fn has_line_pre_comment(item: &ListItem) -> bool {
227-
match item.pre_comment {
228-
Some(ref comment) => comment.starts_with("//"),
229-
None => false
230-
}
231-
}
232-
233229
// Turns a list into a vector of items with associated comments.
234230
// TODO: we probably do not want to take a terminator any more. Instead, we
235231
// should demand a proper span end.
@@ -250,6 +246,8 @@ pub fn itemize_list<T, I, F1, F2, F3>(codemap: &CodeMap,
250246
F3: Fn(&T) -> String
251247
{
252248
let mut result = prefix;
249+
result.reserve(it.size_hint().0);
250+
253251
let mut new_it = it.peekable();
254252
let white_space: &[_] = &[' ', '\t'];
255253

@@ -276,14 +274,27 @@ pub fn itemize_list<T, I, F1, F2, F3>(codemap: &CodeMap,
276274

277275
let comment_end = match new_it.peek() {
278276
Some(..) => {
279-
if let Some(start) = before(&post_snippet, "/*", "\n") {
277+
let block_open_index = post_snippet.find("/*");
278+
let newline_index = post_snippet.find('\n');
279+
let separator_index = post_snippet.find_uncommented(separator).unwrap();
280+
281+
match (block_open_index, newline_index) {
282+
// Separator before comment, with the next item on same line.
283+
// Comment belongs to next item.
284+
(Some(i), None) if i > separator_index => { separator_index + separator.len() }
285+
// Block-style post-comment before the separator.
286+
(Some(i), None) => {
287+
cmp::max(find_comment_end(&post_snippet[i..]).unwrap() + i,
288+
separator_index + separator.len())
289+
}
280290
// Block-style post-comment. Either before or after the separator.
281-
cmp::max(find_comment_end(&post_snippet[start..]).unwrap() + start,
282-
post_snippet.find_uncommented(separator).unwrap() + separator.len())
283-
} else if let Some(idx) = post_snippet.find('\n') {
284-
idx + 1
285-
} else {
286-
post_snippet.len()
291+
(Some(i), Some(j)) if i < j => {
292+
cmp::max(find_comment_end(&post_snippet[i..]).unwrap() + i,
293+
separator_index + separator.len())
294+
}
295+
// Potential *single* line comment.
296+
(_, Some(j)) => { j + 1 }
297+
_ => post_snippet.len()
287298
}
288299
},
289300
None => {
@@ -292,6 +303,7 @@ pub fn itemize_list<T, I, F1, F2, F3>(codemap: &CodeMap,
292303
}
293304
};
294305

306+
// Cleanup post-comment: strip separators and whitespace.
295307
prev_span_end = get_hi(&item) + BytePos(comment_end as u32);
296308
let mut post_snippet = post_snippet[..comment_end].trim();
297309

src/string.rs

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -88,16 +88,3 @@ pub fn rewrite_string<'a>(s: &str, fmt: &StringFormat<'a>) -> String {
8888

8989
result
9090
}
91-
92-
#[inline]
93-
// Checks if a appears before b in given string and, if so, returns the index of
94-
// a.
95-
// FIXME: could be more generic
96-
pub fn before<'x>(s: &'x str, a: &str, b: &str) -> Option<usize> {
97-
s.find(a).and_then(|i| {
98-
match s.find(b) {
99-
Some(j) if j <= i => None,
100-
_ => Some(i)
101-
}
102-
})
103-
}

src/visitor.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,8 @@ impl<'a, 'v> visit::Visitor<'v> for FmtVisitor<'a> {
168168
multi_line_budget,
169169
path,
170170
path_list,
171-
item.vis);
171+
item.vis,
172+
item.span);
172173

173174
if let Some(new_str) = formatted {
174175
self.format_missing_with_indent(item.span.lo);
@@ -186,9 +187,12 @@ impl<'a, 'v> visit::Visitor<'v> for FmtVisitor<'a> {
186187
self.last_pos = item.span.hi;
187188
}
188189
ast::ViewPath_::ViewPathGlob(_) => {
190+
self.format_missing_with_indent(item.span.lo);
189191
// FIXME convert to list?
190192
}
191-
ast::ViewPath_::ViewPathSimple(_,_) => {}
193+
ast::ViewPath_::ViewPathSimple(_,_) => {
194+
self.format_missing_with_indent(item.span.lo);
195+
}
192196
}
193197
visit::walk_item(self, item);
194198
}

tests/config/reorder_imports.toml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
max_width = 100
2+
ideal_width = 80
3+
leeway = 5
4+
tab_spaces = 4
5+
newline_style = "Unix"
6+
fn_brace_style = "SameLineWhere"
7+
fn_return_indent = "WithArgs"
8+
fn_args_paren_newline = true
9+
struct_trailing_comma = "Vertical"
10+
struct_lit_trailing_comma = "Vertical"
11+
enum_trailing_comma = true
12+
report_todo = "Always"
13+
report_fixme = "Never"
14+
reorder_imports = true

tests/config/small_tabs.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ struct_lit_trailing_comma = "Vertical"
1111
enum_trailing_comma = true
1212
report_todo = "Always"
1313
report_fixme = "Never"
14+
reorder_imports = false

tests/source/imports-reorder.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// rustfmt-config: reorder_imports.toml
2+
3+
use path::{C,/*A*/ A, B /* B */, self /* self */};
4+
5+
use {ab, ac, aa, Z, b};

tests/source/imports.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Imports.
2+
3+
// Long import.
4+
use syntax::ast::{ItemForeignMod, ItemImpl, ItemMac, ItemMod, ItemStatic, ItemDefaultImpl};
5+
use exceedingly::looooooooooooooooooooooooooooooooooooooooooooooooooooooooooong::import::path::{ItemA,
6+
ItemB};
7+
8+
use list::{
9+
// Some item
10+
SomeItem /* Comment */, /* Another item */ AnotherItem /* Another Comment */, // Last Item
11+
LastItem
12+
};
13+
14+
use test::{ Other /* C */ , /* A */ self /* B */ };
15+
16+
use syntax::{self};
17+
use {/* Pre-comment! */
18+
Foo, Bar /* comment */};
19+
use Foo::{Bar, Baz};
20+
pub use syntax::ast::{Expr_, Expr, ExprAssign, ExprCall, ExprMethodCall, ExprPath};
21+
use syntax::some::{};
22+
23+
mod Foo {
24+
pub use syntax::ast::{
25+
ItemForeignMod,
26+
ItemImpl,
27+
ItemMac,
28+
ItemMod,
29+
ItemStatic,
30+
ItemDefaultImpl
31+
};
32+
33+
mod Foo2 {
34+
pub use syntax::ast::{ItemForeignMod, ItemImpl, ItemMac, ItemMod, ItemStatic, self, ItemDefaultImpl};
35+
}
36+
}
37+
38+
fn test() {
39+
use Baz::*;
40+
use Qux;
41+
}

tests/target/imports-reorder.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// rustfmt-config: reorder_imports.toml
2+
3+
use path::{self /* self */, /* A */ A, B /* B */, C};
4+
5+
use {Z, aa, ab, ac, b};

0 commit comments

Comments
 (0)