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 self.format_missing_with_indent(b.span.hi - BytePos(1));
196 self.changes.push_str_span(b.span, "}");
197 self.last_pos = b.span.hi;
200 // Note that this only gets called for function defintions. Required methods
201 // on traits do not get handled here.
202 fn visit_fn(&mut self,
203 fk: visit::FnKind<'v>,
208 self.format_missing(s.lo);
209 self.last_pos = s.lo;
211 // TODO need to check against expected indent
212 let indent = self.codemap.lookup_char_pos(s.lo).col.0;
214 visit::FkItemFn(ident, ref generics, ref unsafety, ref abi, vis) => {
215 let new_fn = self.rewrite_fn(indent,
223 self.changes.push_str_span(s, &new_fn);
225 visit::FkMethod(ident, ref sig, vis) => {
226 let new_fn = self.rewrite_fn(indent,
229 Some(&sig.explicit_self),
233 vis.unwrap_or(ast::Visibility::Inherited));
234 self.changes.push_str_span(s, &new_fn);
236 visit::FkFnBlock(..) => {}
239 self.last_pos = b.span.lo;
243 fn visit_item(&mut self, item: &'v ast::Item) {
245 ast::Item_::ItemUse(ref vp) => {
247 ast::ViewPath_::ViewPathList(ref path, ref path_list) => {
248 self.format_missing(item.span.lo);
249 let new_str = self.rewrite_use_list(path, path_list, vp.span);
250 self.changes.push_str_span(item.span, &new_str);
251 self.last_pos = item.span.hi;
253 ast::ViewPath_::ViewPathGlob(_) => {
254 // FIXME convert to list?
258 visit::walk_item(self, item);
260 ast::Item_::ItemImpl(..) => {
261 self.block_indent += TAB_SPACES;
262 visit::walk_item(self, item);
263 self.block_indent -= TAB_SPACES;
266 visit::walk_item(self, item);
271 fn visit_mac(&mut self, mac: &'v ast::Mac) {
272 visit::walk_mac(self, mac)
275 fn visit_mod(&mut self, m: &'v ast::Mod, s: Span, _: ast::NodeId) {
276 // Only visit inline mods here.
277 if self.codemap.lookup_char_pos(s.lo).file.name !=
278 self.codemap.lookup_char_pos(m.inner.lo).file.name {
281 visit::walk_mod(self, m);
285 impl<'a> FmtVisitor<'a> {
286 fn from_codemap<'b>(codemap: &'b CodeMap) -> FmtVisitor<'b> {
289 changes: ChangeSet::from_codemap(codemap),
290 last_pos: BytePos(0),
295 fn snippet(&self, span: Span) -> String {
296 match self.codemap.span_to_snippet(span) {
299 println!("Couldn't make snippet for span {:?}->{:?}",
300 self.codemap.lookup_char_pos(span.lo),
301 self.codemap.lookup_char_pos(span.hi));
308 fn rewrite_string_lit(&mut self, s: &str, span: Span, width: usize, offset: usize) -> String {
309 // FIXME I bet this stomps unicode escapes in the source string
311 // Check if there is anything to fix: we always try to fixup multi-line
312 // strings, or if the string is too long for the line.
313 let l_loc = self.codemap.lookup_char_pos(span.lo);
314 let r_loc = self.codemap.lookup_char_pos(span.hi);
315 if l_loc.line == r_loc.line && r_loc.col.to_usize() <= MAX_WIDTH {
316 return self.snippet(span);
319 // TODO if lo.col > IDEAL - 10, start a new line (need cur indent for that)
321 let s = s.escape_default();
323 let offset = offset + 1;
324 let indent = make_indent(offset);
325 let indent = &indent;
327 let max_chars = width - 1;
329 let mut cur_start = 0;
330 let mut result = String::new();
333 let mut cur_end = cur_start + max_chars;
335 if cur_end >= s.len() {
336 result.push_str(&s[cur_start..]);
340 // Make sure we're on a char boundary.
341 cur_end = next_char(&s, cur_end);
343 // Push cur_end left until we reach whitespace
344 while !s.char_at(cur_end-1).is_whitespace() {
345 cur_end = prev_char(&s, cur_end);
347 if cur_end - cur_start < MIN_STRING {
348 // We can't break at whitespace, fall back to splitting
349 // anywhere that doesn't break an escape sequence
350 cur_end = next_char(&s, cur_start + max_chars);
351 while s.char_at(cur_end) == '\\' {
352 cur_end = prev_char(&s, cur_end);
356 // Make sure there is no whitespace to the right of the break.
357 while cur_end < s.len() && s.char_at(cur_end).is_whitespace() {
358 cur_end = next_char(&s, cur_end+1);
360 result.push_str(&s[cur_start..cur_end]);
361 result.push_str("\\\n");
362 result.push_str(indent);
371 // Basically just pretty prints a multi-item import.
372 fn rewrite_use_list(&mut self,
374 path_list: &[ast::PathListItem],
375 vp_span: Span) -> String {
376 // FIXME remove unused imports
378 // FIXME check indentation
379 let l_loc = self.codemap.lookup_char_pos(vp_span.lo);
381 let path_str = pprust::path_to_string(&path);
384 let indent = l_loc.col.0 + path_str.len() + 3;
385 let fmt = ListFormatting {
386 tactic: ListTactic::Mixed,
388 trailing_separator: SeparatorTactic::Never,
391 h_width: IDEAL_WIDTH - (indent + path_str.len() + 2),
392 v_width: IDEAL_WIDTH - (indent + path_str.len() + 2),
395 // TODO handle any comments inbetween items.
396 // If `self` is in the list, put it first.
397 let head = if path_list.iter().any(|vpi|
398 if let ast::PathListItem_::PathListMod{ .. } = vpi.node {
404 Some(("self".to_string(), String::new()))
409 let items: Vec<_> = head.into_iter().chain(path_list.iter().filter_map(|vpi| {
411 ast::PathListItem_::PathListIdent{ name, .. } => {
412 Some((token::get_ident(name).to_string(), String::new()))
414 // Skip `self`, because we added it above.
415 ast::PathListItem_::PathListMod{ .. } => None,
419 format!("use {}::{{{}}};", path_str, write_list(&items, &fmt))
423 fn rewrite_pred(&self, predicate: &ast::WherePredicate) -> String
426 // TODO assumes we'll always fit on one line...
428 &ast::WherePredicate::BoundPredicate(ast::WhereBoundPredicate{ref bound_lifetimes,
432 if bound_lifetimes.len() > 0 {
433 format!("for<{}> {}: {}",
434 bound_lifetimes.iter().map(|l| self.rewrite_lifetime_def(l)).collect::<Vec<_>>().connect(", "),
435 pprust::ty_to_string(bounded_ty),
436 bounds.iter().map(|b| self.rewrite_ty_bound(b)).collect::<Vec<_>>().connect("+"))
440 pprust::ty_to_string(bounded_ty),
441 bounds.iter().map(|b| self.rewrite_ty_bound(b)).collect::<Vec<_>>().connect("+"))
444 &ast::WherePredicate::RegionPredicate(ast::WhereRegionPredicate{ref lifetime,
448 pprust::lifetime_to_string(lifetime),
449 bounds.iter().map(|l| pprust::lifetime_to_string(l)).collect::<Vec<_>>().connect("+"))
451 &ast::WherePredicate::EqPredicate(ast::WhereEqPredicate{ref path, ref ty, ..}) => {
452 format!("{} = {}", pprust::path_to_string(path), pprust::ty_to_string(ty))
457 fn rewrite_lifetime_def(&self, lifetime: &ast::LifetimeDef) -> String
459 if lifetime.bounds.len() == 0 {
460 return pprust::lifetime_to_string(&lifetime.lifetime);
464 pprust::lifetime_to_string(&lifetime.lifetime),
465 lifetime.bounds.iter().map(|l| pprust::lifetime_to_string(l)).collect::<Vec<_>>().connect("+"))
468 fn rewrite_ty_bound(&self, bound: &ast::TyParamBound) -> String
471 ast::TyParamBound::TraitTyParamBound(ref tref, ast::TraitBoundModifier::None) => {
472 self.rewrite_poly_trait_ref(tref)
474 ast::TyParamBound::TraitTyParamBound(ref tref, ast::TraitBoundModifier::Maybe) => {
475 format!("?{}", self.rewrite_poly_trait_ref(tref))
477 ast::TyParamBound::RegionTyParamBound(ref l) => {
478 pprust::lifetime_to_string(l)
483 fn rewrite_ty_param(&self, ty_param: &ast::TyParam) -> String
485 let mut result = String::with_capacity(128);
486 result.push_str(&token::get_ident(ty_param.ident));
487 if ty_param.bounds.len() > 0 {
488 result.push_str(": ");
489 result.push_str(&ty_param.bounds.iter().map(|b| self.rewrite_ty_bound(b)).collect::<Vec<_>>().connect(", "));
491 if let Some(ref def) = ty_param.default {
492 result.push_str(" = ");
493 result.push_str(&pprust::ty_to_string(&def));
499 fn rewrite_poly_trait_ref(&self, t: &ast::PolyTraitRef) -> String
501 if t.bound_lifetimes.len() > 0 {
502 format!("for<{}> {}",
503 t.bound_lifetimes.iter().map(|l| self.rewrite_lifetime_def(l)).collect::<Vec<_>>().connect(", "),
504 pprust::path_to_string(&t.trait_ref.path))
507 pprust::path_to_string(&t.trait_ref.path)
511 fn rewrite_call(&mut self,
513 args: &[ptr::P<ast::Expr>],
518 debug!("rewrite_call, width: {}, offset: {}", width, offset);
520 // TODO using byte lens instead of char lens (and probably all over the place too)
521 let callee_str = self.rewrite_expr(callee, width, offset);
522 debug!("rewrite_call, callee_str: `{}`", callee_str);
524 let remaining_width = width - callee_str.len() - 2;
525 let offset = callee_str.len() + 1 + offset;
526 let arg_count = args.len();
528 let args_str = if arg_count > 0 {
529 let args: Vec<_> = args.iter().map(|e| (self.rewrite_expr(e,
531 offset), String::new())).collect();
532 // TODO move this into write_list
533 let tactics = if args.iter().any(|&(ref s, _)| s.contains('\n')) {
536 ListTactic::HorizontalVertical
538 let fmt = ListFormatting {
541 trailing_separator: SeparatorTactic::Never,
543 h_width: remaining_width,
544 v_width: remaining_width,
546 write_list(&args, &fmt)
551 format!("{}({})", callee_str, args_str)
554 fn rewrite_expr(&mut self, expr: &ast::Expr, width: usize, offset: usize) -> String {
556 ast::Expr_::ExprLit(ref l) => {
558 ast::Lit_::LitStr(ref is, _) => {
559 return self.rewrite_string_lit(&is, l.span, width, offset);
564 ast::Expr_::ExprCall(ref callee, ref args) => {
565 return self.rewrite_call(callee, args, width, offset);
570 let result = self.snippet(expr.span);
571 debug!("snippet: {}", result);
577 fn prev_char(s: &str, mut i: usize) -> usize {
578 if i == 0 { return 0; }
581 while !s.is_char_boundary(i) {
588 fn next_char(s: &str, mut i: usize) -> usize {
589 if i >= s.len() { return s.len(); }
591 while !s.is_char_boundary(i) {
598 fn make_indent(width: usize) -> String {
599 let mut indent = String::with_capacity(width);
606 struct RustFmtCalls {
607 input_path: Option<PathBuf>,
610 impl<'a> CompilerCalls<'a> for RustFmtCalls {
611 fn early_callback(&mut self,
612 _: &getopts::Matches,
613 _: &diagnostics::registry::Registry)
615 Compilation::Continue
618 fn some_input(&mut self,
620 input_path: Option<PathBuf>)
621 -> (Input, Option<PathBuf>) {
623 Some(ref ip) => self.input_path = Some(ip.clone()),
625 // FIXME should handle string input and write to stdout or something
626 panic!("No input path");
632 fn no_input(&mut self,
633 _: &getopts::Matches,
637 _: &diagnostics::registry::Registry)
638 -> Option<(Input, Option<PathBuf>)> {
639 panic!("No input supplied to RustFmt");
642 fn late_callback(&mut self,
643 _: &getopts::Matches,
649 Compilation::Continue
652 fn build_controller(&mut self, _: &Session) -> driver::CompileController<'a> {
653 let mut control = driver::CompileController::basic();
654 control.after_parse.stop = Compilation::Stop;
655 control.after_parse.callback = box |state| {
656 let krate = state.krate.unwrap();
657 let codemap = state.session.codemap();
658 let mut changes = fmt_ast(krate, codemap);
659 fmt_lines(&mut changes);
661 // FIXME(#5) Should be user specified whether to show or replace.
662 let result = changes.write_all_files(WriteMode::Display);
664 if let Err(msg) = result {
665 println!("Error writing files: {}", msg);
674 let args: Vec<_> = std::env::args().collect();
675 let mut call_ctxt = RustFmtCalls { input_path: None };
676 rustc_driver::run_compiler(&args, &mut call_ctxt);
677 std::env::set_exit_status(0);
680 // let fmt = ListFormatting {
681 // tactic: ListTactic::Horizontal,
683 // trailing_separator: SeparatorTactic::Vertical,
688 // let inputs = vec![(format!("foo"), String::new()),
689 // (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 // let s = write_list(&inputs, &fmt);
697 // println!(" {}", s);
701 // comments aren't in the AST, which makes processing them difficult, but then
702 // comments are complicated anyway. I think I am happy putting off tackling them
703 // for now. Long term the soluton is for comments to be in the AST, but that means
704 // only the libsyntax AST, not the rustc one, which means waiting for the ASTs
705 // to diverge one day....
707 // Once we do have comments, we just have to implement a simple word wrapping
708 // algorithm to keep the width under IDEAL_WIDTH. We should also convert multiline
709 // /* ... */ comments to // and check doc comments are in the right place and of
712 // Should also make sure comments have the right indent