diff --git a/Cargo.lock b/Cargo.lock index 71d4280ed93..162e9160f85 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ [root] name = "rustfmt" -version = "0.4.0" +version = "0.5.0" dependencies = [ "diff 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/src/filemap.rs b/src/filemap.rs index 64c892a0929..17abc6f76e7 100644 --- a/src/filemap.rs +++ b/src/filemap.rs @@ -13,7 +13,6 @@ use strings::string_buffer::StringBuffer; -use std::collections::HashMap; use std::fs::{self, File}; use std::io::{self, Write, Read, stdout, BufWriter}; @@ -22,28 +21,27 @@ use rustfmt_diff::{make_diff, print_diff, Mismatch}; use checkstyle::{output_header, output_footer, output_checkstyle_file}; // A map of the files of a crate, with their new content -pub type FileMap = HashMap; +pub type FileMap = Vec; + +pub type FileRecord = (String, StringBuffer); // Append a newline to the end of each file. -pub fn append_newlines(file_map: &mut FileMap) { - for (_, s) in file_map.iter_mut() { - s.push_str("\n"); - } +pub fn append_newline(s: &mut StringBuffer) { + s.push_str("\n"); } pub fn write_all_files(file_map: &FileMap, out: &mut T, config: &Config) -> Result<(), io::Error> where T: Write { output_header(out, config.write_mode).ok(); - for filename in file_map.keys() { - try!(write_file(&file_map[filename], filename, out, config)); + for &(ref filename, ref text) in file_map { + try!(write_file(text, filename, out, config)); } output_footer(out, config.write_mode).ok(); Ok(()) } - // Prints all newlines either as `\n` or as `\r\n`. pub fn write_system_newlines(writer: T, text: &StringBuffer, diff --git a/src/lib.rs b/src/lib.rs index 61ac2626e02..12258ada15c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,7 +31,9 @@ use syntax::errors::{Handler, DiagnosticBuilder}; use syntax::errors::emitter::{ColorConfig, EmitterWriter}; use syntax::parse::{self, ParseSess}; -use std::io::{stdout, Write}; +use strings::string_buffer::StringBuffer; + +use std::io::{self, stdout, Write}; use std::ops::{Add, Sub}; use std::path::{Path, PathBuf}; use std::rc::Rc; @@ -42,6 +44,7 @@ use issues::{BadIssueSeeker, Issue}; use filemap::FileMap; use visitor::FmtVisitor; use config::Config; +use checkstyle::{output_header, output_footer}; pub use self::summary::Summary; @@ -274,12 +277,16 @@ impl fmt::Display for FormatReport { } // Formatting which depends on the AST. -fn format_ast(krate: &ast::Crate, - parse_session: &ParseSess, - main_file: &Path, - config: &Config) - -> FileMap { - let mut file_map = FileMap::new(); +fn format_ast(krate: &ast::Crate, + parse_session: &ParseSess, + main_file: &Path, + config: &Config, + mut after_file: F) + -> Result + where F: FnMut(&str, &mut StringBuffer) -> Result<(), io::Error> +{ + let mut result = FileMap::new(); + // We always skip children for the "Plain" write mode, since there is // nothing to distinguish the nested module contents. let skip_children = config.skip_children || config.write_mode == config::WriteMode::Plain; @@ -293,92 +300,86 @@ fn format_ast(krate: &ast::Crate, } let mut visitor = FmtVisitor::from_codemap(parse_session, config); visitor.format_separate_mod(module); - file_map.insert(path.to_owned(), visitor.buffer); + + try!(after_file(path, &mut visitor.buffer)); + + result.push((path.to_owned(), visitor.buffer)); } - file_map + + Ok(result) } // Formatting done on a char by char or line by line basis. -// TODO(#209) warn on bad license -// TODO(#20) other stuff for parity with make tidy -fn format_lines(file_map: &mut FileMap, config: &Config) -> FormatReport { - let mut truncate_todo = Vec::new(); - let mut report = FormatReport::new(); - +// FIXME(#209) warn on bad license +// FIXME(#20) other stuff for parity with make tidy +fn format_lines(text: &mut StringBuffer, name: &str, config: &Config, report: &mut FormatReport) { // Iterate over the chars in the file map. - for (f, text) in file_map.iter() { - let mut trims = vec![]; - let mut last_wspace: Option = None; - let mut line_len = 0; - let mut cur_line = 1; - let mut newline_count = 0; - let mut errors = vec![]; - let mut issue_seeker = BadIssueSeeker::new(config.report_todo, config.report_fixme); + let mut trims = vec![]; + let mut last_wspace: Option = None; + let mut line_len = 0; + let mut cur_line = 1; + let mut newline_count = 0; + let mut errors = vec![]; + let mut issue_seeker = BadIssueSeeker::new(config.report_todo, config.report_fixme); - for (c, b) in text.chars() { - if c == '\r' { - line_len += c.len_utf8(); - continue; - } - - // Add warnings for bad todos/ fixmes - if let Some(issue) = issue_seeker.inspect(c) { - errors.push(FormattingError { - line: cur_line, - kind: ErrorKind::BadIssue(issue), - }); - } - - if c == '\n' { - // Check for (and record) trailing whitespace. - if let Some(lw) = last_wspace { - trims.push((cur_line, lw, b)); - line_len -= b - lw; - } - // Check for any line width errors we couldn't correct. - if line_len > config.max_width { - errors.push(FormattingError { - line: cur_line, - kind: ErrorKind::LineOverflow, - }); - } - line_len = 0; - cur_line += 1; - newline_count += 1; - last_wspace = None; - } else { - newline_count = 0; - line_len += c.len_utf8(); - if c.is_whitespace() { - if last_wspace.is_none() { - last_wspace = Some(b); - } - } else { - last_wspace = None; - } - } + for (c, b) in text.chars() { + if c == '\r' { + line_len += c.len_utf8(); + continue; } - if newline_count > 1 { - debug!("track truncate: {} {} {}", f, text.len, newline_count); - truncate_todo.push((f.to_owned(), text.len - newline_count + 1)) - } - - for &(l, _, _) in &trims { + // Add warnings for bad todos/ fixmes + if let Some(issue) = issue_seeker.inspect(c) { errors.push(FormattingError { - line: l, - kind: ErrorKind::TrailingWhitespace, + line: cur_line, + kind: ErrorKind::BadIssue(issue), }); } - report.file_error_map.insert(f.to_owned(), errors); + if c == '\n' { + // Check for (and record) trailing whitespace. + if let Some(lw) = last_wspace { + trims.push((cur_line, lw, b)); + line_len -= b - lw; + } + // Check for any line width errors we couldn't correct. + if line_len > config.max_width { + errors.push(FormattingError { + line: cur_line, + kind: ErrorKind::LineOverflow, + }); + } + line_len = 0; + cur_line += 1; + newline_count += 1; + last_wspace = None; + } else { + newline_count = 0; + line_len += c.len_utf8(); + if c.is_whitespace() { + if last_wspace.is_none() { + last_wspace = Some(b); + } + } else { + last_wspace = None; + } + } } - for (f, l) in truncate_todo { - file_map.get_mut(&f).unwrap().truncate(l); + if newline_count > 1 { + debug!("track truncate: {} {}", text.len, newline_count); + let line = text.len - newline_count + 1; + text.truncate(line); } - report + for &(l, _, _) in &trims { + errors.push(FormattingError { + line: l, + kind: ErrorKind::TrailingWhitespace, + }); + } + + report.file_error_map.insert(name.to_owned(), errors); } fn parse_input(input: Input, @@ -399,7 +400,10 @@ fn parse_input(input: Input, result.map_err(|e| Some(e)) } -pub fn format_input(input: Input, config: &Config) -> (Summary, FileMap, FormatReport) { +pub fn format_input(input: Input, + config: &Config, + mut out: Option<&mut T>) + -> Result<(Summary, FileMap, FormatReport), (io::Error, Summary)> { let mut summary = Summary::new(); let codemap = Rc::new(CodeMap::new()); @@ -419,7 +423,7 @@ pub fn format_input(input: Input, config: &Config) -> (Summary, FileMap, FormatR diagnostic.emit(); } summary.add_parsing_error(); - return (summary, FileMap::new(), FormatReport::new()); + return Ok((summary, FileMap::new(), FormatReport::new())); } }; @@ -431,17 +435,33 @@ pub fn format_input(input: Input, config: &Config) -> (Summary, FileMap, FormatR let silent_emitter = Box::new(EmitterWriter::new(Box::new(Vec::new()), None, codemap.clone())); parse_session.span_diagnostic = Handler::with_emitter(true, false, silent_emitter); - let mut file_map = format_ast(&krate, &parse_session, &main_file, config); + let mut report = FormatReport::new(); - // For some reason, the codemap does not include terminating - // newlines so we must add one on for each file. This is sad. - filemap::append_newlines(&mut file_map); + match format_ast(&krate, + &parse_session, + &main_file, + config, + |file_name, file| { + // For some reason, the codemap does not include terminating + // newlines so we must add one on for each file. This is sad. + filemap::append_newline(file); - let report = format_lines(&mut file_map, config); - if report.has_warnings() { - summary.add_formatting_error(); + format_lines(file, file_name, config, &mut report); + + if let Some(ref mut out) = out { + try!(filemap::write_file(file, file_name, out, config)); + } + Ok(()) + }) { + Ok(file_map) => { + if report.has_warnings() { + summary.add_formatting_error(); + } + + Ok((summary, file_map, report)) + } + Err(e) => Err((e, summary)), } - (summary, file_map, report) } pub enum Input { @@ -450,18 +470,22 @@ pub enum Input { } pub fn run(input: Input, config: &Config) -> Summary { - let (mut summary, file_map, report) = format_input(input, config); - if report.has_warnings() { - msg!("{}", report); + let mut out = &mut stdout(); + output_header(out, config.write_mode).ok(); + match format_input(input, config, Some(out)) { + Ok((summary, _, report)) => { + output_footer(out, config.write_mode).ok(); + + if report.has_warnings() { + msg!("{}", report); + } + + summary + } + Err((msg, mut summary)) => { + msg!("Error writing files: {}", msg); + summary.add_operational_error(); + summary + } } - - let mut out = stdout(); - let write_result = filemap::write_all_files(&file_map, &mut out, config); - - if let Err(msg) = write_result { - msg!("Error writing files: {}", msg); - summary.add_operational_error(); - } - - summary } diff --git a/src/summary.rs b/src/summary.rs index 5e5c0a579af..87d20899a58 100644 --- a/src/summary.rs +++ b/src/summary.rs @@ -1,4 +1,5 @@ #[must_use] +#[derive(Debug, Clone)] pub struct Summary { // Encountered e.g. an IO error. has_operational_errors: bool, diff --git a/tests/system.rs b/tests/system.rs index 1af9c0f3462..38d1e8584a5 100644 --- a/tests/system.rs +++ b/tests/system.rs @@ -143,9 +143,16 @@ fn self_tests() { fn stdin_formatting_smoke_test() { let input = Input::Text("fn main () {}".to_owned()); let config = Config::default(); - let (error_summary, file_map, _report) = format_input(input, &config); + let (error_summary, file_map, _report) = format_input::(input, &config, None) + .unwrap(); assert!(error_summary.has_no_errors()); - assert_eq!(file_map["stdin"].to_string(), "fn main() {}\n") + for &(ref file_name, ref text) in &file_map { + if file_name == "stdin" { + assert!(text.to_string() == "fn main() {}\n"); + return; + } + } + panic!("no stdin"); } #[test] @@ -153,7 +160,8 @@ fn format_lines_errors_are_reported() { let long_identifier = String::from_utf8(vec![b'a'; 239]).unwrap(); let input = Input::Text(format!("fn {}() {{}}", long_identifier)); let config = Config::default(); - let (error_summary, _file_map, _report) = format_input(input, &config); + let (error_summary, _file_map, _report) = format_input::(input, &config, None) + .unwrap(); assert!(error_summary.has_formatting_errors()); } @@ -212,7 +220,8 @@ fn read_config(filename: &str) -> Config { fn format_file>(filename: P, config: &Config) -> (FileMap, FormatReport) { let input = Input::File(filename.into()); - let (_error_summary, file_map, report) = format_input(input, &config); + let (_error_summary, file_map, report) = format_input::(input, &config, None) + .unwrap(); return (file_map, report); } @@ -222,7 +231,7 @@ pub fn idempotent_check(filename: String) -> Result