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.
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.
11 #![feature(box_syntax)]
12 #![feature(box_patterns)]
13 #![feature(rustc_private)]
14 #![feature(collections)]
18 #![feature(exit_status)]
21 // TODO we're going to allocate a whole bunch of temp Strings, is it worth
22 // keeping some scratch mem for this and running our own StrPool?
29 extern crate rustc_driver;
32 use rustc::session::Session;
33 use rustc::session::config::{self, Input};
34 use rustc_driver::{driver, CompilerCalls, Compilation};
36 use syntax::{ast, ptr};
37 use syntax::codemap::{self, CodeMap, Span, Pos, BytePos};
38 use syntax::diagnostics;
39 use syntax::parse::token;
40 use syntax::print::pprust;
43 use std::path::PathBuf;
44 use std::slice::SliceConcatExt;
46 use changes::ChangeSet;
49 pub mod string_buffer;
52 const IDEAL_WIDTH: usize = 80;
53 const LEEWAY: usize = 5;
54 const MAX_WIDTH: usize = 100;
55 const MIN_STRING: usize = 10;
57 // Formatting which depends on the AST.
58 fn fmt_ast<'a>(krate: &ast::Crate, codemap: &'a CodeMap) -> ChangeSet<'a> {
59 let mut visitor = FmtVisitor::from_codemap(codemap);
60 visit::walk_crate(&mut visitor, krate);
61 let files = codemap.files.borrow();
62 if let Some(last) = files.last() {
63 visitor.format_missing(last.end_pos);
69 // Formatting done on a char by char basis.
70 fn fmt_lines(changes: &mut ChangeSet) {
71 // Iterate over the chars in the change set.
72 for (f, text) in changes.text() {
73 let mut trims = vec![];
74 let mut last_wspace = None;
77 for (c, b) in text.chars() {
78 if c == '\n' { // TOOD test for \r too
79 // Check for (and record) trailing whitespace.
80 if let Some(lw) = last_wspace {
81 trims.push((cur_line, lw, b));
84 // Check for any line width errors we couldn't correct.
85 if line_len > MAX_WIDTH {
86 // FIXME store the error rather than reporting immediately.
87 println!("Rustfmt couldn't fix (sorry). {}:{}: line longer than {} characters",
88 f, cur_line, MAX_WIDTH);
95 if c.is_whitespace() {
96 if last_wspace.is_none() {
97 last_wspace = Some(b);
105 for &(l, _, _) in trims.iter() {
106 // FIXME store the error rather than reporting immediately.
107 println!("Rustfmt left trailing whitespace at {}:{} (sorry)", f, l);
112 struct FmtVisitor<'a> {
113 codemap: &'a CodeMap,
114 changes: ChangeSet<'a>,
119 impl<'a, 'v> visit::Visitor<'v> for FmtVisitor<'a> {
120 fn visit_expr(&mut self, ex: &'v ast::Expr) {
121 self.format_missing(ex.span.lo);
122 let offset = self.changes.cur_offset_span(ex.span);
123 let new_str = self.rewrite_expr(ex, MAX_WIDTH - offset, offset);
124 self.changes.push_str_span(ex.span, &new_str);
125 self.last_pos = ex.span.hi;
128 fn visit_block(&mut self, b: &'v ast::Block) {
129 self.format_missing(b.span.lo);
131 self.changes.push_str_span(b.span, "{");
132 self.last_pos = self.last_pos + BytePos(1);
133 self.block_indent += 4;
135 for stmt in &b.stmts {
136 self.format_missing_with_indent(stmt.span.lo);
137 self.visit_stmt(&**stmt)
141 self.format_missing_with_indent(e.span.lo);
147 self.block_indent -= 4;
148 // TODO we should compress any newlines here to just one
149 self.format_missing_with_indent(b.span.hi - BytePos(1));
150 self.changes.push_str_span(b.span, "}");
151 self.last_pos = b.span.hi;
154 fn visit_fn(&mut self,
155 fk: visit::FnKind<'v>,
160 if let Some(new_str) = self.formal_args(fk, fd) {
161 self.changes.push_str_span(s, &new_str);
163 visit::walk_fn(self, fk, fd, b, s);
166 fn visit_item(&mut self, item: &'v ast::Item) {
168 ast::Item_::ItemUse(ref vp) => {
170 ast::ViewPath_::ViewPathList(ref path, ref path_list) => {
171 self.format_missing(item.span.lo);
172 let new_str = self.fix_use_list(path, path_list, vp.span);
173 self.changes.push_str_span(item.span, &new_str);
174 self.last_pos = item.span.hi;
176 ast::ViewPath_::ViewPathGlob(_) => {
177 // FIXME convert to list?
181 visit::walk_item(self, item);
183 ast::Item_::ItemImpl(..) => {
184 self.block_indent += 4;
185 visit::walk_item(self, item);
186 self.block_indent -= 4;
189 visit::walk_item(self, item);
195 fn make_indent(width: usize) -> String {
196 let mut indent = String::with_capacity(width);
203 impl<'a> FmtVisitor<'a> {
204 fn from_codemap<'b>(codemap: &'b CodeMap) -> FmtVisitor<'b> {
207 changes: ChangeSet::from_codemap(codemap),
208 last_pos: BytePos(0),
213 fn format_missing(&mut self, end: BytePos) {
214 self.format_missing_inner(end, |this, last_snippet, span, _| {
215 this.changes.push_str_span(span, last_snippet)
219 fn format_missing_with_indent(&mut self, end: BytePos) {
220 self.format_missing_inner(end, |this, last_snippet, span, snippet| {
221 if last_snippet == snippet {
223 this.changes.push_str_span(span, last_snippet);
224 this.changes.push_str_span(span, "\n");
226 this.changes.push_str_span(span, last_snippet.trim_right());
228 let indent = make_indent(this.block_indent);
229 this.changes.push_str_span(span, &indent);
233 fn format_missing_inner<F: Fn(&mut FmtVisitor, &str, Span, &str)>(&mut self,
235 process_last_snippet: F)
237 let start = self.last_pos;
238 // TODO(#11) gets tricky if we're missing more than one file
239 assert!(self.codemap.lookup_char_pos(start).file.name == self.codemap.lookup_char_pos(end).file.name,
240 "not implemented: unformated span across files");
243 let span = codemap::mk_sp(start, end);
244 let snippet = self.snippet(span);
246 // Annoyingly, the library functions for splitting by lines etc. are not
247 // quite right, so we must do it ourselves.
248 let mut line_start = 0;
249 let mut last_wspace = None;
250 for (i, c) in snippet.char_indices() {
252 if let Some(lw) = last_wspace {
253 self.changes.push_str_span(span, &snippet[line_start..lw]);
254 self.changes.push_str_span(span, "\n");
256 self.changes.push_str_span(span, &snippet[line_start..i+1]);
262 if c.is_whitespace() {
263 if last_wspace.is_none() {
264 last_wspace = Some(i);
271 process_last_snippet(self, &snippet[line_start..], span, &snippet);
274 fn snippet(&self, span: Span) -> String {
275 match self.codemap.span_to_snippet(span) {
278 println!("Couldn't make snippet for span {:?}", span);
285 fn rewrite_string(&mut self, s: &str, span: Span, width: usize, offset: usize) -> String {
286 // FIXME I bet this stomps unicode escapes in the source string
288 // Check if there is anything to fix: we always try to fixup multi-line
289 // strings, or if the string is too long for the line.
290 let l_loc = self.codemap.lookup_char_pos(span.lo);
291 let r_loc = self.codemap.lookup_char_pos(span.hi);
292 if l_loc.line == r_loc.line && r_loc.col.to_usize() <= MAX_WIDTH {
293 return self.snippet(span);
296 // TODO if lo.col > IDEAL - 10, start a new line (need cur indent for that)
298 let s = s.escape_default();
300 let offset = offset + 1;
301 let indent = make_indent(offset);
302 let indent = &indent;
304 let max_chars = width - 1;
306 let mut cur_start = 0;
307 let mut result = String::new();
310 let mut cur_end = cur_start + max_chars;
312 if cur_end >= s.len() {
313 result.push_str(&s[cur_start..]);
317 // Make sure we're on a char boundary.
318 cur_end = next_char(&s, cur_end);
320 // Push cur_end left until we reach whitespace
321 while !s.char_at(cur_end-1).is_whitespace() {
322 cur_end = prev_char(&s, cur_end);
324 if cur_end - cur_start < MIN_STRING {
325 // We can't break at whitespace, fall back to splitting
326 // anywhere that doesn't break an escape sequence
327 cur_end = next_char(&s, cur_start + max_chars);
328 while s.char_at(cur_end) == '\\' {
329 cur_end = prev_char(&s, cur_end);
333 // Make sure there is no whitespace to the right of the break.
334 while cur_end < s.len() && s.char_at(cur_end).is_whitespace() {
335 cur_end = next_char(&s, cur_end+1);
337 result.push_str(&s[cur_start..cur_end]);
338 result.push_str("\\\n");
339 result.push_str(indent);
348 // Basically just pretty prints a multi-item import.
349 fn fix_use_list(&mut self,
351 path_list: &[ast::PathListItem],
352 vp_span: Span) -> String {
353 // FIXME remove unused imports
355 // FIXME check indentation
356 let l_loc = self.codemap.lookup_char_pos(vp_span.lo);
357 let path_str = pprust::path_to_string(&path);
358 let indent = l_loc.col.0;
359 // After accounting for the overhead, how much space left for
360 // the item list? ( 5 = :: + { + } + ; )
361 let space = IDEAL_WIDTH - (indent + path_str.len() + 5);
362 // 4 = `use` + one space
363 // TODO might be pub use
364 let indent = make_indent(indent-4);
366 let mut cur_str = String::new();
367 let mut first = true;
368 // If `self` is in the list, put it first.
369 if path_list.iter().any(|vpi|
370 if let ast::PathListItem_::PathListMod{ .. } = vpi.node {
376 cur_str = "self".to_string();
380 let mut new_str = String::new();
381 for vpi in path_list.iter() {
383 ast::PathListItem_::PathListIdent{ name, .. } => {
384 let next_item = &token::get_ident(name);
385 if cur_str.len() + next_item.len() > space {
386 let cur_line = format!("{}use {}::{{{}}};\n", indent, path_str, cur_str);
387 new_str.push_str(&cur_line);
389 cur_str = String::new();
396 cur_str.push_str(", ");
399 cur_str.push_str(next_item);
401 ast::PathListItem_::PathListMod{ .. } => {}
406 let cur_line = format!("{}use {}::{{{}}};", indent, path_str, cur_str);
407 new_str.push_str(&cur_line);
412 fn formal_args<'v>(&mut self, fk: visit::FnKind<'v>, fd: &'v ast::FnDecl) -> Option<String> {
413 // For now, just check the arguments line up and make them per-row if the line is too long.
414 let args = &fd.inputs;
416 let ret_str = match fd.output {
417 ast::FunctionRetTy::DefaultReturn(_) => "".to_string(),
418 ast::FunctionRetTy::NoReturn(_) => " -> !".to_string(),
419 ast::FunctionRetTy::Return(ref ty) => pprust::ty_to_string(ty),
422 // TODO don't return, want to do the return type etc.
427 // TODO not really using the hi positions
428 let spans: Vec<_> = args.iter().map(|a| (a.pat.span.lo, a.ty.span.hi)).collect();
429 let locs: Vec<_> = spans.iter().map(|&(a, b)| {
430 (self.codemap.lookup_char_pos(a), self.codemap.lookup_char_pos(b))
432 let first_col = locs[0].0.col.0;
434 // Print up to the start of the args.
435 self.format_missing(spans[0].0);
436 self.last_pos = spans.last().unwrap().1;
438 let arg_strs: Vec<_> = args.iter().map(|a| format!("{}: {}",
439 pprust::pat_to_string(&a.pat),
440 pprust::ty_to_string(&a.ty))).collect();
442 // Try putting everything on one row:
443 let mut len = arg_strs.iter().fold(0, |a, b| a + b.len());
444 // Account for punctuation and spacing.
445 len += 2 * arg_strs.len() + 2 * (arg_strs.len()-1);
447 len += ret_str.len();
448 // Opening brace if no where clause.
450 visit::FnKind::FkItemFn(_, g, _, _) |
451 visit::FnKind::FkMethod(_, g, _)
452 if g.where_clause.predicates.len() > 0 => {}
453 _ => len += 2 // ` {`
457 if len <= IDEAL_WIDTH + LEEWAY || args.len() == 1 {
458 // It should all fit on one line.
459 return Some(arg_strs.connect(", "));
462 let mut indent = String::with_capacity(first_col + 2);
463 indent.push_str(",\n");
464 for _ in 0..first_col { indent.push(' '); }
465 return Some(arg_strs.connect(&indent));
469 fn rewrite_call(&mut self,
471 args: &[ptr::P<ast::Expr>],
476 debug!("rewrite_call, width: {}, offset: {}", width, offset);
478 // TODO using byte lens instead of char lens (and probably all over the place too)
479 let callee_str = self.rewrite_expr(callee, width, offset);
480 debug!("rewrite_call, callee_str: `{}`", callee_str);
482 let remaining_width = width - callee_str.len() - 2;
483 let offset = callee_str.len() + 1 + offset;
484 let arg_count = args.len();
485 let args: Vec<_> = args.iter().map(|e| self.rewrite_expr(e,
488 debug!("rewrite_call, args: `{}`", args.connect(","));
490 let multi_line = args.iter().any(|s| s.contains('\n'));
491 let args_width = args.iter().map(|s| s.len()).fold(0, |a, l| a + l);
492 let over_wide = args_width + (arg_count - 1) * 2 > remaining_width;
493 let args_str = if multi_line || over_wide {
494 args.connect(&(",\n".to_string() + &make_indent(offset)))
499 format!("{}({})", callee_str, args_str)
502 fn rewrite_expr(&mut self, expr: &ast::Expr, width: usize, offset: usize) -> String {
504 ast::Expr_::ExprLit(ref l) => {
506 ast::Lit_::LitStr(ref is, _) => {
507 return self.rewrite_string(&is, l.span, width, offset);
512 ast::Expr_::ExprCall(ref callee, ref args) => {
513 return self.rewrite_call(callee, args, width, offset);
518 let result = self.snippet(expr.span);
519 debug!("snippet: {}", result);
525 fn prev_char(s: &str, mut i: usize) -> usize {
526 if i == 0 { return 0; }
529 while !s.is_char_boundary(i) {
536 fn next_char(s: &str, mut i: usize) -> usize {
537 if i >= s.len() { return s.len(); }
539 while !s.is_char_boundary(i) {
545 struct RustFmtCalls {
546 input_path: Option<PathBuf>,
549 impl<'a> CompilerCalls<'a> for RustFmtCalls {
550 fn early_callback(&mut self,
551 _: &getopts::Matches,
552 _: &diagnostics::registry::Registry)
554 Compilation::Continue
557 fn some_input(&mut self, input: Input, input_path: Option<PathBuf>) -> (Input, Option<PathBuf>) {
559 Some(ref ip) => self.input_path = Some(ip.clone()),
561 // FIXME should handle string input and write to stdout or something
562 panic!("No input path");
568 fn no_input(&mut self,
569 _: &getopts::Matches,
573 _: &diagnostics::registry::Registry)
574 -> Option<(Input, Option<PathBuf>)> {
575 panic!("No input supplied to RustFmt");
578 fn late_callback(&mut self,
579 _: &getopts::Matches,
585 Compilation::Continue
588 fn build_controller(&mut self, _: &Session) -> driver::CompileController<'a> {
589 let mut control = driver::CompileController::basic();
590 control.after_parse.stop = Compilation::Stop;
591 control.after_parse.callback = box |state| {
592 let krate = state.krate.unwrap();
593 let codemap = state.session.codemap();
594 let mut changes = fmt_ast(krate, codemap);
595 fmt_lines(&mut changes);
597 println!("{}", changes);
598 // FIXME(#5) Should be user specified whether to show or replace.
600 // TODO we stop before expansion, but we still seem to get expanded for loops which
601 // cause problems - probably a rustc bug
609 let args = std::os::args();
610 let mut call_ctxt = RustFmtCalls { input_path: None };
611 rustc_driver::run_compiler(&args, &mut call_ctxt);
612 std::env::set_exit_status(0);
616 // comments aren't in the AST, which makes processing them difficult, but then
617 // comments are complicated anyway. I think I am happy putting off tackling them
618 // for now. Long term the soluton is for comments to be in the AST, but that means
619 // only the libsyntax AST, not the rustc one, which means waiting for the ASTs
620 // to diverge one day....
622 // Once we do have comments, we just have to implement a simple word wrapping
623 // algorithm to keep the width under IDEAL_WIDTH. We should also convert multiline
624 // /* ... */ comments to // and check doc comments are in the right place and of
627 // Should also make sure comments have the right indent