]> git.lizzy.rs Git - rust.git/blob - src/imports.rs
Tidy up and pass tests
[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 config::lists::*;
14 use syntax::ast;
15 use syntax::codemap::{BytePos, Span};
16
17 use codemap::SpanUtils;
18 use config::IndentStyle;
19 use lists::{definitive_tactic, itemize_list, write_list, ListFormatting, ListItem, Separator};
20 use rewrite::{Rewrite, RewriteContext};
21 use shape::Shape;
22 use types::{rewrite_path, PathContext};
23 use utils::{format_visibility, mk_sp};
24 use visitor::FmtVisitor;
25
26 /// Returns a name imported by a `use` declaration. e.g. returns `Ordering`
27 /// for `std::cmp::Ordering` and `self` for `std::cmp::self`.
28 pub fn path_to_imported_ident(path: &ast::Path) -> ast::Ident {
29     path.segments.last().unwrap().identifier
30 }
31
32 fn rewrite_prefix(path: &ast::Path, context: &RewriteContext, shape: Shape) -> Option<String> {
33     if path.segments.len() > 1 && path_to_imported_ident(path).to_string() == "self" {
34         let path = &ast::Path {
35             span: path.span,
36             segments: path.segments[..path.segments.len() - 1].to_owned(),
37         };
38         rewrite_path(context, PathContext::Import, None, path, shape)
39     } else {
40         rewrite_path(context, PathContext::Import, None, path, shape)
41     }
42 }
43
44 impl Rewrite for ast::UseTree {
45     fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> {
46         match self.kind {
47             ast::UseTreeKind::Nested(ref items) => {
48                 rewrite_nested_use_tree(shape, &self.prefix, items, self.span, context)
49             }
50             ast::UseTreeKind::Glob => {
51                 let prefix_shape = shape.sub_width(3)?;
52
53                 if !self.prefix.segments.is_empty() {
54                     let path_str = rewrite_prefix(&self.prefix, context, prefix_shape)?;
55                     Some(format!("{}::*", path_str))
56                 } else {
57                     Some("*".to_owned())
58                 }
59             }
60             ast::UseTreeKind::Simple(ident) => {
61                 let ident_str = ident.to_string();
62
63                 // 4 = " as ".len()
64                 let is_same_name_bind = path_to_imported_ident(&self.prefix) == ident;
65                 let prefix_shape = if is_same_name_bind {
66                     shape
67                 } else {
68                     shape.sub_width(ident_str.len() + 4)?
69                 };
70                 let path_str = rewrite_prefix(&self.prefix, context, prefix_shape)
71                     .unwrap_or_else(|| context.snippet(self.prefix.span).to_owned());
72
73                 if is_same_name_bind {
74                     Some(path_str)
75                 } else {
76                     Some(format!("{} as {}", path_str, ident_str))
77                 }
78             }
79         }
80     }
81 }
82
83 fn is_unused_import(tree: &ast::UseTree, attrs: &[ast::Attribute]) -> bool {
84     attrs.is_empty() && is_unused_import_inner(tree)
85 }
86
87 fn is_unused_import_inner(tree: &ast::UseTree) -> bool {
88     match tree.kind {
89         ast::UseTreeKind::Nested(ref items) => match items.len() {
90             0 => true,
91             1 => is_unused_import_inner(&items[0].0),
92             _ => false,
93         },
94         _ => false,
95     }
96 }
97
98 // Rewrite `use foo;` WITHOUT attributes.
99 pub fn rewrite_import(
100     context: &RewriteContext,
101     vis: &ast::Visibility,
102     tree: &ast::UseTree,
103     attrs: &[ast::Attribute],
104     shape: Shape,
105 ) -> Option<String> {
106     let vis = format_visibility(vis);
107     // 4 = `use `, 1 = `;`
108     let rw = shape
109         .offset_left(vis.len() + 4)
110         .and_then(|shape| shape.sub_width(1))
111         .and_then(|shape| {
112             // If we have an empty nested group with no attributes, we erase it
113             if is_unused_import(tree, attrs) {
114                 Some("".to_owned())
115             } else {
116                 tree.rewrite(context, shape)
117             }
118         });
119     match rw {
120         Some(ref s) if !s.is_empty() => Some(format!("{}use {};", vis, s)),
121         _ => rw,
122     }
123 }
124
125 impl<'a> FmtVisitor<'a> {
126     pub fn format_import(&mut self, item: &ast::Item, tree: &ast::UseTree) {
127         let span = item.span;
128         let shape = self.shape();
129         let rw = rewrite_import(&self.get_context(), &item.vis, tree, &item.attrs, shape);
130         match rw {
131             Some(ref s) if s.is_empty() => {
132                 // Format up to last newline
133                 let prev_span = mk_sp(self.last_pos, source!(self, span).lo());
134                 let trimmed_snippet = self.snippet(prev_span).trim_right();
135                 let span_end = self.last_pos + BytePos(trimmed_snippet.len() as u32);
136                 self.format_missing(span_end);
137                 // We have an excessive newline from the removed import.
138                 if self.buffer.ends_with('\n') {
139                     self.buffer.pop();
140                     self.line_number -= 1;
141                 }
142                 self.last_pos = source!(self, span).hi();
143             }
144             Some(ref s) => {
145                 self.format_missing_with_indent(source!(self, span).lo());
146                 self.push_str(s);
147                 self.last_pos = source!(self, span).hi();
148             }
149             None => {
150                 self.format_missing_with_indent(source!(self, span).lo());
151                 self.format_missing(source!(self, span).hi());
152             }
153         }
154     }
155 }
156
157 fn rewrite_nested_use_tree_single(
158     context: &RewriteContext,
159     path_str: &str,
160     tree: &ast::UseTree,
161     shape: Shape,
162 ) -> Option<String> {
163     match tree.kind {
164         ast::UseTreeKind::Simple(rename) => {
165             let ident = path_to_imported_ident(&tree.prefix);
166             let mut item_str = rewrite_prefix(&tree.prefix, context, shape)?;
167             if item_str == "self" {
168                 item_str = "".to_owned();
169             }
170
171             let path_item_str = if path_str.is_empty() {
172                 if item_str.is_empty() {
173                     "self".to_owned()
174                 } else {
175                     item_str
176                 }
177             } else if item_str.is_empty() {
178                 path_str.to_owned()
179             } else {
180                 format!("{}::{}", path_str, item_str)
181             };
182
183             Some(if ident == rename {
184                 path_item_str
185             } else {
186                 format!("{} as {}", path_item_str, rename)
187             })
188         }
189         ast::UseTreeKind::Glob | ast::UseTreeKind::Nested(..) => {
190             // 2 = "::"
191             let nested_shape = shape.offset_left(path_str.len() + 2)?;
192             tree.rewrite(context, nested_shape)
193                 .map(|item| format!("{}::{}", path_str, item))
194         }
195     }
196 }
197
198 #[derive(Eq, PartialEq)]
199 enum ImportItem<'a> {
200     // `self` or `self as a`
201     SelfImport(&'a str),
202     // name_one, name_two, ...
203     SnakeCase(&'a str),
204     // NameOne, NameTwo, ...
205     CamelCase(&'a str),
206     // NAME_ONE, NAME_TWO, ...
207     AllCaps(&'a str),
208     // Failed to format the import item
209     Invalid,
210 }
211
212 impl<'a> ImportItem<'a> {
213     fn from_str(s: &str) -> ImportItem {
214         if s == "self" || s.starts_with("self as") {
215             ImportItem::SelfImport(s)
216         } else if s.chars().all(|c| c.is_lowercase() || c == '_' || c == ' ') {
217             ImportItem::SnakeCase(s)
218         } else if s.chars().all(|c| c.is_uppercase() || c == '_' || c == ' ') {
219             ImportItem::AllCaps(s)
220         } else {
221             ImportItem::CamelCase(s)
222         }
223     }
224
225     fn from_opt_str(s: Option<&String>) -> ImportItem {
226         s.map_or(ImportItem::Invalid, |s| ImportItem::from_str(s))
227     }
228
229     fn to_str(&self) -> Option<&str> {
230         match *self {
231             ImportItem::SelfImport(s)
232             | ImportItem::SnakeCase(s)
233             | ImportItem::CamelCase(s)
234             | ImportItem::AllCaps(s) => Some(s),
235             ImportItem::Invalid => None,
236         }
237     }
238
239     fn to_u32(&self) -> u32 {
240         match *self {
241             ImportItem::SelfImport(..) => 0,
242             ImportItem::SnakeCase(..) => 1,
243             ImportItem::CamelCase(..) => 2,
244             ImportItem::AllCaps(..) => 3,
245             ImportItem::Invalid => 4,
246         }
247     }
248 }
249
250 impl<'a> PartialOrd for ImportItem<'a> {
251     fn partial_cmp(&self, other: &ImportItem<'a>) -> Option<Ordering> {
252         Some(self.cmp(other))
253     }
254 }
255
256 impl<'a> Ord for ImportItem<'a> {
257     fn cmp(&self, other: &ImportItem<'a>) -> Ordering {
258         let res = self.to_u32().cmp(&other.to_u32());
259         if res != Ordering::Equal {
260             return res;
261         }
262         self.to_str().map_or(Ordering::Greater, |self_str| {
263             other
264                 .to_str()
265                 .map_or(Ordering::Less, |other_str| self_str.cmp(other_str))
266         })
267     }
268 }
269
270 // Pretty prints a multi-item import.
271 // If the path list is empty, it leaves the braces empty.
272 fn rewrite_nested_use_tree(
273     shape: Shape,
274     path: &ast::Path,
275     trees: &[(ast::UseTree, ast::NodeId)],
276     span: Span,
277     context: &RewriteContext,
278 ) -> Option<String> {
279     // Returns a different option to distinguish `::foo` and `foo`
280     let path_str = rewrite_path(context, PathContext::Import, None, path, shape)?;
281
282     match trees.len() {
283         0 => {
284             let shape = shape.offset_left(path_str.len() + 3)?;
285             return rewrite_path(context, PathContext::Import, None, path, shape)
286                 .map(|path_str| format!("{}::{{}}", path_str));
287         }
288         1 => {
289             return rewrite_nested_use_tree_single(context, &path_str, &trees[0].0, shape);
290         }
291         _ => (),
292     }
293
294     let path_str = if path_str.is_empty() {
295         path_str
296     } else {
297         format!("{}::", path_str)
298     };
299
300     // 2 = "{}"
301     let remaining_width = shape.width.checked_sub(path_str.len() + 2).unwrap_or(0);
302     let nested_indent = match context.config.imports_indent() {
303         IndentStyle::Block => shape.indent.block_indent(context.config),
304         // 1 = `{`
305         IndentStyle::Visual => shape.visual_indent(path_str.len() + 1).indent,
306     };
307
308     let nested_shape = match context.config.imports_indent() {
309         IndentStyle::Block => Shape::indented(nested_indent, context.config).sub_width(1)?,
310         IndentStyle::Visual => Shape::legacy(remaining_width, nested_indent),
311     };
312
313     let mut items = {
314         // Dummy value, see explanation below.
315         let mut items = vec![ListItem::from_str("")];
316         let iter = itemize_list(
317             context.snippet_provider,
318             trees.iter().map(|tree| &tree.0),
319             "}",
320             ",",
321             |tree| tree.span.lo(),
322             |tree| tree.span.hi(),
323             |tree| tree.rewrite(context, nested_shape),
324             context.snippet_provider.span_after(span, "{"),
325             span.hi(),
326             false,
327         );
328         items.extend(iter);
329         items
330     };
331
332     // We prefixed the item list with a dummy value so that we can
333     // potentially move "self" to the front of the vector without touching
334     // the rest of the items.
335     let has_self = move_self_to_front(&mut items);
336     let first_index = if has_self { 0 } else { 1 };
337
338     if context.config.reorder_imported_names() {
339         items[1..].sort_by(|a, b| {
340             let a = ImportItem::from_opt_str(a.item.as_ref());
341             let b = ImportItem::from_opt_str(b.item.as_ref());
342             a.cmp(&b)
343         });
344     }
345
346     let tactic = definitive_tactic(
347         &items[first_index..],
348         context.config.imports_layout(),
349         Separator::Comma,
350         remaining_width,
351     );
352
353     let ends_with_newline = context.config.imports_indent() == IndentStyle::Block
354         && tactic != DefinitiveListTactic::Horizontal;
355
356     let fmt = ListFormatting {
357         tactic,
358         separator: ",",
359         trailing_separator: if ends_with_newline {
360             context.config.trailing_comma()
361         } else {
362             SeparatorTactic::Never
363         },
364         separator_place: SeparatorPlace::Back,
365         shape: nested_shape,
366         ends_with_newline,
367         preserve_newline: true,
368         config: context.config,
369     };
370     let list_str = write_list(&items[first_index..], &fmt)?;
371
372     let result = if list_str.contains('\n') && context.config.imports_indent() == IndentStyle::Block
373     {
374         format!(
375             "{}{{\n{}{}\n{}}}",
376             path_str,
377             nested_shape.indent.to_string(context.config),
378             list_str,
379             shape.indent.to_string(context.config)
380         )
381     } else {
382         format!("{}{{{}}}", path_str, list_str)
383     };
384     Some(result)
385 }
386
387 // Returns true when self item was found.
388 fn move_self_to_front(items: &mut Vec<ListItem>) -> bool {
389     match items
390         .iter()
391         .position(|item| item.item.as_ref().map(|x| &x[..]) == Some("self"))
392     {
393         Some(pos) => {
394             items[0] = items.remove(pos);
395             true
396         }
397         None => false,
398     }
399 }