diff --git a/Cargo.lock b/Cargo.lock index a1534ecd913f..297e4f65fe18 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,6 +3,7 @@ name = "rustfmt" version = "0.0.1" dependencies = [ "diff 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "strings 0.0.1 (git+https://github.com/nrc/strings.rs.git)", "toml 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/Cargo.toml b/Cargo.toml index acf53b77e7c3..f76d45d39c75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,9 @@ build = "build.rs" strings = "0.0.1" git = "https://github.com/nrc/strings.rs.git" +[dependencies] +toml = "0.1.20" +rustc-serialize = "0.3.14" + [dev-dependencies] diff = "0.1.0" -toml = "0.1.20" diff --git a/build.rs b/build.rs index ac27db564939..3e6d051d9b1d 100644 --- a/build.rs +++ b/build.rs @@ -17,11 +17,8 @@ fn main() { let in_file = Path::new("src/default.toml"); let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); - let profile = env::var("PROFILE").unwrap(); let mut out_file = PathBuf::new(); out_file.push(manifest_dir); - out_file.push("target"); - out_file.push(profile); out_file.push("default.toml"); std::fs::copy(in_file, out_file).unwrap(); diff --git a/src/bin/rustfmt.rs b/src/bin/rustfmt.rs index 32c38ea15e4d..5a12dbd639b6 100644 --- a/src/bin/rustfmt.rs +++ b/src/bin/rustfmt.rs @@ -15,10 +15,18 @@ use rustfmt::{WriteMode, run}; +use std::fs::File; +use std::io::Read; + fn main() { let args: Vec<_> = std::env::args().collect(); - //run(args, WriteMode::Display); - run(args, WriteMode::Overwrite); + let mut def_config_file = File::open("default.toml").unwrap(); + let mut def_config = String::new(); + def_config_file.read_to_string(&mut def_config).unwrap(); + + //run(args, WriteMode::Display, &def_config); + run(args, WriteMode::Overwrite, &def_config); + std::env::set_exit_status(0); // TODO unit tests diff --git a/src/changes.rs b/src/changes.rs index f0aebe7c94f0..5ed1c82b22f9 100644 --- a/src/changes.rs +++ b/src/changes.rs @@ -20,7 +20,6 @@ use std::fs::File; use std::io::{Write, stdout}; use WriteMode; -use NEWLINE_STYLE; use NewlineStyle; // This is basically a wrapper around a bunch of Ropes which makes it convenient @@ -157,7 +156,7 @@ fn write_system_newlines( -> Result<(), ::std::io::Error> where T: Write, { - match NEWLINE_STYLE { + match config!(newline_style) { NewlineStyle::Unix => write!(writer, "{}", text), NewlineStyle::Windows => { for (c, _) in text.chars() { diff --git a/src/default.toml b/src/default.toml index e049dfa59ba4..218f2a04fb29 100644 --- a/src/default.toml +++ b/src/default.toml @@ -1,2 +1,7 @@ -max-width = 100 -ideal-width = 80 +max_width = 100 +ideal_width = 80 +leeway = 5 +tab_spaces = 4 +newline_style = "Unix" +fn_brace_style = "SameLineWhere" +fn_return_indent = "WithArgs" diff --git a/src/expr.rs b/src/expr.rs index 1da66c4e328a..c1cbf1756457 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -15,7 +15,7 @@ use syntax::{ast, ptr}; use syntax::codemap::{Span, Pos}; -use {MAX_WIDTH, MIN_STRING}; +use MIN_STRING; impl<'a> FmtVisitor<'a> { // TODO NEEDS TESTS @@ -26,7 +26,7 @@ fn rewrite_string_lit(&mut self, s: &str, span: Span, width: usize, offset: usiz // strings, or if the string is too long for the line. let l_loc = self.codemap.lookup_char_pos(span.lo); let r_loc = self.codemap.lookup_char_pos(span.hi); - if l_loc.line == r_loc.line && r_loc.col.to_usize() <= MAX_WIDTH { + if l_loc.line == r_loc.line && r_loc.col.to_usize() <= config!(max_width) { return self.snippet(span); } diff --git a/src/functions.rs b/src/functions.rs index 2133b7c44184..2ca5423f0c8a 100644 --- a/src/functions.rs +++ b/src/functions.rs @@ -8,8 +8,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use {ReturnIndent, MAX_WIDTH, BraceStyle, - IDEAL_WIDTH, LEEWAY, FN_BRACE_STYLE, FN_RETURN_INDENT}; +use {ReturnIndent, BraceStyle}; use utils::make_indent; use lists::{write_list, ListFormatting, SeparatorTactic, ListTactic}; use visitor::FmtVisitor; @@ -149,8 +148,8 @@ fn rewrite_fn_base(&mut self, // If we've already gone multi-line, or the return type would push // over the max width, then put the return type on a new line. if result.contains("\n") || - result.len() + indent + ret_str.len() > MAX_WIDTH { - let indent = match FN_RETURN_INDENT { + result.len() + indent + ret_str.len() > config!(max_width) { + let indent = match config!(fn_return_indent) { ReturnIndent::WithWhereClause => indent + 4, // TODO we might want to check that using the arg indent doesn't // blow our budget, and if it does, then fallback to the where @@ -344,15 +343,15 @@ fn compute_budgets_for_args(&self, if !newline_brace { used_space += 2; } - let one_line_budget = if used_space > MAX_WIDTH { + let one_line_budget = if used_space > config!(max_width) { 0 } else { - MAX_WIDTH - used_space + config!(max_width) - used_space }; // 2 = `()` let used_space = indent + result.len() + 2; - let max_space = IDEAL_WIDTH + LEEWAY; + let max_space = config!(ideal_width) + config!(leeway); debug!("compute_budgets_for_args: used_space: {}, max_space: {}", used_space, max_space); if used_space < max_space { @@ -368,7 +367,7 @@ fn compute_budgets_for_args(&self, result.push_str(&make_indent(indent + 4)); // 6 = new indent + `()` let used_space = indent + 6; - let max_space = IDEAL_WIDTH + LEEWAY; + let max_space = config!(ideal_width) + config!(leeway); if used_space > max_space { // Whoops! bankrupt. // TODO take evasive action, perhaps kill the indent or something. @@ -382,7 +381,7 @@ fn compute_budgets_for_args(&self, } fn newline_for_brace(&self, where_clause: &ast::WhereClause) -> bool { - match FN_BRACE_STYLE { + match config!(fn_brace_style) { BraceStyle::AlwaysNextLine => true, BraceStyle::SameLineWhere if where_clause.predicates.len() > 0 => true, _ => false, @@ -399,7 +398,7 @@ fn rewrite_generics(&self, generics: &ast::Generics, indent: usize, ret_span: Sp return result; } - let budget = MAX_WIDTH - indent - 2; + let budget = config!(max_width) - indent - 2; // TODO might need to insert a newline if the generics are really long result.push('<'); @@ -475,7 +474,7 @@ fn rewrite_where_clause(&self, .zip(comments.into_iter()) .collect(); - let budget = IDEAL_WIDTH + LEEWAY - indent - 10; + let budget = config!(ideal_width) + config!(leeway) - indent - 10; let fmt = ListFormatting { tactic: ListTactic::Vertical, separator: ",", diff --git a/src/lib.rs b/src/lib.rs index 08ee465dddf6..1c0fe0105ef4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,13 +24,17 @@ extern crate rustc; extern crate rustc_driver; extern crate syntax; +extern crate rustc_serialize; extern crate strings; use rustc::session::Session; -use rustc::session::config::{self, Input}; +use rustc::session::config as rustc_config; +use rustc::session::config::Input; use rustc_driver::{driver, CompilerCalls, Compilation}; +use rustc_serialize::{Decodable, Decoder}; + use syntax::ast; use syntax::codemap::CodeMap; use syntax::diagnostics; @@ -42,6 +46,8 @@ use changes::ChangeSet; use visitor::FmtVisitor; +#[macro_use] +mod config; mod changes; mod visitor; mod functions; @@ -52,17 +58,12 @@ mod expr; mod imports; -const IDEAL_WIDTH: usize = 80; -const LEEWAY: usize = 5; -const MAX_WIDTH: usize = 100; const MIN_STRING: usize = 10; -const TAB_SPACES: usize = 4; -const NEWLINE_STYLE: NewlineStyle = NewlineStyle::Unix; -const FN_BRACE_STYLE: BraceStyle = BraceStyle::SameLineWhere; -const FN_RETURN_INDENT: ReturnIndent = ReturnIndent::WithArgs; // When we get scoped annotations, we should have rustfmt::skip. const SKIP_ANNOTATION: &'static str = "rustfmt_skip"; +static mut CONFIG: Option = None; + #[derive(Copy, Clone)] pub enum WriteMode { Overwrite, @@ -75,13 +76,24 @@ pub enum WriteMode { } #[derive(Copy, Clone, Eq, PartialEq, Debug)] -enum NewlineStyle { +pub enum NewlineStyle { Windows, // \r\n Unix, // \n } +impl Decodable for NewlineStyle { + fn decode(d: &mut D) -> Result { + let s = try!(d.read_str()); + match &*s { + "Windows" => Ok(NewlineStyle::Windows), + "Unix" => Ok(NewlineStyle::Unix), + _ => Err(d.error("Bad variant")), + } + } +} + #[derive(Copy, Clone, Eq, PartialEq, Debug)] -enum BraceStyle { +pub enum BraceStyle { AlwaysNextLine, PreferSameLine, // Prefer same line except where there is a where clause, in which case force @@ -89,15 +101,39 @@ enum BraceStyle { SameLineWhere, } +impl Decodable for BraceStyle { + fn decode(d: &mut D) -> Result { + let s = try!(d.read_str()); + match &*s { + "AlwaysNextLine" => Ok(BraceStyle::AlwaysNextLine), + "PreferSameLine" => Ok(BraceStyle::PreferSameLine), + "SameLineWhere" => Ok(BraceStyle::SameLineWhere), + _ => Err(d.error("Bad variant")), + } + } +} + // How to indent a function's return type. #[derive(Copy, Clone, Eq, PartialEq, Debug)] -enum ReturnIndent { +pub enum ReturnIndent { // Aligned with the arguments WithArgs, // Aligned with the where clause WithWhereClause, } +// TODO could use a macro for all these Decodable impls. +impl Decodable for ReturnIndent { + fn decode(d: &mut D) -> Result { + let s = try!(d.read_str()); + match &*s { + "WithArgs" => Ok(ReturnIndent::WithArgs), + "WithWhereClause" => Ok(ReturnIndent::WithWhereClause), + _ => Err(d.error("Bad variant")), + } + } +} + // Formatting which depends on the AST. fn fmt_ast<'a>(krate: &ast::Crate, codemap: &'a CodeMap) -> ChangeSet<'a> { let mut visitor = FmtVisitor::from_codemap(codemap); @@ -133,10 +169,10 @@ fn fmt_lines(changes: &mut ChangeSet) { line_len -= b - lw; } // Check for any line width errors we couldn't correct. - if line_len > MAX_WIDTH { + if line_len > config!(max_width) { // TODO store the error rather than reporting immediately. println!("Rustfmt couldn't fix (sorry). {}:{}: line longer than {} characters", - f, cur_line, MAX_WIDTH); + f, cur_line, config!(max_width)); } line_len = 0; cur_line += 1; @@ -200,7 +236,7 @@ fn some_input(&mut self, fn no_input(&mut self, _: &getopts::Matches, - _: &config::Options, + _: &rustc_config::Options, _: &Option, _: &Option, _: &diagnostics::registry::Registry) @@ -248,7 +284,14 @@ fn build_controller(&mut self, _: &Session) -> driver::CompileController<'a> { } } -pub fn run(args: Vec, write_mode: WriteMode) { +// args are the arguments passed on the command line, generally passed through +// to the compiler. +// write_mode determines what happens to the result of running rustfmt, see +// WriteMode. +// default_config is a string of toml data to be used to configure rustfmt. +pub fn run(args: Vec, write_mode: WriteMode, default_config: &str) { + config::set_config(default_config); + let mut call_ctxt = RustFmtCalls { input_path: None, write_mode: write_mode }; rustc_driver::run_compiler(&args, &mut call_ctxt); } diff --git a/src/visitor.rs b/src/visitor.rs index 3cc9b36222bb..2aac931c4e6c 100644 --- a/src/visitor.rs +++ b/src/visitor.rs @@ -14,7 +14,7 @@ use utils; -use {IDEAL_WIDTH, MAX_WIDTH, TAB_SPACES, SKIP_ANNOTATION}; +use SKIP_ANNOTATION; use changes::ChangeSet; pub struct FmtVisitor<'a> { @@ -32,7 +32,7 @@ fn visit_expr(&mut self, ex: &'v ast::Expr) { self.codemap.lookup_char_pos(ex.span.hi)); self.format_missing(ex.span.lo); let offset = self.changes.cur_offset_span(ex.span); - let new_str = self.rewrite_expr(ex, MAX_WIDTH - offset, offset); + let new_str = self.rewrite_expr(ex, config!(max_width) - offset, offset); self.changes.push_str_span(ex.span, &new_str); self.last_pos = ex.span.hi; } @@ -65,7 +65,7 @@ fn visit_block(&mut self, b: &'v ast::Block) { self.changes.push_str_span(b.span, "{"); self.last_pos = self.last_pos + BytePos(1); - self.block_indent += TAB_SPACES; + self.block_indent += config!(tab_spaces); for stmt in &b.stmts { self.visit_stmt(&stmt) @@ -78,7 +78,7 @@ fn visit_block(&mut self, b: &'v ast::Block) { None => {} } - self.block_indent -= TAB_SPACES; + self.block_indent -= config!(tab_spaces); // TODO we should compress any newlines here to just one self.format_missing_with_indent(b.span.hi - BytePos(1)); self.changes.push_str_span(b.span, "}"); @@ -149,8 +149,8 @@ fn visit_item(&mut self, item: &'v ast::Item) { match vp.node { ast::ViewPath_::ViewPathList(ref path, ref path_list) => { let block_indent = self.block_indent; - let one_line_budget = MAX_WIDTH - block_indent; - let multi_line_budget = IDEAL_WIDTH - block_indent; + let one_line_budget = config!(max_width) - block_indent; + let multi_line_budget = config!(ideal_width) - block_indent; let new_str = self.rewrite_use_list(block_indent, one_line_budget, multi_line_budget, @@ -170,9 +170,9 @@ fn visit_item(&mut self, item: &'v ast::Item) { ast::Item_::ItemImpl(..) | ast::Item_::ItemMod(_) | ast::Item_::ItemTrait(..) => { - self.block_indent += TAB_SPACES; + self.block_indent += config!(tab_spaces); visit::walk_item(self, item); - self.block_indent -= TAB_SPACES; + self.block_indent -= config!(tab_spaces); } ast::Item_::ItemExternCrate(_) => { self.format_missing_with_indent(item.span.lo); diff --git a/tests/idem.rs b/tests/idem.rs index 1b74b1aa83ed..40474b8dfee0 100644 --- a/tests/idem.rs +++ b/tests/idem.rs @@ -58,7 +58,6 @@ fn idempotent_tests() { // Compare output to input. fn print_mismatches(result: HashMap) { for (file_name, fmt_text) in result { - println!("Mismatch in {}.", file_name); println!("{}", fmt_text); } } @@ -68,14 +67,16 @@ fn print_mismatches(result: HashMap) { pub fn idempotent_check(filename: String) -> Result<(), HashMap> { let args = vec!["rustfmt".to_owned(), filename]; + let mut def_config_file = fs::File::open("default.toml").unwrap(); + let mut def_config = String::new(); + def_config_file.read_to_string(&mut def_config).unwrap(); // this thread is not used for concurrency, but rather to workaround the issue that the passed // function handle needs to have static lifetime. Instead of using a global RefCell, we use // panic to return a result in case of failure. This has the advantage of smoothing the road to // multithreaded rustfmt thread::catch_panic(move || { - run(args, WriteMode::Return(HANDLE_RESULT)); + run(args, WriteMode::Return(HANDLE_RESULT), &def_config); }).map_err(|any| - // i know it is a hashmap *any.downcast().unwrap() ) } @@ -90,8 +91,8 @@ fn handle_result(result: HashMap) { // TODO: speedup by running through bytes iterator f.read_to_string(&mut text).unwrap(); if fmt_text != text { - show_diff(&file_name, &fmt_text, &text); - failures.insert(file_name, fmt_text); + let diff_str = make_diff(&file_name, &fmt_text, &text); + failures.insert(file_name, diff_str); } } if !failures.is_empty() { @@ -100,24 +101,25 @@ fn handle_result(result: HashMap) { } -fn show_diff(file_name: &str, expected: &str, actual: &str) { +fn make_diff(file_name: &str, expected: &str, actual: &str) -> String { let mut line_number = 1; let mut prev_both = true; + let mut text = String::new(); for result in diff::lines(expected, actual) { match result { diff::Result::Left(str) => { if prev_both { - println!("Mismatch @ {}:{}", file_name, line_number); + text.push_str(&format!("Mismatch @ {}:{}\n", file_name, line_number)); } - println!("-{}⏎", str); + text.push_str(&format!("-{}⏎\n", str)); prev_both = false; } diff::Result::Right(str) => { if prev_both { - println!("Mismatch @ {}:{}", file_name, line_number); + text.push_str(&format!("Mismatch @ {}:{}\n", file_name, line_number)); } - println!("+{}⏎", str); + text.push_str(&format!("+{}⏎\n", str)); prev_both = false; line_number += 1; } @@ -127,4 +129,6 @@ fn show_diff(file_name: &str, expected: &str, actual: &str) { } } } + + text }