]> git.lizzy.rs Git - rust.git/blob - src/visitor.rs
Format rustfmt's own indices
[rust.git] / src / visitor.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 syntax::ast;
12 use syntax::codemap::{self, CodeMap, Span, BytePos};
13 use syntax::visit;
14
15 use strings::string_buffer::StringBuffer;
16
17 use Indent;
18 use utils;
19 use config::Config;
20 use rewrite::{Rewrite, RewriteContext};
21 use comment::rewrite_comment;
22 use macros::rewrite_macro;
23
24 pub struct FmtVisitor<'a> {
25     pub codemap: &'a CodeMap,
26     pub buffer: StringBuffer,
27     pub last_pos: BytePos,
28     // TODO: RAII util for indenting
29     pub block_indent: Indent,
30     pub config: &'a Config,
31 }
32
33 impl<'a, 'v> visit::Visitor<'v> for FmtVisitor<'a> {
34     // FIXME: We'd rather not format expressions here, as we have little
35     // context. How are we still reaching this?
36     fn visit_expr(&mut self, ex: &'v ast::Expr) {
37         debug!("visit_expr: {:?} {:?}",
38                self.codemap.lookup_char_pos(ex.span.lo),
39                self.codemap.lookup_char_pos(ex.span.hi));
40         self.format_missing(ex.span.lo);
41
42         let rewrite = ex.rewrite(&self.get_context(),
43                                  self.config.max_width - self.block_indent.width(),
44                                  self.block_indent);
45
46         if let Some(new_str) = rewrite {
47             self.buffer.push_str(&new_str);
48             self.last_pos = ex.span.hi;
49         }
50     }
51
52     fn visit_stmt(&mut self, stmt: &'v ast::Stmt) {
53         match stmt.node {
54             ast::Stmt_::StmtDecl(ref decl, _) => {
55                 match decl.node {
56                     ast::Decl_::DeclLocal(ref local) => self.visit_let(local, stmt.span),
57                     ast::Decl_::DeclItem(..) => visit::walk_stmt(self, stmt),
58                 }
59             }
60             ast::Stmt_::StmtExpr(ref ex, _) | ast::Stmt_::StmtSemi(ref ex, _) => {
61                 self.format_missing_with_indent(stmt.span.lo);
62                 let suffix = if let ast::Stmt_::StmtExpr(..) = stmt.node {
63                     ""
64                 } else {
65                     ";"
66                 };
67
68                 // 1 = trailing semicolon;
69                 let rewrite = ex.rewrite(&self.get_context(),
70                                          self.config.max_width - self.block_indent.width() -
71                                          suffix.len(),
72                                          self.block_indent);
73
74                 if let Some(new_str) = rewrite {
75                     self.buffer.push_str(&new_str);
76                     self.buffer.push_str(suffix);
77                     self.last_pos = stmt.span.hi;
78                 }
79             }
80             ast::Stmt_::StmtMac(ref _mac, _macro_style) => {
81                 self.format_missing_with_indent(stmt.span.lo);
82                 visit::walk_stmt(self, stmt);
83             }
84         }
85     }
86
87     fn visit_block(&mut self, b: &'v ast::Block) {
88         debug!("visit_block: {:?} {:?}",
89                self.codemap.lookup_char_pos(b.span.lo),
90                self.codemap.lookup_char_pos(b.span.hi));
91
92         // Check if this block has braces.
93         let snippet = self.snippet(b.span);
94         let has_braces = &snippet[..1] == "{" || &snippet[..6] == "unsafe";
95         let brace_compensation = if has_braces {
96             BytePos(1)
97         } else {
98             BytePos(0)
99         };
100
101         self.last_pos = self.last_pos + brace_compensation;
102         self.block_indent = self.block_indent.block_indent(self.config);
103         self.buffer.push_str("{");
104
105         for stmt in &b.stmts {
106             self.visit_stmt(&stmt)
107         }
108
109         match b.expr {
110             Some(ref e) => {
111                 self.format_missing_with_indent(e.span.lo);
112                 self.visit_expr(e);
113             }
114             None => {}
115         }
116
117         self.block_indent = self.block_indent.block_unindent(self.config);
118         // TODO: we should compress any newlines here to just one
119         self.format_missing_with_indent(b.span.hi - brace_compensation);
120         self.buffer.push_str("}");
121         self.last_pos = b.span.hi;
122     }
123
124     // Note that this only gets called for function definitions. Required methods
125     // on traits do not get handled here.
126     fn visit_fn(&mut self,
127                 fk: visit::FnKind<'v>,
128                 fd: &'v ast::FnDecl,
129                 b: &'v ast::Block,
130                 s: Span,
131                 _: ast::NodeId) {
132
133         let indent = self.block_indent;
134         let rewrite = match fk {
135             visit::FnKind::ItemFn(ident, ref generics, unsafety, constness, abi, vis) => {
136                 self.rewrite_fn(indent,
137                                 ident,
138                                 fd,
139                                 None,
140                                 generics,
141                                 unsafety,
142                                 constness,
143                                 abi,
144                                 vis,
145                                 codemap::mk_sp(s.lo, b.span.lo))
146             }
147             visit::FnKind::Method(ident, ref sig, vis) => {
148                 self.rewrite_fn(indent,
149                                 ident,
150                                 fd,
151                                 Some(&sig.explicit_self),
152                                 &sig.generics,
153                                 sig.unsafety,
154                                 sig.constness,
155                                 sig.abi,
156                                 vis.unwrap_or(ast::Visibility::Inherited),
157                                 codemap::mk_sp(s.lo, b.span.lo))
158             }
159             visit::FnKind::Closure => None,
160         };
161
162         if let Some(fn_str) = rewrite {
163             self.format_missing_with_indent(s.lo);
164             self.buffer.push_str(&fn_str);
165         } else {
166             self.format_missing(b.span.lo);
167         }
168
169         self.last_pos = b.span.lo;
170         self.visit_block(b)
171     }
172
173     fn visit_item(&mut self, item: &'v ast::Item) {
174         // Don't look at attributes for modules.
175         // We want to avoid looking at attributes in another file, which the AST
176         // doesn't distinguish. FIXME This is overly conservative and means we miss
177         // attributes on inline modules.
178         match item.node {
179             ast::Item_::ItemMod(_) => {}
180             _ => {
181                 if self.visit_attrs(&item.attrs) {
182                     return;
183                 }
184             }
185         }
186
187         match item.node {
188             ast::Item_::ItemUse(ref vp) => {
189                 self.format_import(item.vis, vp, item.span);
190             }
191             ast::Item_::ItemImpl(..) |
192             ast::Item_::ItemTrait(..) => {
193                 self.block_indent = self.block_indent.block_indent(self.config);
194                 visit::walk_item(self, item);
195                 self.block_indent = self.block_indent.block_unindent(self.config);
196             }
197             ast::Item_::ItemExternCrate(_) => {
198                 self.format_missing_with_indent(item.span.lo);
199                 let new_str = self.snippet(item.span);
200                 self.buffer.push_str(&new_str);
201                 self.last_pos = item.span.hi;
202             }
203             ast::Item_::ItemStruct(ref def, ref generics) => {
204                 self.format_missing_with_indent(item.span.lo);
205                 self.visit_struct(item.ident, item.vis, def, generics, item.span);
206             }
207             ast::Item_::ItemEnum(ref def, ref generics) => {
208                 self.format_missing_with_indent(item.span.lo);
209                 self.visit_enum(item.ident, item.vis, def, generics, item.span);
210                 self.last_pos = item.span.hi;
211             }
212             ast::Item_::ItemMod(ref module) => {
213                 self.format_missing_with_indent(item.span.lo);
214                 self.format_mod(module, item.span, item.ident);
215             }
216             ast::Item_::ItemMac(..) => {
217                 self.format_missing_with_indent(item.span.lo);
218                 // TODO: we cannot format these yet, because of a bad span.
219                 // See rust lang issue #28424.
220                 // visit::walk_item(self, item);
221             }
222             ast::Item_::ItemForeignMod(ref foreign_mod) => {
223                 self.format_missing_with_indent(item.span.lo);
224                 self.format_foreign_mod(foreign_mod, item.span);
225             }
226             _ => {
227                 visit::walk_item(self, item);
228             }
229         }
230     }
231
232     fn visit_trait_item(&mut self, ti: &'v ast::TraitItem) {
233         if self.visit_attrs(&ti.attrs) {
234             return;
235         }
236
237         if let ast::TraitItem_::MethodTraitItem(ref sig, None) = ti.node {
238             self.format_missing_with_indent(ti.span.lo);
239
240             let indent = self.block_indent;
241             let new_fn = self.rewrite_required_fn(indent, ti.ident, sig, ti.span);
242
243             if let Some(fn_str) = new_fn {
244                 self.buffer.push_str(&fn_str);
245                 self.last_pos = ti.span.hi;
246             }
247         }
248         // TODO: format trait types.
249
250         visit::walk_trait_item(self, ti)
251     }
252
253     fn visit_impl_item(&mut self, ii: &'v ast::ImplItem) {
254         if self.visit_attrs(&ii.attrs) {
255             return;
256         }
257         visit::walk_impl_item(self, ii)
258     }
259
260     fn visit_mac(&mut self, mac: &'v ast::Mac) {
261         // 1 = ;
262         let width = self.config.max_width - self.block_indent.width() - 1;
263         let rewrite = rewrite_macro(mac, &self.get_context(), width, self.block_indent);
264
265         if let Some(res) = rewrite {
266             self.buffer.push_str(&res);
267             self.last_pos = mac.span.hi;
268         }
269     }
270 }
271
272 impl<'a> FmtVisitor<'a> {
273     pub fn from_codemap<'b>(codemap: &'b CodeMap, config: &'b Config) -> FmtVisitor<'b> {
274         FmtVisitor {
275             codemap: codemap,
276             buffer: StringBuffer::new(),
277             last_pos: BytePos(0),
278             block_indent: Indent {
279                 block_indent: 0,
280                 alignment: 0,
281             },
282             config: config,
283         }
284     }
285
286     pub fn snippet(&self, span: Span) -> String {
287         match self.codemap.span_to_snippet(span) {
288             Ok(s) => s,
289             Err(_) => {
290                 println!("Couldn't make snippet for span {:?}->{:?}",
291                          self.codemap.lookup_char_pos(span.lo),
292                          self.codemap.lookup_char_pos(span.hi));
293                 "".to_owned()
294             }
295         }
296     }
297
298     // Returns true if we should skip the following item.
299     pub fn visit_attrs(&mut self, attrs: &[ast::Attribute]) -> bool {
300         if attrs.is_empty() {
301             return false;
302         }
303
304         let first = &attrs[0];
305         self.format_missing_with_indent(first.span.lo);
306
307         if utils::contains_skip(attrs) {
308             true
309         } else {
310             let rewrite = attrs.rewrite(&self.get_context(),
311                                         self.config.max_width - self.block_indent.width(),
312                                         self.block_indent)
313                                .unwrap();
314             self.buffer.push_str(&rewrite);
315             let last = attrs.last().unwrap();
316             self.last_pos = last.span.hi;
317             false
318         }
319     }
320
321     fn format_mod(&mut self, m: &ast::Mod, s: Span, ident: ast::Ident) {
322         debug!("FmtVisitor::format_mod: ident: {:?}, span: {:?}",
323                ident,
324                s);
325
326         // Decide whether this is an inline mod or an external mod.
327         let local_file_name = self.codemap.span_to_filename(s);
328         let is_internal = local_file_name == self.codemap.span_to_filename(m.inner);
329
330         // TODO: Should rewrite properly `mod X;`
331
332         if is_internal {
333             self.block_indent = self.block_indent.block_indent(self.config);
334             visit::walk_mod(self, m);
335             self.block_indent = self.block_indent.block_unindent(self.config);
336
337             self.format_missing_with_indent(m.inner.hi - BytePos(1));
338             self.buffer.push_str("}");
339             self.last_pos = m.inner.hi;
340         }
341     }
342
343     pub fn format_separate_mod(&mut self, m: &ast::Mod, filename: &str) {
344         let filemap = self.codemap.get_filemap(filename);
345         self.last_pos = filemap.start_pos;
346         self.block_indent = Indent::empty();
347         visit::walk_mod(self, m);
348         self.format_missing(filemap.end_pos);
349     }
350
351     fn format_import(&mut self, vis: ast::Visibility, vp: &ast::ViewPath, span: Span) {
352         let vis = utils::format_visibility(vis);
353         let mut offset = self.block_indent;
354         offset.alignment += vis.len() + "use ".len();
355         let context = RewriteContext {
356             codemap: self.codemap,
357             config: self.config,
358             block_indent: self.block_indent,
359             overflow_indent: Indent::empty(),
360         };
361         // 1 = ";"
362         match vp.rewrite(&context,
363                          self.config.max_width - offset.width() - 1,
364                          offset) {
365             Some(ref s) if s.is_empty() => {
366                 // Format up to last newline
367                 let prev_span = codemap::mk_sp(self.last_pos, span.lo);
368                 let span_end = match self.snippet(prev_span).rfind('\n') {
369                     Some(offset) => self.last_pos + BytePos(offset as u32),
370                     None => span.lo,
371                 };
372                 self.format_missing(span_end);
373                 self.last_pos = span.hi;
374             }
375             Some(ref s) => {
376                 let s = format!("{}use {};", vis, s);
377                 self.format_missing_with_indent(span.lo);
378                 self.buffer.push_str(&s);
379                 self.last_pos = span.hi;
380             }
381             None => {
382                 self.format_missing_with_indent(span.lo);
383                 self.format_missing(span.hi);
384             }
385         }
386     }
387
388     pub fn get_context(&self) -> RewriteContext {
389         RewriteContext {
390             codemap: self.codemap,
391             config: self.config,
392             block_indent: self.block_indent,
393             overflow_indent: Indent::empty(),
394         }
395     }
396 }
397
398 impl<'a> Rewrite for [ast::Attribute] {
399     fn rewrite(&self, context: &RewriteContext, _: usize, offset: Indent) -> Option<String> {
400         let mut result = String::new();
401         if self.is_empty() {
402             return Some(result);
403         }
404         let indent = offset.to_string(context.config);
405
406         for (i, a) in self.iter().enumerate() {
407             let a_str = context.snippet(a.span);
408
409             if i > 0 {
410                 let comment = context.snippet(codemap::mk_sp(self[i - 1].span.hi, a.span.lo));
411                 // This particular horror show is to preserve line breaks in between doc
412                 // comments. An alternative would be to force such line breaks to start
413                 // with the usual doc comment token.
414                 let multi_line = a_str.starts_with("//") && comment.matches('\n').count() > 1;
415                 let comment = comment.trim();
416                 if !comment.is_empty() {
417                     let comment = try_opt!(rewrite_comment(comment,
418                                                            false,
419                                                            context.config.max_width -
420                                                            offset.width(),
421                                                            offset,
422                                                            context.config));
423                     result.push_str(&indent);
424                     result.push_str(&comment);
425                     result.push('\n');
426                 } else if multi_line {
427                     result.push('\n');
428                 }
429                 result.push_str(&indent);
430             }
431
432             result.push_str(&a_str);
433
434             if i < self.len() - 1 {
435                 result.push('\n');
436             }
437         }
438
439         Some(result)
440     }
441 }