]> git.lizzy.rs Git - rust.git/blob - src/imports.rs
Reorder modules
[rust.git] / src / imports.rs
1 // Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 use std::cmp::Ordering;
12
13 use syntax::ast;
14 use syntax::codemap::{BytePos, Span};
15
16 use codemap::SpanUtils;
17 use comment::combine_strs_with_missing_comments;
18 use config::IndentStyle;
19 use lists::{definitive_tactic, itemize_list, write_list, DefinitiveListTactic, ListFormatting,
20             ListItem, Separator, SeparatorPlace, SeparatorTactic};
21 use rewrite::{Rewrite, RewriteContext};
22 use shape::Shape;
23 use spanned::Spanned;
24 use types::{rewrite_path, PathContext};
25 use utils::{format_visibility, mk_sp};
26 use visitor::{rewrite_extern_crate, FmtVisitor};
27
28 fn compare_path_segments(a: &ast::PathSegment, b: &ast::PathSegment) -> Ordering {
29     a.identifier.name.as_str().cmp(&b.identifier.name.as_str())
30 }
31
32 fn compare_paths(a: &ast::Path, b: &ast::Path) -> Ordering {
33     for segment in a.segments.iter().zip(b.segments.iter()) {
34         let ord = compare_path_segments(segment.0, segment.1);
35         if ord != Ordering::Equal {
36             return ord;
37         }
38     }
39     a.segments.len().cmp(&b.segments.len())
40 }
41
42 fn compare_use_trees(a: &ast::UseTree, b: &ast::UseTree, nested: bool) -> Ordering {
43     use ast::UseTreeKind::*;
44
45     // `use_nested_groups` is not yet supported, remove the `if !nested` when support will be
46     // fully added
47     if !nested {
48         let paths_cmp = compare_paths(&a.prefix, &b.prefix);
49         if paths_cmp != Ordering::Equal {
50             return paths_cmp;
51         }
52     }
53
54     match (&a.kind, &b.kind) {
55         (&Simple(ident_a), &Simple(ident_b)) => {
56             let name_a = &*path_to_imported_ident(&a.prefix).name.as_str();
57             let name_b = &*path_to_imported_ident(&b.prefix).name.as_str();
58             let name_ordering = if name_a == "self" {
59                 if name_b == "self" {
60                     Ordering::Equal
61                 } else {
62                     Ordering::Less
63                 }
64             } else if name_b == "self" {
65                 Ordering::Greater
66             } else {
67                 name_a.cmp(name_b)
68             };
69             if name_ordering == Ordering::Equal {
70                 if ident_a.name.as_str() != name_a {
71                     if ident_b.name.as_str() != name_b {
72                         ident_a.name.as_str().cmp(&ident_b.name.as_str())
73                     } else {
74                         Ordering::Greater
75                     }
76                 } else {
77                     Ordering::Less
78                 }
79             } else {
80                 name_ordering
81             }
82         }
83         (&Glob, &Glob) => Ordering::Equal,
84         (&Simple(_), _) | (&Glob, &Nested(_)) => Ordering::Less,
85         (&Nested(ref a_items), &Nested(ref b_items)) => {
86             let mut a = a_items
87                 .iter()
88                 .map(|&(ref tree, _)| tree.clone())
89                 .collect::<Vec<_>>();
90             let mut b = b_items
91                 .iter()
92                 .map(|&(ref tree, _)| tree.clone())
93                 .collect::<Vec<_>>();
94             a.sort_by(|a, b| compare_use_trees(a, b, true));
95             b.sort_by(|a, b| compare_use_trees(a, b, true));
96             for comparison_pair in a.iter().zip(b.iter()) {
97                 let ord = compare_use_trees(comparison_pair.0, comparison_pair.1, true);
98                 if ord != Ordering::Equal {
99                     return ord;
100                 }
101             }
102             a.len().cmp(&b.len())
103         }
104         (&Glob, &Simple(_)) | (&Nested(_), _) => Ordering::Greater,
105     }
106 }
107
108 fn compare_use_items(a: &ast::Item, b: &ast::Item) -> Ordering {
109     match (&a.node, &b.node) {
110         (&ast::ItemKind::Mod(..), &ast::ItemKind::Mod(..)) => {
111             a.ident.name.as_str().cmp(&b.ident.name.as_str())
112         }
113         (&ast::ItemKind::Use(ref a_tree), &ast::ItemKind::Use(ref b_tree)) => {
114             compare_use_trees(a_tree, b_tree, false)
115         }
116         (&ast::ItemKind::ExternCrate(ref a_name), &ast::ItemKind::ExternCrate(ref b_name)) => {
117             // `extern crate foo as bar;`
118             //               ^^^ Comparing this.
119             let a_orig_name =
120                 a_name.map_or_else(|| a.ident.name.as_str(), |symbol| symbol.as_str());
121             let b_orig_name =
122                 b_name.map_or_else(|| b.ident.name.as_str(), |symbol| symbol.as_str());
123             let result = a_orig_name.cmp(&b_orig_name);
124             if result != Ordering::Equal {
125                 return result;
126             }
127
128             // `extern crate foo as bar;`
129             //                      ^^^ Comparing this.
130             let result = match (a_name, b_name) {
131                 (Some(..), None) => Ordering::Greater,
132                 (None, Some(..)) => Ordering::Less,
133                 (None, None) => Ordering::Equal,
134                 (Some(..), Some(..)) => a.ident.name.as_str().cmp(&b.ident.name.as_str()),
135             };
136             result
137         }
138         _ => unreachable!(),
139     }
140 }
141
142 // TODO (some day) remove unused imports, expand globs, compress many single
143 // imports into a list import.
144
145 fn rewrite_prefix(path: &ast::Path, context: &RewriteContext, shape: Shape) -> Option<String> {
146     if path.segments.len() > 1 && path_to_imported_ident(path).to_string() == "self" {
147         let path = &ast::Path {
148             span: path.span,
149             segments: path.segments[..path.segments.len() - 1].to_owned(),
150         };
151         rewrite_path(context, PathContext::Import, None, path, shape)
152     } else {
153         rewrite_path(context, PathContext::Import, None, path, shape)
154     }
155 }
156
157 impl Rewrite for ast::UseTree {
158     fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> {
159         match self.kind {
160             ast::UseTreeKind::Nested(ref items) => {
161                 rewrite_nested_use_tree(shape, &self.prefix, items, self.span, context)
162             }
163             ast::UseTreeKind::Glob => {
164                 let prefix_shape = shape.sub_width(3)?;
165
166                 if !self.prefix.segments.is_empty() {
167                     let path_str = rewrite_prefix(&self.prefix, context, prefix_shape)?;
168                     Some(format!("{}::*", path_str))
169                 } else {
170                     Some("*".to_owned())
171                 }
172             }
173             ast::UseTreeKind::Simple(ident) => {
174                 let ident_str = ident.to_string();
175
176                 // 4 = " as ".len()
177                 let is_same_name_bind = path_to_imported_ident(&self.prefix) == ident;
178                 let prefix_shape = if is_same_name_bind {
179                     shape
180                 } else {
181                     shape.sub_width(ident_str.len() + 4)?
182                 };
183                 let path_str = rewrite_prefix(&self.prefix, context, prefix_shape)
184                     .unwrap_or_else(|| context.snippet(self.prefix.span).to_owned());
185
186                 if is_same_name_bind {
187                     Some(path_str)
188                 } else {
189                     Some(format!("{} as {}", path_str, ident_str))
190                 }
191             }
192         }
193     }
194 }
195
196 fn is_unused_import(tree: &ast::UseTree, attrs: &[ast::Attribute]) -> bool {
197     attrs.is_empty() && is_unused_import_inner(tree)
198 }
199
200 fn is_unused_import_inner(tree: &ast::UseTree) -> bool {
201     match tree.kind {
202         ast::UseTreeKind::Nested(ref items) => match items.len() {
203             0 => true,
204             1 => is_unused_import_inner(&items[0].0),
205             _ => false,
206         },
207         _ => false,
208     }
209 }
210
211 // Rewrite `use foo;` WITHOUT attributes.
212 fn rewrite_import(
213     context: &RewriteContext,
214     vis: &ast::Visibility,
215     tree: &ast::UseTree,
216     attrs: &[ast::Attribute],
217     shape: Shape,
218 ) -> Option<String> {
219     let vis = format_visibility(vis);
220     // 4 = `use `, 1 = `;`
221     let rw = shape
222         .offset_left(vis.len() + 4)
223         .and_then(|shape| shape.sub_width(1))
224         .and_then(|shape| {
225             // If we have an empty nested group with no attributes, we erase it
226             if is_unused_import(tree, attrs) {
227                 Some("".to_owned())
228             } else {
229                 tree.rewrite(context, shape)
230             }
231         });
232     match rw {
233         Some(ref s) if !s.is_empty() => Some(format!("{}use {};", vis, s)),
234         _ => rw,
235     }
236 }
237
238 /// Rewrite an inline mod.
239 fn rewrite_mod(item: &ast::Item) -> String {
240     let mut result = String::with_capacity(32);
241     result.push_str(&*format_visibility(&item.vis));
242     result.push_str("mod ");
243     result.push_str(&item.ident.to_string());
244     result.push(';');
245     result
246 }
247
248 fn rewrite_imports(
249     context: &RewriteContext,
250     use_items: &[&ast::Item],
251     shape: Shape,
252     span: Span,
253 ) -> Option<String> {
254     let items = itemize_list(
255         context.codemap,
256         use_items.iter(),
257         "",
258         ";",
259         |item| item.span().lo(),
260         |item| item.span().hi(),
261         |item| {
262             let attrs = ::visitor::filter_inline_attrs(&item.attrs, item.span());
263             let attrs_str = attrs.rewrite(context, shape)?;
264
265             let missed_span = if attrs.is_empty() {
266                 mk_sp(item.span.lo(), item.span.lo())
267             } else {
268                 mk_sp(attrs.last().unwrap().span.hi(), item.span.lo())
269             };
270
271             let item_str = match item.node {
272                 ast::ItemKind::Use(ref tree) => {
273                     rewrite_import(context, &item.vis, tree, &item.attrs, shape)?
274                 }
275                 ast::ItemKind::ExternCrate(..) => rewrite_extern_crate(context, item)?,
276                 ast::ItemKind::Mod(..) => rewrite_mod(item),
277                 _ => return None,
278             };
279
280             combine_strs_with_missing_comments(
281                 context,
282                 &attrs_str,
283                 &item_str,
284                 missed_span,
285                 shape,
286                 false,
287             )
288         },
289         span.lo(),
290         span.hi(),
291         false,
292     );
293     let mut item_pair_vec: Vec<_> = items.zip(use_items.iter()).collect();
294     item_pair_vec.sort_by(|a, b| compare_use_items(a.1, b.1));
295     let item_vec: Vec<_> = item_pair_vec.into_iter().map(|pair| pair.0).collect();
296
297     let fmt = ListFormatting {
298         tactic: DefinitiveListTactic::Vertical,
299         separator: "",
300         trailing_separator: SeparatorTactic::Never,
301         separator_place: SeparatorPlace::Back,
302         shape: shape,
303         ends_with_newline: true,
304         preserve_newline: false,
305         config: context.config,
306     };
307
308     write_list(&item_vec, &fmt)
309 }
310
311 impl<'a> FmtVisitor<'a> {
312     pub fn format_imports(&mut self, use_items: &[&ast::Item]) {
313         if use_items.is_empty() {
314             return;
315         }
316
317         let lo = use_items.first().unwrap().span().lo();
318         let hi = use_items.last().unwrap().span().hi();
319         let span = mk_sp(lo, hi);
320         let rw = rewrite_imports(&self.get_context(), use_items, self.shape(), span);
321         self.push_rewrite(span, rw);
322     }
323
324     pub fn format_import(&mut self, item: &ast::Item, tree: &ast::UseTree) {
325         let span = item.span;
326         let shape = self.shape();
327         let rw = rewrite_import(&self.get_context(), &item.vis, tree, &item.attrs, shape);
328         match rw {
329             Some(ref s) if s.is_empty() => {
330                 // Format up to last newline
331                 let prev_span = mk_sp(self.last_pos, source!(self, span).lo());
332                 let trimmed_snippet = self.snippet(prev_span).trim_right();
333                 let span_end = self.last_pos + BytePos(trimmed_snippet.len() as u32);
334                 self.format_missing(span_end);
335                 // We have an excessive newline from the removed import.
336                 if self.buffer.ends_with('\n') {
337                     self.buffer.pop();
338                     self.line_number -= 1;
339                 }
340                 self.last_pos = source!(self, span).hi();
341             }
342             Some(ref s) => {
343                 self.format_missing_with_indent(source!(self, span).lo());
344                 self.push_str(s);
345                 self.last_pos = source!(self, span).hi();
346             }
347             None => {
348                 self.format_missing_with_indent(source!(self, span).lo());
349                 self.format_missing(source!(self, span).hi());
350             }
351         }
352     }
353 }
354
355 fn rewrite_nested_use_tree_single(
356     context: &RewriteContext,
357     path_str: &str,
358     tree: &ast::UseTree,
359     shape: Shape,
360 ) -> Option<String> {
361     match tree.kind {
362         ast::UseTreeKind::Simple(rename) => {
363             let ident = path_to_imported_ident(&tree.prefix);
364             let mut item_str = rewrite_prefix(&tree.prefix, context, shape)?;
365             if item_str == "self" {
366                 item_str = "".to_owned();
367             }
368
369             let path_item_str = if path_str.is_empty() {
370                 if item_str.is_empty() {
371                     "self".to_owned()
372                 } else {
373                     item_str
374                 }
375             } else if item_str.is_empty() {
376                 path_str.to_owned()
377             } else {
378                 format!("{}::{}", path_str, item_str)
379             };
380
381             Some(if ident == rename {
382                 path_item_str
383             } else {
384                 format!("{} as {}", path_item_str, rename)
385             })
386         }
387         ast::UseTreeKind::Glob | ast::UseTreeKind::Nested(..) => {
388             // 2 = "::"
389             let nested_shape = shape.offset_left(path_str.len() + 2)?;
390             tree.rewrite(context, nested_shape)
391                 .map(|item| format!("{}::{}", path_str, item))
392         }
393     }
394 }
395
396 #[derive(Eq, PartialEq)]
397 enum ImportItem<'a> {
398     // `self` or `self as a`
399     SelfImport(&'a str),
400     // name_one, name_two, ...
401     SnakeCase(&'a str),
402     // NameOne, NameTwo, ...
403     CamelCase(&'a str),
404     // NAME_ONE, NAME_TWO, ...
405     AllCaps(&'a str),
406     // Failed to format the import item
407     Invalid,
408 }
409
410 impl<'a> ImportItem<'a> {
411     fn from_str(s: &str) -> ImportItem {
412         if s == "self" || s.starts_with("self as") {
413             ImportItem::SelfImport(s)
414         } else if s.chars().all(|c| c.is_lowercase() || c == '_' || c == ' ') {
415             ImportItem::SnakeCase(s)
416         } else if s.chars().all(|c| c.is_uppercase() || c == '_' || c == ' ') {
417             ImportItem::AllCaps(s)
418         } else {
419             ImportItem::CamelCase(s)
420         }
421     }
422
423     fn from_opt_str(s: Option<&String>) -> ImportItem {
424         s.map_or(ImportItem::Invalid, |s| ImportItem::from_str(s))
425     }
426
427     fn to_str(&self) -> Option<&str> {
428         match *self {
429             ImportItem::SelfImport(s)
430             | ImportItem::SnakeCase(s)
431             | ImportItem::CamelCase(s)
432             | ImportItem::AllCaps(s) => Some(s),
433             ImportItem::Invalid => None,
434         }
435     }
436
437     fn to_u32(&self) -> u32 {
438         match *self {
439             ImportItem::SelfImport(..) => 0,
440             ImportItem::SnakeCase(..) => 1,
441             ImportItem::CamelCase(..) => 2,
442             ImportItem::AllCaps(..) => 3,
443             ImportItem::Invalid => 4,
444         }
445     }
446 }
447
448 impl<'a> PartialOrd for ImportItem<'a> {
449     fn partial_cmp(&self, other: &ImportItem<'a>) -> Option<Ordering> {
450         Some(self.cmp(other))
451     }
452 }
453
454 impl<'a> Ord for ImportItem<'a> {
455     fn cmp(&self, other: &ImportItem<'a>) -> Ordering {
456         let res = self.to_u32().cmp(&other.to_u32());
457         if res != Ordering::Equal {
458             return res;
459         }
460         self.to_str().map_or(Ordering::Greater, |self_str| {
461             other
462                 .to_str()
463                 .map_or(Ordering::Less, |other_str| self_str.cmp(other_str))
464         })
465     }
466 }
467
468 // Pretty prints a multi-item import.
469 // If the path list is empty, it leaves the braces empty.
470 fn rewrite_nested_use_tree(
471     shape: Shape,
472     path: &ast::Path,
473     trees: &[(ast::UseTree, ast::NodeId)],
474     span: Span,
475     context: &RewriteContext,
476 ) -> Option<String> {
477     // Returns a different option to distinguish `::foo` and `foo`
478     let path_str = rewrite_path(context, PathContext::Import, None, path, shape)?;
479
480     match trees.len() {
481         0 => {
482             let shape = shape.offset_left(path_str.len() + 3)?;
483             return rewrite_path(context, PathContext::Import, None, path, shape)
484                 .map(|path_str| format!("{}::{{}}", path_str));
485         }
486         1 => {
487             return rewrite_nested_use_tree_single(context, &path_str, &trees[0].0, shape);
488         }
489         _ => (),
490     }
491
492     let path_str = if path_str.is_empty() {
493         path_str
494     } else {
495         format!("{}::", path_str)
496     };
497
498     // 2 = "{}"
499     let remaining_width = shape.width.checked_sub(path_str.len() + 2).unwrap_or(0);
500     let nested_indent = match context.config.imports_indent() {
501         IndentStyle::Block => shape.indent.block_indent(context.config),
502         // 1 = `{`
503         IndentStyle::Visual => shape.visual_indent(path_str.len() + 1).indent,
504     };
505
506     let nested_shape = match context.config.imports_indent() {
507         IndentStyle::Block => Shape::indented(nested_indent, context.config).sub_width(1)?,
508         IndentStyle::Visual => Shape::legacy(remaining_width, nested_indent),
509     };
510
511     let mut items = {
512         // Dummy value, see explanation below.
513         let mut items = vec![ListItem::from_str("")];
514         let iter = itemize_list(
515             context.codemap,
516             trees.iter().map(|tree| &tree.0),
517             "}",
518             ",",
519             |tree| tree.span.lo(),
520             |tree| tree.span.hi(),
521             |tree| tree.rewrite(context, nested_shape),
522             context.codemap.span_after(span, "{"),
523             span.hi(),
524             false,
525         );
526         items.extend(iter);
527         items
528     };
529
530     // We prefixed the item list with a dummy value so that we can
531     // potentially move "self" to the front of the vector without touching
532     // the rest of the items.
533     let has_self = move_self_to_front(&mut items);
534     let first_index = if has_self { 0 } else { 1 };
535
536     if context.config.reorder_imported_names() {
537         items[1..].sort_by(|a, b| {
538             let a = ImportItem::from_opt_str(a.item.as_ref());
539             let b = ImportItem::from_opt_str(b.item.as_ref());
540             a.cmp(&b)
541         });
542     }
543
544     let tactic = definitive_tactic(
545         &items[first_index..],
546         context.config.imports_layout(),
547         Separator::Comma,
548         remaining_width,
549     );
550
551     let ends_with_newline = context.config.imports_indent() == IndentStyle::Block
552         && tactic != DefinitiveListTactic::Horizontal;
553
554     let fmt = ListFormatting {
555         tactic: tactic,
556         separator: ",",
557         trailing_separator: if ends_with_newline {
558             context.config.trailing_comma()
559         } else {
560             SeparatorTactic::Never
561         },
562         separator_place: SeparatorPlace::Back,
563         shape: nested_shape,
564         ends_with_newline: ends_with_newline,
565         preserve_newline: true,
566         config: context.config,
567     };
568     let list_str = write_list(&items[first_index..], &fmt)?;
569
570     let result = if list_str.contains('\n') && context.config.imports_indent() == IndentStyle::Block
571     {
572         format!(
573             "{}{{\n{}{}\n{}}}",
574             path_str,
575             nested_shape.indent.to_string(context.config),
576             list_str,
577             shape.indent.to_string(context.config)
578         )
579     } else {
580         format!("{}{{{}}}", path_str, list_str)
581     };
582     Some(result)
583 }
584
585 // Returns true when self item was found.
586 fn move_self_to_front(items: &mut Vec<ListItem>) -> bool {
587     match items
588         .iter()
589         .position(|item| item.item.as_ref().map(|x| &x[..]) == Some("self"))
590     {
591         Some(pos) => {
592             items[0] = items.remove(pos);
593             true
594         }
595         None => false,
596     }
597 }
598
599 fn path_to_imported_ident(path: &ast::Path) -> ast::Ident {
600     path.segments.last().unwrap().identifier
601 }