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)]
15 #![feature(exit_status)]
18 // TODO we're going to allocate a whole bunch of temp Strings, is it worth
19 // keeping some scratch mem for this and running our own StrPool?
20 // TODO for lint violations of names, emit a refactor script
23 // Fix fns and methods properly
26 // Smoke testing till we can use it
27 // end of multi-line string has wspace
28 // no newline at the end of doc.rs
35 extern crate rustc_driver;
40 use rustc::session::Session;
41 use rustc::session::config::{self, Input};
42 use rustc_driver::{driver, CompilerCalls, Compilation};
44 use syntax::{ast, ptr};
45 use syntax::codemap::{CodeMap, Span, Pos, BytePos};
46 use syntax::diagnostics;
47 use syntax::parse::token;
48 use syntax::print::pprust;
51 use std::path::PathBuf;
53 use changes::ChangeSet;
54 use lists::{write_list, ListFormatting, SeparatorTactic, ListTactic};
61 const IDEAL_WIDTH: usize = 80;
62 const LEEWAY: usize = 5;
63 const MAX_WIDTH: usize = 100;
64 const MIN_STRING: usize = 10;
65 const TAB_SPACES: usize = 4;
66 const FN_BRACE_STYLE: BraceStyle = BraceStyle::SameLineWhere;
67 const FN_RETURN_INDENT: ReturnIndent = ReturnIndent::WithArgs;
69 #[derive(Copy, Clone, Eq, PartialEq, Debug)]
72 // str is the extension of the new file
73 NewFile(&'static str),
74 // Write the output to stdout.
78 #[derive(Copy, Clone, Eq, PartialEq, Debug)]
82 // Prefer same line except where there is a where clause, in which case force
83 // the brace to the next line.
87 // How to indent a function's return type.
88 #[derive(Copy, Clone, Eq, PartialEq, Debug)]
90 // Aligned with the arguments
92 // Aligned with the where clause
96 // Formatting which depends on the AST.
97 fn fmt_ast<'a>(krate: &ast::Crate, codemap: &'a CodeMap) -> ChangeSet<'a> {
98 let mut visitor = FmtVisitor::from_codemap(codemap);
99 visit::walk_crate(&mut visitor, krate);
100 let files = codemap.files.borrow();
101 if let Some(last) = files.last() {
102 visitor.format_missing(last.end_pos);
108 // Formatting done on a char by char basis.
109 fn fmt_lines(changes: &mut ChangeSet) {
110 // Iterate over the chars in the change set.
111 for (f, text) in changes.text() {
112 let mut trims = vec![];
113 let mut last_wspace: Option<usize> = None;
114 let mut line_len = 0;
115 let mut cur_line = 1;
116 for (c, b) in text.chars() {
117 if c == '\n' { // TOOD test for \r too
118 // Check for (and record) trailing whitespace.
119 if let Some(lw) = last_wspace {
120 trims.push((cur_line, lw, b));
123 // Check for any line width errors we couldn't correct.
124 if line_len > MAX_WIDTH {
125 // FIXME store the error rather than reporting immediately.
126 println!("Rustfmt couldn't fix (sorry). {}:{}: line longer than {} characters",
127 f, cur_line, MAX_WIDTH);
134 if c.is_whitespace() {
135 if last_wspace.is_none() {
136 last_wspace = Some(b);
144 for &(l, _, _) in trims.iter() {
145 // FIXME store the error rather than reporting immediately.
146 println!("Rustfmt left trailing whitespace at {}:{} (sorry)", f, l);
151 struct FmtVisitor<'a> {
152 codemap: &'a CodeMap,
153 changes: ChangeSet<'a>,
155 // TODO RAII util for indenting
159 impl<'a, 'v> visit::Visitor<'v> for FmtVisitor<'a> {
160 fn visit_expr(&mut self, ex: &'v ast::Expr) {
161 debug!("visit_expr: {:?} {:?}",
162 self.codemap.lookup_char_pos(ex.span.lo),
163 self.codemap.lookup_char_pos(ex.span.hi));
164 self.format_missing(ex.span.lo);
165 let offset = self.changes.cur_offset_span(ex.span);
166 let new_str = self.rewrite_expr(ex, MAX_WIDTH - offset, offset);
167 self.changes.push_str_span(ex.span, &new_str);
168 self.last_pos = ex.span.hi;
171 fn visit_block(&mut self, b: &'v ast::Block) {
172 debug!("visit_block: {:?} {:?}",
173 self.codemap.lookup_char_pos(b.span.lo),
174 self.codemap.lookup_char_pos(b.span.hi));
175 self.format_missing(b.span.lo);
177 self.changes.push_str_span(b.span, "{");
178 self.last_pos = self.last_pos + BytePos(1);
179 self.block_indent += TAB_SPACES;
181 for stmt in &b.stmts {
182 self.format_missing_with_indent(stmt.span.lo);
183 self.visit_stmt(&stmt)
187 self.format_missing_with_indent(e.span.lo);
193 self.block_indent -= TAB_SPACES;
194 // TODO we should compress any newlines here to just one
195 // TODO somewhere here we are preserving bogus whitespace
196 self.format_missing_with_indent(b.span.hi - BytePos(1));
197 self.changes.push_str_span(b.span, "}");
198 self.last_pos = b.span.hi;
201 // Note that this only gets called for function defintions. Required methods
202 // on traits do not get handled here.
203 fn visit_fn(&mut self,
204 fk: visit::FnKind<'v>,
209 self.format_missing(s.lo);
210 self.last_pos = s.lo;
212 // TODO need to check against expected indent
213 let indent = self.codemap.lookup_char_pos(s.lo).col.0;
215 visit::FkItemFn(ident, ref generics, ref unsafety, ref abi, vis) => {
216 let new_fn = self.rewrite_fn(indent,
224 self.changes.push_str_span(s, &new_fn);
226 visit::FkMethod(ident, ref sig, vis) => {
227 let new_fn = self.rewrite_fn(indent,
230 Some(&sig.explicit_self),
234 vis.unwrap_or(ast::Visibility::Inherited));
235 self.changes.push_str_span(s, &new_fn);
237 visit::FkFnBlock(..) => {}
240 self.last_pos = b.span.lo;
244 fn visit_item(&mut self, item: &'v ast::Item) {
246 ast::Item_::ItemUse(ref vp) => {
248 ast::ViewPath_::ViewPathList(ref path, ref path_list) => {
249 self.format_missing(item.span.lo);
250 let new_str = self.rewrite_use_list(path, path_list, vp.span);
251 self.changes.push_str_span(item.span, &new_str);
252 self.last_pos = item.span.hi;
254 ast::ViewPath_::ViewPathGlob(_) => {
255 // FIXME convert to list?
259 visit::walk_item(self, item);
261 ast::Item_::ItemImpl(..) => {
262 self.block_indent += TAB_SPACES;
263 visit::walk_item(self, item);
264 self.block_indent -= TAB_SPACES;
267 visit::walk_item(self, item);
272 fn visit_mac(&mut self, mac: &'v ast::Mac) {
273 visit::walk_mac(self, mac)
276 fn visit_mod(&mut self, m: &'v ast::Mod, s: Span, _: ast::NodeId) {
277 // Only visit inline mods here.
278 if self.codemap.lookup_char_pos(s.lo).file.name !=
279 self.codemap.lookup_char_pos(m.inner.lo).file.name {
282 visit::walk_mod(self, m);
286 impl<'a> FmtVisitor<'a> {
287 fn from_codemap<'b>(codemap: &'b CodeMap) -> FmtVisitor<'b> {
290 changes: ChangeSet::from_codemap(codemap),
291 last_pos: BytePos(0),
296 fn snippet(&self, span: Span) -> String {
297 match self.codemap.span_to_snippet(span) {
300 println!("Couldn't make snippet for span {:?}->{:?}",
301 self.codemap.lookup_char_pos(span.lo),
302 self.codemap.lookup_char_pos(span.hi));
309 fn rewrite_string_lit(&mut self, s: &str, span: Span, width: usize, offset: usize) -> String {
310 // FIXME I bet this stomps unicode escapes in the source string
312 // Check if there is anything to fix: we always try to fixup multi-line
313 // strings, or if the string is too long for the line.
314 let l_loc = self.codemap.lookup_char_pos(span.lo);
315 let r_loc = self.codemap.lookup_char_pos(span.hi);
316 if l_loc.line == r_loc.line && r_loc.col.to_usize() <= MAX_WIDTH {
317 return self.snippet(span);
320 // TODO if lo.col > IDEAL - 10, start a new line (need cur indent for that)
322 let s = s.escape_default();
324 let offset = offset + 1;
325 let indent = make_indent(offset);
326 let indent = &indent;
328 let max_chars = width - 1;
330 let mut cur_start = 0;
331 let mut result = String::new();
334 let mut cur_end = cur_start + max_chars;
336 if cur_end >= s.len() {
337 result.push_str(&s[cur_start..]);
341 // Make sure we're on a char boundary.
342 cur_end = next_char(&s, cur_end);
344 // Push cur_end left until we reach whitespace
345 while !s.char_at(cur_end-1).is_whitespace() {
346 cur_end = prev_char(&s, cur_end);
348 if cur_end - cur_start < MIN_STRING {
349 // We can't break at whitespace, fall back to splitting
350 // anywhere that doesn't break an escape sequence
351 cur_end = next_char(&s, cur_start + max_chars);
352 while s.char_at(cur_end) == '\\' {
353 cur_end = prev_char(&s, cur_end);
357 // Make sure there is no whitespace to the right of the break.
358 while cur_end < s.len() && s.char_at(cur_end).is_whitespace() {
359 cur_end = next_char(&s, cur_end+1);
361 result.push_str(&s[cur_start..cur_end]);
362 result.push_str("\\\n");
363 result.push_str(indent);
372 // Basically just pretty prints a multi-item import.
373 fn rewrite_use_list(&mut self,
375 path_list: &[ast::PathListItem],
376 vp_span: Span) -> String {
377 // FIXME remove unused imports
379 // FIXME check indentation
380 let l_loc = self.codemap.lookup_char_pos(vp_span.lo);
382 let path_str = pprust::path_to_string(&path);
385 let indent = l_loc.col.0 + path_str.len() + 3;
386 let fmt = ListFormatting {
387 tactic: ListTactic::Mixed,
389 trailing_separator: SeparatorTactic::Never,
392 h_width: IDEAL_WIDTH - (indent + path_str.len() + 2),
393 v_width: IDEAL_WIDTH - (indent + path_str.len() + 2),
396 // TODO handle any comments inbetween items.
397 // If `self` is in the list, put it first.
398 let head = if path_list.iter().any(|vpi|
399 if let ast::PathListItem_::PathListMod{ .. } = vpi.node {
405 Some(("self".to_string(), String::new()))
410 let items: Vec<_> = head.into_iter().chain(path_list.iter().filter_map(|vpi| {
412 ast::PathListItem_::PathListIdent{ name, .. } => {
413 Some((token::get_ident(name).to_string(), String::new()))
415 // Skip `self`, because we added it above.
416 ast::PathListItem_::PathListMod{ .. } => None,
420 format!("use {}::{{{}}};", path_str, write_list(&items, &fmt))
424 fn rewrite_pred(&self, predicate: &ast::WherePredicate) -> String
427 // TODO assumes we'll always fit on one line...
429 &ast::WherePredicate::BoundPredicate(ast::WhereBoundPredicate{ref bound_lifetimes,
433 if bound_lifetimes.len() > 0 {
434 format!("for<{}> {}: {}",
435 bound_lifetimes.iter().map(|l| self.rewrite_lifetime_def(l)).collect::<Vec<_>>().connect(", "),
436 pprust::ty_to_string(bounded_ty),
437 bounds.iter().map(|b| self.rewrite_ty_bound(b)).collect::<Vec<_>>().connect("+"))
441 pprust::ty_to_string(bounded_ty),
442 bounds.iter().map(|b| self.rewrite_ty_bound(b)).collect::<Vec<_>>().connect("+"))
445 &ast::WherePredicate::RegionPredicate(ast::WhereRegionPredicate{ref lifetime,
449 pprust::lifetime_to_string(lifetime),
450 bounds.iter().map(|l| pprust::lifetime_to_string(l)).collect::<Vec<_>>().connect("+"))
452 &ast::WherePredicate::EqPredicate(ast::WhereEqPredicate{ref path, ref ty, ..}) => {
453 format!("{} = {}", pprust::path_to_string(path), pprust::ty_to_string(ty))
458 fn rewrite_lifetime_def(&self, lifetime: &ast::LifetimeDef) -> String
460 if lifetime.bounds.len() == 0 {
461 return pprust::lifetime_to_string(&lifetime.lifetime);
465 pprust::lifetime_to_string(&lifetime.lifetime),
466 lifetime.bounds.iter().map(|l| pprust::lifetime_to_string(l)).collect::<Vec<_>>().connect("+"))
469 fn rewrite_ty_bound(&self, bound: &ast::TyParamBound) -> String
472 ast::TyParamBound::TraitTyParamBound(ref tref, ast::TraitBoundModifier::None) => {
473 self.rewrite_poly_trait_ref(tref)
475 ast::TyParamBound::TraitTyParamBound(ref tref, ast::TraitBoundModifier::Maybe) => {
476 format!("?{}", self.rewrite_poly_trait_ref(tref))
478 ast::TyParamBound::RegionTyParamBound(ref l) => {
479 pprust::lifetime_to_string(l)
484 fn rewrite_ty_param(&self, ty_param: &ast::TyParam) -> String
486 let mut result = String::with_capacity(128);
487 result.push_str(&token::get_ident(ty_param.ident));
488 if ty_param.bounds.len() > 0 {
489 result.push_str(": ");
490 result.push_str(&ty_param.bounds.iter().map(|b| self.rewrite_ty_bound(b)).collect::<Vec<_>>().connect(", "));
492 if let Some(ref def) = ty_param.default {
493 result.push_str(" = ");
494 result.push_str(&pprust::ty_to_string(&def));
500 fn rewrite_poly_trait_ref(&self, t: &ast::PolyTraitRef) -> String
502 if t.bound_lifetimes.len() > 0 {
503 format!("for<{}> {}",
504 t.bound_lifetimes.iter().map(|l| self.rewrite_lifetime_def(l)).collect::<Vec<_>>().connect(", "),
505 pprust::path_to_string(&t.trait_ref.path))
508 pprust::path_to_string(&t.trait_ref.path)
512 fn rewrite_call(&mut self,
514 args: &[ptr::P<ast::Expr>],
519 debug!("rewrite_call, width: {}, offset: {}", width, offset);
521 // TODO using byte lens instead of char lens (and probably all over the place too)
522 let callee_str = self.rewrite_expr(callee, width, offset);
523 debug!("rewrite_call, callee_str: `{}`", callee_str);
525 let remaining_width = width - callee_str.len() - 2;
526 let offset = callee_str.len() + 1 + offset;
527 let arg_count = args.len();
529 let args_str = if arg_count > 0 {
530 let args: Vec<_> = args.iter().map(|e| (self.rewrite_expr(e,
532 offset), String::new())).collect();
533 // TODO move this into write_list
534 let tactics = if args.iter().any(|&(ref s, _)| s.contains('\n')) {
537 ListTactic::HorizontalVertical
539 let fmt = ListFormatting {
542 trailing_separator: SeparatorTactic::Never,
544 h_width: remaining_width,
545 v_width: remaining_width,
547 write_list(&args, &fmt)
552 format!("{}({})", callee_str, args_str)
555 fn rewrite_expr(&mut self, expr: &ast::Expr, width: usize, offset: usize) -> String {
557 ast::Expr_::ExprLit(ref l) => {
559 ast::Lit_::LitStr(ref is, _) => {
560 return self.rewrite_string_lit(&is, l.span, width, offset);
565 ast::Expr_::ExprCall(ref callee, ref args) => {
566 return self.rewrite_call(callee, args, width, offset);
571 let result = self.snippet(expr.span);
572 debug!("snippet: {}", result);
578 fn prev_char(s: &str, mut i: usize) -> usize {
579 if i == 0 { return 0; }
582 while !s.is_char_boundary(i) {
589 fn next_char(s: &str, mut i: usize) -> usize {
590 if i >= s.len() { return s.len(); }
592 while !s.is_char_boundary(i) {
599 fn make_indent(width: usize) -> String {
600 let mut indent = String::with_capacity(width);
607 struct RustFmtCalls {
608 input_path: Option<PathBuf>,
611 impl<'a> CompilerCalls<'a> for RustFmtCalls {
612 fn early_callback(&mut self,
613 _: &getopts::Matches,
614 _: &diagnostics::registry::Registry)
616 Compilation::Continue
619 fn some_input(&mut self,
621 input_path: Option<PathBuf>)
622 -> (Input, Option<PathBuf>) {
624 Some(ref ip) => self.input_path = Some(ip.clone()),
626 // FIXME should handle string input and write to stdout or something
627 panic!("No input path");
633 fn no_input(&mut self,
634 _: &getopts::Matches,
638 _: &diagnostics::registry::Registry)
639 -> Option<(Input, Option<PathBuf>)> {
640 panic!("No input supplied to RustFmt");
643 fn late_callback(&mut self,
644 _: &getopts::Matches,
650 Compilation::Continue
653 fn build_controller(&mut self, _: &Session) -> driver::CompileController<'a> {
654 let mut control = driver::CompileController::basic();
655 control.after_parse.stop = Compilation::Stop;
656 control.after_parse.callback = box |state| {
657 let krate = state.krate.unwrap();
658 let codemap = state.session.codemap();
659 let mut changes = fmt_ast(krate, codemap);
660 fmt_lines(&mut changes);
662 // FIXME(#5) Should be user specified whether to show or replace.
663 let result = changes.write_all_files(WriteMode::Display);
665 if let Err(msg) = result {
666 println!("Error writing files: {}", msg);
675 let args: Vec<_> = std::env::args().collect();
676 let mut call_ctxt = RustFmtCalls { input_path: None };
677 rustc_driver::run_compiler(&args, &mut call_ctxt);
678 std::env::set_exit_status(0);
681 // let fmt = ListFormatting {
682 // tactic: ListTactic::Horizontal,
684 // trailing_separator: SeparatorTactic::Vertical,
689 // let inputs = vec![(format!("foo"), String::new()),
690 // (format!("foo"), String::new()),
691 // (format!("foo"), String::new()),
692 // (format!("foo"), String::new()),
693 // (format!("foo"), String::new()),
694 // (format!("foo"), String::new()),
695 // (format!("foo"), String::new()),
696 // (format!("foo"), String::new())];
697 // let s = write_list(&inputs, &fmt);
698 // println!(" {}", s);
702 // comments aren't in the AST, which makes processing them difficult, but then
703 // comments are complicated anyway. I think I am happy putting off tackling them
704 // for now. Long term the soluton is for comments to be in the AST, but that means
705 // only the libsyntax AST, not the rustc one, which means waiting for the ASTs
706 // to diverge one day....
708 // Once we do have comments, we just have to implement a simple word wrapping
709 // algorithm to keep the width under IDEAL_WIDTH. We should also convert multiline
710 // /* ... */ comments to // and check doc comments are in the right place and of
713 // Should also make sure comments have the right indent