From 71d3d04270474ae0afdeeb410fdcc168a54714b7 Mon Sep 17 00:00:00 2001 From: Nick Cameron Date: Mon, 23 Jul 2018 14:52:02 +1200 Subject: [PATCH] factor out a `Session` object --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/bin/main.rs | 163 ++++++++++------------- src/formatting.rs | 286 +++++++++++++++++----------------------- src/git-rustfmt/main.rs | 12 +- src/imports.rs | 6 +- src/items.rs | 2 +- src/lib.rs | 83 ++++++++---- src/test/mod.rs | 111 +++++++++++----- 9 files changed, 340 insertions(+), 327 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f6e11cdb0a9..70dec3dcd2f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -533,7 +533,7 @@ dependencies = [ [[package]] name = "rustfmt-nightly" -version = "0.8.3" +version = "0.9.0" dependencies = [ "assert_cli 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", "cargo_metadata 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 99c83f993d4..5023e8bec0d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "rustfmt-nightly" -version = "0.8.3" +version = "0.9.0" authors = ["Nicholas Cameron ", "The Rustfmt developers"] description = "Tool to find and fix Rust formatting issues" repository = "https://github.com/rust-lang-nursery/rustfmt" diff --git a/src/bin/main.rs b/src/bin/main.rs index cd5e58437e6..5b6398f028e 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -28,8 +28,8 @@ use getopts::{Matches, Options}; use rustfmt::{ - checkstyle_footer, checkstyle_header, format_input, load_config, CliOptions, Color, Config, - EmitMode, ErrorKind, FileLines, FileName, Input, Summary, Verbosity, + load_config, CliOptions, Color, Config, EmitMode, ErrorKind, FileLines, FileName, Input, + Session, Verbosity, }; fn main() { @@ -37,17 +37,7 @@ fn main() { let opts = make_opts(); let exit_code = match execute(&opts) { - Ok((exit_mode, summary)) => { - if summary.has_operational_errors() - || summary.has_parsing_errors() - || ((summary.has_diff || summary.has_check_errors()) - && exit_mode == ExitCodeMode::Check) - { - 1 - } else { - 0 - } - } + Ok(code) => code, Err(e) => { eprintln!("{}", e.to_string()); 1 @@ -174,26 +164,27 @@ fn is_nightly() -> bool { .unwrap_or(false) } -fn execute(opts: &Options) -> Result<(ExitCodeMode, Summary), failure::Error> { +// Returned i32 is an exit code +fn execute(opts: &Options) -> Result { let matches = opts.parse(env::args().skip(1))?; let options = GetOptsOptions::from_matches(&matches)?; match determine_operation(&matches)? { Operation::Help(HelpOp::None) => { print_usage_to_stdout(opts, ""); - Ok((ExitCodeMode::Normal, Summary::default())) + return Ok(1); } Operation::Help(HelpOp::Config) => { Config::print_docs(&mut stdout(), options.unstable_features); - Ok((ExitCodeMode::Normal, Summary::default())) + return Ok(1); } Operation::Help(HelpOp::FileLines) => { print_help_file_lines(); - Ok((ExitCodeMode::Normal, Summary::default())) + return Ok(1); } Operation::Version => { print_version(); - Ok((ExitCodeMode::Normal, Summary::default())) + return Ok(1); } Operation::ConfigOutputDefault { path } => { let toml = Config::default().all_options().to_toml().map_err(err_msg)?; @@ -203,35 +194,9 @@ fn execute(opts: &Options) -> Result<(ExitCodeMode, Summary), failure::Error> { } else { io::stdout().write_all(toml.as_bytes())?; } - Ok((ExitCodeMode::Normal, Summary::default())) - } - Operation::Stdin { input } => { - // try to read config from local directory - let (mut config, _) = load_config(Some(Path::new(".")), Some(options.clone()))?; - - // emit mode is always Stdout for Stdin. - config.set().emit_mode(EmitMode::Stdout); - config.set().verbose(Verbosity::Quiet); - - // parse file_lines - config.set().file_lines(options.file_lines); - for f in config.file_lines().files() { - match *f { - FileName::Stdin => {} - _ => eprintln!("Warning: Extra file listed in file_lines option '{}'", f), - } - } - - let mut error_summary = Summary::default(); - emit_pre_matter(&config); - match format_and_emit_report(Input::Text(input), &config) { - Ok(summary) => error_summary.add(summary), - Err(_) => error_summary.add_operational_error(), - } - emit_post_matter(&config); - - Ok((ExitCodeMode::Normal, error_summary)) + return Ok(1); } + Operation::Stdin { input } => format_string(input, options), Operation::Format { files, minimal_config_path, @@ -239,11 +204,41 @@ fn execute(opts: &Options) -> Result<(ExitCodeMode, Summary), failure::Error> { } } +fn format_string(input: String, options: GetOptsOptions) -> Result { + // try to read config from local directory + let (mut config, _) = load_config(Some(Path::new(".")), Some(options.clone()))?; + + // emit mode is always Stdout for Stdin. + config.set().emit_mode(EmitMode::Stdout); + config.set().verbose(Verbosity::Quiet); + + // parse file_lines + config.set().file_lines(options.file_lines); + for f in config.file_lines().files() { + match *f { + FileName::Stdin => {} + _ => eprintln!("Warning: Extra file listed in file_lines option '{}'", f), + } + } + + let out = &mut stdout(); + let mut session = Session::new(config, Some(out)); + format_and_emit_report(&mut session, Input::Text(input)); + + let exit_code = + if session.summary.has_operational_errors() || session.summary.has_parsing_errors() { + 1 + } else { + 0 + }; + Ok(exit_code) +} + fn format( files: Vec, minimal_config_path: Option, options: GetOptsOptions, -) -> Result<(ExitCodeMode, Summary), failure::Error> { +) -> Result { options.verify_file_lines(&files); let (config, config_path) = load_config(None, Some(options.clone()))?; @@ -253,19 +248,19 @@ fn format( } } - emit_pre_matter(&config); - let mut error_summary = Summary::default(); + let out = &mut stdout(); + let mut session = Session::new(config, Some(out)); for file in files { if !file.exists() { eprintln!("Error: file `{}` does not exist", file.to_str().unwrap()); - error_summary.add_operational_error(); + session.summary.add_operational_error(); } else if file.is_dir() { eprintln!("Error: `{}` is a directory", file.to_str().unwrap()); - error_summary.add_operational_error(); + session.summary.add_operational_error(); } else { // Check the file directory if the config-path could not be read or not provided - let local_config = if config_path.is_none() { + if config_path.is_none() { let (local_config, config_path) = load_config(Some(file.parent().unwrap()), Some(options.clone()))?; if local_config.verbose() == Verbosity::Verbose { @@ -277,47 +272,42 @@ fn format( ); } } - local_config - } else { - config.clone() - }; - match format_and_emit_report(Input::File(file), &local_config) { - Ok(summary) => error_summary.add(summary), - Err(_) => { - error_summary.add_operational_error(); - break; - } + session.override_config(local_config, |sess| { + format_and_emit_report(sess, Input::File(file)) + }); + } else { + format_and_emit_report(&mut session, Input::File(file)); } } } - emit_post_matter(&config); // If we were given a path via dump-minimal-config, output any options // that were used during formatting as TOML. if let Some(path) = minimal_config_path { let mut file = File::create(path)?; - let toml = config.used_options().to_toml().map_err(err_msg)?; + let toml = session.config.used_options().to_toml().map_err(err_msg)?; file.write_all(toml.as_bytes())?; } - let exit_mode = if options.check { - ExitCodeMode::Check + let exit_code = if session.summary.has_operational_errors() + || session.summary.has_parsing_errors() + || ((session.summary.has_diff || session.summary.has_check_errors()) && options.check) + { + 1 } else { - ExitCodeMode::Normal + 0 }; - Ok((exit_mode, error_summary)) + Ok(exit_code) } -fn format_and_emit_report(input: Input, config: &Config) -> Result { - let out = &mut stdout(); - - match format_input(input, config, Some(out)) { - Ok((summary, report)) => { +fn format_and_emit_report(session: &mut Session, input: Input) { + match session.format(input) { + Ok(report) => { if report.has_warnings() { match term::stderr() { Some(ref t) - if config.color().use_colored_tty() + if session.config.color().use_colored_tty() && t.supports_color() && t.supports_attr(term::Attr::Bold) => { @@ -329,29 +319,14 @@ fn format_and_emit_report(input: Input, config: &Config) -> Result eprintln!("{}", report), } } - - Ok(summary) } - Err((msg, mut summary)) => { + Err(msg) => { eprintln!("Error writing files: {}", msg); - summary.add_operational_error(); - Ok(summary) + session.summary.add_operational_error(); } } } -fn emit_pre_matter(config: &Config) { - if config.emit_mode() == EmitMode::Checkstyle { - println!("{}", checkstyle_header()); - } -} - -fn emit_post_matter(config: &Config) { - if config.emit_mode() == EmitMode::Checkstyle { - println!("{}", checkstyle_footer()); - } -} - fn print_usage_to_stdout(opts: &Options, reason: &str) { let sep = if reason.is_empty() { String::new() @@ -456,12 +431,6 @@ fn determine_operation(matches: &Matches) -> Result { }) } -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -enum ExitCodeMode { - Normal, - Check, -} - const STABLE_EMIT_MODES: [EmitMode; 3] = [EmitMode::Files, EmitMode::Stdout, EmitMode::Diff]; /// Parsed command line options. diff --git a/src/formatting.rs b/src/formatting.rs index 95e2b08e3fe..53e673c5e7a 100644 --- a/src/formatting.rs +++ b/src/formatting.rs @@ -15,9 +15,8 @@ use comment::{CharClasses, FullCodeCharKind}; use issues::BadIssueSeeker; use visitor::{FmtVisitor, SnippetProvider}; -use {filemap, modules, ErrorKind, FormatReport, Input}; +use {filemap, modules, ErrorKind, FormatReport, Input, Session}; -use config::summary::Summary; use config::{Config, FileName, NewlineStyle, Verbosity}; // A map of the files of a crate, with their new content @@ -365,143 +364,146 @@ enum ParseError<'sess> { Panic, } -pub(crate) fn format_input_inner( - input: Input, - config: &Config, - mut out: Option<&mut T>, -) -> Result<(Summary, FileMap, FormatReport), (ErrorKind, Summary)> { - syntax_pos::hygiene::set_default_edition(config.edition().to_libsyntax_pos_edition()); - let mut summary = Summary::default(); - if config.disable_all_formatting() { - // When the input is from stdin, echo back the input. - if let Input::Text(ref buf) = input { - if let Err(e) = io::stdout().write_all(buf.as_bytes()) { - return Err((From::from(e), summary)); - } - } - return Ok((summary, FileMap::new(), FormatReport::new())); - } - let codemap = Rc::new(CodeMap::new(FilePathMapping::empty())); +impl<'b, T: Write + 'b> Session<'b, T> { + pub(crate) fn format_input_inner( + &mut self, + input: Input, + ) -> Result<(FileMap, FormatReport), ErrorKind> { + syntax_pos::hygiene::set_default_edition(self.config.edition().to_libsyntax_pos_edition()); - let tty_handler = if config.hide_parse_errors() { + if self.config.disable_all_formatting() { + // When the input is from stdin, echo back the input. + if let Input::Text(ref buf) = input { + if let Err(e) = io::stdout().write_all(buf.as_bytes()) { + return Err(From::from(e)); + } + } + return Ok((FileMap::new(), FormatReport::new())); + } + let codemap = Rc::new(CodeMap::new(FilePathMapping::empty())); + + let tty_handler = if self.config.hide_parse_errors() { + let silent_emitter = Box::new(EmitterWriter::new( + Box::new(Vec::new()), + Some(codemap.clone()), + false, + false, + )); + Handler::with_emitter(true, false, silent_emitter) + } else { + let supports_color = term::stderr().map_or(false, |term| term.supports_color()); + let color_cfg = if supports_color { + ColorConfig::Auto + } else { + ColorConfig::Never + }; + Handler::with_tty_emitter(color_cfg, true, false, Some(codemap.clone())) + }; + let mut parse_session = ParseSess::with_span_handler(tty_handler, codemap.clone()); + + let main_file = match input { + Input::File(ref file) => FileName::Real(file.clone()), + Input::Text(..) => FileName::Stdin, + }; + + let krate = match parse_input(input, &parse_session, &self.config) { + Ok(krate) => krate, + Err(err) => { + match err { + ParseError::Error(mut diagnostic) => diagnostic.emit(), + ParseError::Panic => { + // Note that if you see this message and want more information, + // then go to `parse_input` and run the parse function without + // `catch_unwind` so rustfmt panics and you can get a backtrace. + should_emit_verbose(&main_file, &self.config, || { + println!("The Rust parser panicked") + }); + } + ParseError::Recovered => {} + } + self.summary.add_parsing_error(); + return Err(ErrorKind::ParseError); + } + }; + + self.summary.mark_parse_time(); + + // Suppress error output after parsing. let silent_emitter = Box::new(EmitterWriter::new( Box::new(Vec::new()), Some(codemap.clone()), false, false, )); - Handler::with_emitter(true, false, silent_emitter) - } else { - let supports_color = term::stderr().map_or(false, |term| term.supports_color()); - let color_cfg = if supports_color { - ColorConfig::Auto - } else { - ColorConfig::Never - }; - Handler::with_tty_emitter(color_cfg, true, false, Some(codemap.clone())) - }; - let mut parse_session = ParseSess::with_span_handler(tty_handler, codemap.clone()); + parse_session.span_diagnostic = Handler::with_emitter(true, false, silent_emitter); - let main_file = match input { - Input::File(ref file) => FileName::Real(file.clone()), - Input::Text(..) => FileName::Stdin, - }; + let report = FormatReport::new(); - let krate = match parse_input(input, &parse_session, config) { - Ok(krate) => krate, - Err(err) => { - match err { - ParseError::Error(mut diagnostic) => diagnostic.emit(), - ParseError::Panic => { - // Note that if you see this message and want more information, - // then go to `parse_input` and run the parse function without - // `catch_unwind` so rustfmt panics and you can get a backtrace. - should_emit_verbose(&main_file, config, || { - println!("The Rust parser panicked") - }); + let config = &self.config; + let out = &mut self.out; + let format_result = format_ast( + &krate, + &mut parse_session, + &main_file, + config, + report.clone(), + |file_name, file, skipped_range, report| { + // 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); + + format_lines(file, file_name, skipped_range, config, report); + replace_with_system_newlines(file, config); + + if let Some(ref mut out) = out { + return filemap::write_file(file, file_name, out, config); } - ParseError::Recovered => {} - } - summary.add_parsing_error(); - return Err((ErrorKind::ParseError, summary)); - } - }; + Ok(false) + }, + ); - summary.mark_parse_time(); + self.summary.mark_format_time(); - // Suppress error output after parsing. - let silent_emitter = Box::new(EmitterWriter::new( - Box::new(Vec::new()), - Some(codemap.clone()), - false, - false, - )); - parse_session.span_diagnostic = Handler::with_emitter(true, false, silent_emitter); - - let report = FormatReport::new(); - - let format_result = format_ast( - &krate, - &mut parse_session, - &main_file, - config, - report.clone(), - |file_name, file, skipped_range, report| { - // 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); - - format_lines(file, file_name, skipped_range, config, report); - replace_with_system_newlines(file, config); - - if let Some(ref mut out) = out { - return filemap::write_file(file, file_name, out, config); - } - Ok(false) - }, - ); - - summary.mark_format_time(); - - should_emit_verbose(&main_file, config, || { - fn duration_to_f32(d: Duration) -> f32 { - d.as_secs() as f32 + d.subsec_nanos() as f32 / 1_000_000_000f32 - } - - println!( - "Spent {0:.3} secs in the parsing phase, and {1:.3} secs in the formatting phase", - duration_to_f32(summary.get_parse_time().unwrap()), - duration_to_f32(summary.get_format_time().unwrap()), - ) - }); - - { - let report_errs = &report.internal.borrow().1; - if report_errs.has_check_errors { - summary.add_check_error(); - } - if report_errs.has_operational_errors { - summary.add_operational_error(); - } - } - - match format_result { - Ok((file_map, has_diff, has_macro_rewrite_failure)) => { - if report.has_warnings() { - summary.add_formatting_error(); + should_emit_verbose(&main_file, &self.config, || { + fn duration_to_f32(d: Duration) -> f32 { + d.as_secs() as f32 + d.subsec_nanos() as f32 / 1_000_000_000f32 } - if has_diff { - summary.add_diff(); - } + println!( + "Spent {0:.3} secs in the parsing phase, and {1:.3} secs in the formatting phase", + duration_to_f32(self.summary.get_parse_time().unwrap()), + duration_to_f32(self.summary.get_format_time().unwrap()), + ) + }); - if has_macro_rewrite_failure { - summary.add_macro_foramt_failure(); + { + let report_errs = &report.internal.borrow().1; + if report_errs.has_check_errors { + self.summary.add_check_error(); + } + if report_errs.has_operational_errors { + self.summary.add_operational_error(); } - - Ok((summary, file_map, report)) } - Err(e) => Err((From::from(e), summary)), + + match format_result { + Ok((file_map, has_diff, has_macro_rewrite_failure)) => { + if report.has_warnings() { + self.summary.add_formatting_error(); + } + + if has_diff { + self.summary.add_diff(); + } + + if has_macro_rewrite_failure { + self.summary.add_macro_foramt_failure(); + } + + Ok((file_map, report)) + } + Err(e) => Err(From::from(e)), + } } } @@ -551,43 +553,3 @@ pub(crate) struct ModifiedLines { /// The set of changed chunks. pub chunks: Vec, } - -/// Format a file and return a `ModifiedLines` data structure describing -/// the changed ranges of lines. -#[cfg(test)] -pub(crate) fn get_modified_lines( - input: Input, - config: &Config, -) -> Result { - use std::io::BufRead; - - let mut data = Vec::new(); - - let mut config = config.clone(); - config.set().emit_mode(::config::EmitMode::ModifiedLines); - ::format_input(input, &config, Some(&mut data))?; - - let mut lines = data.lines(); - let mut chunks = Vec::new(); - while let Some(Ok(header)) = lines.next() { - // Parse the header line - let values: Vec<_> = header - .split(' ') - .map(|s| s.parse::().unwrap()) - .collect(); - assert_eq!(values.len(), 3); - let line_number_orig = values[0]; - let lines_removed = values[1]; - let num_added = values[2]; - let mut added_lines = Vec::new(); - for _ in 0..num_added { - added_lines.push(lines.next().unwrap().unwrap()); - } - chunks.push(ModifiedChunk { - line_number_orig, - lines_removed, - lines: added_lines, - }); - } - Ok(ModifiedLines { chunks }) -} diff --git a/src/git-rustfmt/main.rs b/src/git-rustfmt/main.rs index 545756576c9..d6ab3661482 100644 --- a/src/git-rustfmt/main.rs +++ b/src/git-rustfmt/main.rs @@ -22,7 +22,7 @@ use getopts::{Matches, Options}; -use rustfmt::{format_input, load_config, CliOptions, Input}; +use rustfmt::{load_config, CliOptions, Input, Session}; fn prune_files(files: Vec<&str>) -> Vec<&str> { let prefixes: Vec<_> = files @@ -73,16 +73,14 @@ fn fmt_files(files: &[&str]) -> i32 { load_config::(Some(Path::new(".")), None).expect("couldn't load config"); let mut exit_code = 0; + let mut out = stdout(); + let mut session = Session::new(config, Some(&mut out)); for file in files { - let (summary, report) = format_input( - Input::File(PathBuf::from(file)), - &config, - Some(&mut stdout()), - ).unwrap(); + let report = session.format(Input::File(PathBuf::from(file))).unwrap(); if report.has_warnings() { eprintln!("{}", report); } - if !summary.has_no_errors() { + if !session.summary.has_no_errors() { exit_code = 1; } } diff --git a/src/imports.rs b/src/imports.rs index 79d0641504e..f087ea5e52a 100644 --- a/src/imports.rs +++ b/src/imports.rs @@ -349,7 +349,7 @@ fn from_ast( )); } UseTreeKind::Simple(ref rename, ..) => { - let mut name = rewrite_ident(context, path_to_imported_ident(&a.prefix)).to_owned(); + let name = rewrite_ident(context, path_to_imported_ident(&a.prefix)).to_owned(); let alias = rename.and_then(|ident| { if ident == path_to_imported_ident(&a.prefix) { None @@ -511,7 +511,7 @@ fn flatten(self) -> Vec { let prefix = &self.path[..self.path.len() - 1]; let mut result = vec![]; for nested_use_tree in list { - for mut flattend in &mut nested_use_tree.clone().flatten() { + for flattend in &mut nested_use_tree.clone().flatten() { let mut new_path = prefix.to_vec(); new_path.append(&mut flattend.path); result.push(UseTree { @@ -532,7 +532,7 @@ fn flatten(self) -> Vec { fn merge(&mut self, other: UseTree) { let mut new_path = vec![]; - for (mut a, b) in self + for (a, b) in self .path .clone() .iter_mut() diff --git a/src/items.rs b/src/items.rs index 00db19f3b6c..721f02b0187 100644 --- a/src/items.rs +++ b/src/items.rs @@ -663,7 +663,7 @@ pub fn format_impl( option.compress_where(); } - let mut where_clause_str = rewrite_where_clause( + let where_clause_str = rewrite_where_clause( context, &generics.where_clause, context.config.brace_style(), diff --git a/src/lib.rs b/src/lib.rs index 2c1ef49f354..6183117a58c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,7 @@ #![feature(type_ascription)] #![feature(unicode_internals)] #![feature(extern_prelude)] +#![feature(nll)] #[macro_use] extern crate derive_new; @@ -41,17 +42,17 @@ use std::collections::HashMap; use std::fmt; use std::io::{self, Write}; +use std::mem; use std::path::PathBuf; use std::rc::Rc; use syntax::ast; use comment::LineClasses; use failure::Fail; -use formatting::{format_input_inner, FormatErrorMap, FormattingError, ReportedErrors}; +use formatting::{FormatErrorMap, FormattingError, ReportedErrors}; use issues::Issue; use shape::Indent; -pub use checkstyle::{footer as checkstyle_footer, header as checkstyle_header}; pub use config::summary::Summary; pub use config::{ load_config, CliOptions, Color, Config, EmitMode, FileLines, FileName, NewlineStyle, Range, @@ -348,13 +349,16 @@ fn format_snippet(snippet: &str, config: &Config) -> Option { config.set().emit_mode(config::EmitMode::Stdout); config.set().verbose(Verbosity::Quiet); config.set().hide_parse_errors(true); - match format_input(input, &config, Some(&mut out)) { - // `format_input()` returns an empty string on parsing error. - Ok((summary, _)) if summary.has_macro_formatting_failure() => None, - Ok(..) if out.is_empty() && !snippet.is_empty() => None, - Ok(..) => String::from_utf8(out).ok(), - Err(..) => None, + { + let mut session = Session::new(config, Some(&mut out)); + let result = session.format(input); + let formatting_error = session.summary.has_macro_formatting_failure() + || session.out.as_ref().unwrap().is_empty() && !snippet.is_empty(); + if formatting_error || result.is_err() { + return None; + } } + String::from_utf8(out).ok() } /// Format the given code block. Mainly targeted for code block in comment. @@ -436,26 +440,61 @@ fn enclose_in_main_block(s: &str, config: &Config) -> String { Some(result) } +/// A session is a run of rustfmt across a single or multiple inputs. +pub struct Session<'b, T: Write + 'b> { + pub config: Config, + pub out: Option<&'b mut T>, + pub summary: Summary, +} + +impl<'b, T: Write + 'b> Session<'b, T> { + pub fn new(config: Config, out: Option<&'b mut T>) -> Session<'b, T> { + if config.emit_mode() == EmitMode::Checkstyle { + println!("{}", checkstyle::header()); + } + + Session { + config, + out, + summary: Summary::default(), + } + } + + /// The main entry point for Rustfmt. Formats the given input according to the + /// given config. `out` is only necessary if required by the configuration. + pub fn format(&mut self, input: Input) -> Result { + if !self.config.version_meets_requirement() { + return Err(ErrorKind::VersionMismatch); + } + + syntax::with_globals(|| self.format_input_inner(input)).map(|tup| tup.1) + } + + pub fn override_config(&mut self, mut config: Config, f: F) -> U + where + F: FnOnce(&mut Session<'b, T>) -> U, + { + mem::swap(&mut config, &mut self.config); + let result = f(self); + mem::swap(&mut config, &mut self.config); + result + } +} + +impl<'b, T: Write + 'b> Drop for Session<'b, T> { + fn drop(&mut self) { + if self.config.emit_mode() == EmitMode::Checkstyle { + println!("{}", checkstyle::footer()); + } + } +} + #[derive(Debug)] pub enum Input { File(PathBuf), Text(String), } -/// The main entry point for Rustfmt. Formats the given input according to the -/// given config. `out` is only necessary if required by the configuration. -pub fn format_input( - input: Input, - config: &Config, - out: Option<&mut T>, -) -> Result<(Summary, FormatReport), (ErrorKind, Summary)> { - if !config.version_meets_requirement() { - return Err((ErrorKind::VersionMismatch, Summary::default())); - } - - syntax::with_globals(|| format_input_inner(input, config, out)).map(|tup| (tup.0, tup.2)) -} - #[cfg(test)] mod unit_tests { use super::*; diff --git a/src/test/mod.rs b/src/test/mod.rs index b2eead3183d..f02cfbc858c 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -23,9 +23,9 @@ use config::summary::Summary; use config::{Color, Config, EmitMode, FileName, ReportTactic}; use filemap; -use formatting::{format_input_inner, get_modified_lines, FileMap, ModifiedChunk, ModifiedLines}; +use formatting::{FileMap, ModifiedChunk}; use rustfmt_diff::{make_diff, print_diff, DiffLine, Mismatch, OutputWriter}; -use {format_input, FormatReport, Input}; +use {FormatReport, Input, Session}; const DIFF_CONTEXT_SIZE: usize = 3; const CONFIGURATIONS_FILE_NAME: &str = "Configurations.md"; @@ -142,25 +142,56 @@ fn checkstyle_test() { #[test] fn modified_test() { + use std::io::BufRead; + // Test "modified" output let filename = "tests/writemode/source/modified.rs"; - let result = get_modified_lines(Input::File(filename.into()), &Config::default()).unwrap(); - assert_eq!( - result, - ModifiedLines { - chunks: vec![ - ModifiedChunk { - line_number_orig: 4, - lines_removed: 4, - lines: vec!["fn blah() {}".into()], - }, - ModifiedChunk { - line_number_orig: 9, - lines_removed: 6, - lines: vec!["#[cfg(a, b)]".into(), "fn main() {}".into()], - }, - ], + let mut data = Vec::new(); + let mut config = Config::default(); + config.set().emit_mode(::config::EmitMode::ModifiedLines); + + { + let mut session = Session::new(config, Some(&mut data)); + session.format(Input::File(filename.into())).unwrap(); + } + + let mut lines = data.lines(); + let mut chunks = Vec::new(); + while let Some(Ok(header)) = lines.next() { + // Parse the header line + let values: Vec<_> = header + .split(' ') + .map(|s| s.parse::().unwrap()) + .collect(); + assert_eq!(values.len(), 3); + let line_number_orig = values[0]; + let lines_removed = values[1]; + let num_added = values[2]; + let mut added_lines = Vec::new(); + for _ in 0..num_added { + added_lines.push(lines.next().unwrap().unwrap()); } + chunks.push(ModifiedChunk { + line_number_orig, + lines_removed, + lines: added_lines, + }); + } + + assert_eq!( + chunks, + vec![ + ModifiedChunk { + line_number_orig: 4, + lines_removed: 4, + lines: vec!["fn blah() {}".into()], + }, + ModifiedChunk { + line_number_orig: 9, + lines_removed: 6, + lines: vec!["#[cfg(a, b)]".into(), "fn main() {}".into()], + }, + ], ); } @@ -168,7 +199,7 @@ fn modified_test() { // to a known output file generated by one of the write modes. fn assert_output(source: &Path, expected_filename: &Path) { let config = read_config(source); - let (_error_summary, file_map, _report) = format_file(source, &config); + let (_, file_map, _) = format_file(source, config.clone()); // Populate output by writing to a vec. let mut out = vec![]; @@ -246,8 +277,11 @@ fn stdin_formatting_smoke_test() { let mut config = Config::default(); config.set().emit_mode(EmitMode::Stdout); let mut buf: Vec = vec![]; - let (error_summary, _) = format_input(input, &config, Some(&mut buf)).unwrap(); - assert!(error_summary.has_no_errors()); + { + let mut session = Session::new(config, Some(&mut buf)); + session.format(input).unwrap(); + assert!(session.summary.has_no_errors()); + } //eprintln!("{:?}", ); #[cfg(not(windows))] assert_eq!(buf, "fn main() {}\n".as_bytes()); @@ -284,8 +318,9 @@ fn format_lines_errors_are_reported() { let input = Input::Text(format!("fn {}() {{}}", long_identifier)); let mut config = Config::default(); config.set().error_on_line_overflow(true); - let (error_summary, _) = format_input::(input, &config, None).unwrap(); - assert!(error_summary.has_formatting_errors()); + let mut session = Session::::new(config, None); + session.format(input).unwrap(); + assert!(session.summary.has_formatting_errors()); } #[test] @@ -295,8 +330,9 @@ fn format_lines_errors_are_reported_with_tabs() { let mut config = Config::default(); config.set().error_on_line_overflow(true); config.set().hard_tabs(true); - let (error_summary, _) = format_input::(input, &config, None).unwrap(); - assert!(error_summary.has_formatting_errors()); + let mut session = Session::::new(config, None); + session.format(input).unwrap(); + assert!(session.summary.has_formatting_errors()); } // For each file, run rustfmt and collect the output. @@ -380,11 +416,15 @@ fn read_config(filename: &Path) -> Config { config } -fn format_file>(filepath: P, config: &Config) -> (Summary, FileMap, FormatReport) { +fn format_file>(filepath: P, config: Config) -> (bool, FileMap, FormatReport) { let filepath = filepath.into(); let input = Input::File(filepath); - //format_input::(input, config, None).unwrap() - syntax::with_globals(|| format_input_inner::(input, config, None)).unwrap() + let mut session = Session::::new(config, None); + syntax::with_globals(|| { + let result = session.format_input_inner(input).unwrap(); + let parsing_errors = session.summary.has_parsing_errors(); + (parsing_errors, result.0, result.1) + }) } enum IdempotentCheckError { @@ -402,8 +442,8 @@ fn idempotent_check( } else { read_config(filename) }; - let (error_summary, file_map, format_report) = format_file(filename, &config); - if error_summary.has_parsing_errors() { + let (parsing_errors, file_map, format_report) = format_file(filename, config); + if parsing_errors { return Err(IdempotentCheckError::Parse); } @@ -779,10 +819,15 @@ fn formatted_is_idempotent(&self) -> bool { config.set().emit_mode(EmitMode::Stdout); let mut buf: Vec = vec![]; - let (error_summary, _) = format_input(input, &config, Some(&mut buf)).unwrap(); + { + let mut session = Session::new(config, Some(&mut buf)); + session.format(input).unwrap(); + if self.has_parsing_errors(session.summary) { + return false; + } + } - !self.has_parsing_errors(error_summary) - && !self.formatted_has_diff(&String::from_utf8(buf).unwrap()) + !self.formatted_has_diff(&String::from_utf8(buf).unwrap()) } // Extract a code block from the iterator. Behavior: