From ca610d35b3a78b9b2e9d8444eaf0296a1a242277 Mon Sep 17 00:00:00 2001 From: Nick Cameron Date: Fri, 20 Apr 2018 21:08:20 +1200 Subject: [PATCH] Refactor to make a sensible public API 0.5 - lots of breaking changes cc #2639 --- Cargo.lock | 2 +- Cargo.toml | 4 +- src/bin/main.rs | 216 ++++++-------------------------- src/checkstyle.rs | 27 ++-- src/config/config_type.rs | 15 +-- src/config/file_lines.rs | 14 ++- src/config/license.rs | 3 +- src/config/mod.rs | 83 +++++++++--- src/config/options.rs | 103 +++++++++++++++ src/config/summary.rs | 14 +-- src/filemap.rs | 14 ++- src/git-rustfmt/main.rs | 7 +- src/lib.rs | 75 +++++++---- src/shape.rs | 8 -- tests/lib.rs => src/test/mod.rs | 17 +-- src/utils.rs | 9 -- 16 files changed, 315 insertions(+), 296 deletions(-) rename tests/lib.rs => src/test/mod.rs (98%) diff --git a/Cargo.lock b/Cargo.lock index ee76b0204769..4ce2fae6a3f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -451,7 +451,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "rustfmt-nightly" -version = "0.4.2" +version = "0.5.0" dependencies = [ "assert_cli 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", "cargo_metadata 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index e989e123dbb3..bacde50ce777 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "rustfmt-nightly" -version = "0.4.2" +version = "0.5.0" authors = ["Nicholas Cameron ", "The Rustfmt developers"] description = "Tool to find and fix Rust formatting issues" repository = "https://github.com/rust-lang-nursery/rustfmt" @@ -49,8 +49,8 @@ cargo_metadata = "0.5.1" rustc-ap-syntax = "103.0.0" [dev-dependencies] -lazy_static = "1.0.0" assert_cli = "0.5" +lazy_static = "1.0.0" [target.'cfg(unix)'.dependencies] libc = "0.2.11" diff --git a/src/bin/main.rs b/src/bin/main.rs index 8d95dbb209f0..453c7806d234 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -14,23 +14,16 @@ extern crate env_logger; extern crate getopts; extern crate rustfmt_nightly as rustfmt; +use std::env; use std::fs::File; use std::io::{self, stdout, Read, Write}; -use std::path::{Path, PathBuf}; -use std::str::FromStr; -use std::{env, error}; +use std::path::PathBuf; use getopts::{Matches, Options}; -use rustfmt::checkstyle; -use rustfmt::config::file_lines::FileLines; -use rustfmt::config::{get_toml_path, Color, Config, WriteMode}; -use rustfmt::{run, FileName, Input, Summary}; - -type FmtError = Box; -type FmtResult = std::result::Result; - -const WRITE_MODE_LIST: &str = "[replace|overwrite|display|plain|diff|coverage|checkstyle|check]"; +use rustfmt::{emit_post_matter, emit_pre_matter, load_config, CliOptions, Config, FmtResult, + WriteMode, WRITE_MODE_LIST}; +use rustfmt::{format_and_emit_report, FileName, Input, Summary}; fn main() { env_logger::init(); @@ -66,7 +59,6 @@ enum Operation { /// Format files and their child modules. Format { files: Vec, - config_path: Option, minimal_config_path: Option, }, /// Print the help message. @@ -82,105 +74,9 @@ enum Operation { /// No file specified, read from stdin Stdin { input: String, - config_path: Option, }, } -/// Parsed command line options. -#[derive(Clone, Debug, Default)] -struct CliOptions { - skip_children: Option, - verbose: bool, - verbose_diff: bool, - write_mode: Option, - color: Option, - file_lines: FileLines, // Default is all lines in all files. - unstable_features: bool, - error_on_unformatted: Option, -} - -impl CliOptions { - fn from_matches(matches: &Matches) -> FmtResult { - let mut options = CliOptions::default(); - options.verbose = matches.opt_present("verbose"); - options.verbose_diff = matches.opt_present("verbose-diff"); - - let unstable_features = matches.opt_present("unstable-features"); - let rust_nightly = option_env!("CFG_RELEASE_CHANNEL") - .map(|c| c == "nightly") - .unwrap_or(false); - if unstable_features && !rust_nightly { - return Err(FmtError::from( - "Unstable features are only available on Nightly channel", - )); - } else { - options.unstable_features = unstable_features; - } - - if let Some(ref write_mode) = matches.opt_str("write-mode") { - if let Ok(write_mode) = WriteMode::from_str(write_mode) { - options.write_mode = Some(write_mode); - } else { - return Err(FmtError::from(format!( - "Invalid write-mode: {}, expected one of {}", - write_mode, WRITE_MODE_LIST - ))); - } - } - - if let Some(ref color) = matches.opt_str("color") { - match Color::from_str(color) { - Ok(color) => options.color = Some(color), - _ => return Err(FmtError::from(format!("Invalid color: {}", color))), - } - } - - if let Some(ref file_lines) = matches.opt_str("file-lines") { - options.file_lines = file_lines.parse()?; - } - - if matches.opt_present("skip-children") { - options.skip_children = Some(true); - } - if matches.opt_present("error-on-unformatted") { - options.error_on_unformatted = Some(true); - } - - Ok(options) - } - - fn apply_to(self, config: &mut Config) { - config.set().verbose(self.verbose); - config.set().verbose_diff(self.verbose_diff); - config.set().file_lines(self.file_lines); - config.set().unstable_features(self.unstable_features); - if let Some(skip_children) = self.skip_children { - config.set().skip_children(skip_children); - } - if let Some(error_on_unformatted) = self.error_on_unformatted { - config.set().error_on_unformatted(error_on_unformatted); - } - if let Some(write_mode) = self.write_mode { - config.set().write_mode(write_mode); - } - if let Some(color) = self.color { - config.set().color(color); - } - } -} - -/// read the given config file path recursively if present else read the project file path -fn match_cli_path_or_file( - config_path: Option, - input_file: &Path, -) -> FmtResult<(Config, Option)> { - if let Some(config_file) = config_path { - let toml = Config::from_toml_path(config_file.as_ref())?; - return Ok((toml, Some(config_file))); - } - Config::from_resolved_toml_path(input_file).map_err(FmtError::from) -} - fn make_opts() -> Options { let mut opts = Options::new(); @@ -280,10 +176,10 @@ fn execute(opts: &Options) -> FmtResult<(WriteMode, Summary)> { } Ok((WriteMode::None, Summary::default())) } - Operation::Stdin { input, config_path } => { + Operation::Stdin { input } => { // try to read config from local directory - let (mut config, _) = - match_cli_path_or_file(config_path, &env::current_dir().unwrap())?; + let options = CliOptions::from_matches(&matches)?; + let (mut config, _) = load_config(None, Some(&options))?; // write_mode is always Plain for Stdin. config.set().write_mode(WriteMode::Plain); @@ -300,57 +196,40 @@ fn execute(opts: &Options) -> FmtResult<(WriteMode, Summary)> { } let mut error_summary = Summary::default(); - if config.version_meets_requirement(&mut error_summary) { - let mut out = &mut stdout(); - checkstyle::output_header(&mut out, config.write_mode())?; - error_summary.add(run(Input::Text(input), &config)); - checkstyle::output_footer(&mut out, config.write_mode())?; + 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((WriteMode::Plain, error_summary)) } Operation::Format { files, - config_path, minimal_config_path, } => { let options = CliOptions::from_matches(&matches)?; - format(files, config_path, minimal_config_path, options) + format(files, minimal_config_path, options) } } } fn format( files: Vec, - config_path: Option, minimal_config_path: Option, options: CliOptions, ) -> FmtResult<(WriteMode, Summary)> { - for f in options.file_lines.files() { - match *f { - FileName::Real(ref f) if files.contains(f) => {} - FileName::Real(_) => { - eprintln!("Warning: Extra file listed in file_lines option '{}'", f) - } - _ => eprintln!("Warning: Not a file '{}'", f), - } - } + options.verify_file_lines(&files); + let (config, config_path) = load_config(None, Some(&options))?; - let mut config = Config::default(); - // Load the config path file if provided - if let Some(config_file) = config_path.as_ref() { - config = Config::from_toml_path(config_file.as_ref())?; - }; - - if options.verbose { + if config.verbose() { if let Some(path) = config_path.as_ref() { println!("Using rustfmt config file {}", path.display()); } } - let write_mode = config.write_mode(); - let mut out = &mut stdout(); - checkstyle::output_header(&mut out, write_mode)?; + emit_pre_matter(&config)?; let mut error_summary = Summary::default(); for file in files { @@ -362,11 +241,11 @@ fn format( error_summary.add_operational_error(); } else { // Check the file directory if the config-path could not be read or not provided - if config_path.is_none() { - let (config_tmp, path_tmp) = - Config::from_resolved_toml_path(file.parent().unwrap())?; - if options.verbose { - if let Some(path) = path_tmp.as_ref() { + let local_config = if config_path.is_none() { + let (local_config, config_path) = + load_config(Some(file.parent().unwrap()), Some(&options))?; + if local_config.verbose() { + if let Some(path) = config_path { println!( "Using rustfmt config file {} for {}", path.display(), @@ -374,18 +253,21 @@ fn format( ); } } - config = config_tmp; - } + local_config + } else { + config.clone() + }; - if !config.version_meets_requirement(&mut error_summary) { - break; + match format_and_emit_report(Input::File(file), &local_config) { + Ok(summary) => error_summary.add(summary), + Err(_) => { + error_summary.add_operational_error(); + break; + } } - - options.clone().apply_to(&mut config); - error_summary.add(run(Input::File(file), &config)); } } - checkstyle::output_footer(&mut out, write_mode)?; + emit_post_matter(&config)?; // If we were given a path via dump-minimal-config, output any options // that were used during formatting as TOML. @@ -395,7 +277,7 @@ fn format( file.write_all(toml.as_bytes())?; } - Ok((write_mode, error_summary)) + Ok((config.write_mode(), error_summary)) } fn print_usage_to_stdout(opts: &Options, reason: &str) { @@ -451,28 +333,6 @@ fn determine_operation(matches: &Matches) -> FmtResult { return Ok(Operation::Version); } - let config_path_not_found = |path: &str| -> FmtResult { - Err(FmtError::from(format!( - "Error: unable to find a config file for the given path: `{}`", - path - ))) - }; - - // Read the config_path and convert to parent dir if a file is provided. - // If a config file cannot be found from the given path, return error. - let config_path: Option = match matches.opt_str("config-path").map(PathBuf::from) { - Some(ref path) if !path.exists() => return config_path_not_found(path.to_str().unwrap()), - Some(ref path) if path.is_dir() => { - let config_file_path = get_toml_path(path)?; - if config_file_path.is_some() { - config_file_path - } else { - return config_path_not_found(path.to_str().unwrap()); - } - } - path => path, - }; - // If no path is given, we won't output a minimal config. let minimal_config_path = matches.opt_str("dump-minimal-config"); @@ -481,10 +341,7 @@ fn determine_operation(matches: &Matches) -> FmtResult { let mut buffer = String::new(); io::stdin().read_to_string(&mut buffer)?; - return Ok(Operation::Stdin { - input: buffer, - config_path, - }); + return Ok(Operation::Stdin { input: buffer }); } let files: Vec<_> = matches @@ -500,7 +357,6 @@ fn determine_operation(matches: &Matches) -> FmtResult { Ok(Operation::Format { files, - config_path, minimal_config_path, }) } diff --git a/src/checkstyle.rs b/src/checkstyle.rs index 7f6e650ad220..19d737cd4c24 100644 --- a/src/checkstyle.rs +++ b/src/checkstyle.rs @@ -11,33 +11,26 @@ use std::io::{self, Write}; use std::path::Path; -use config::WriteMode; use rustfmt_diff::{DiffLine, Mismatch}; -pub fn output_header(out: &mut T, mode: WriteMode) -> Result<(), io::Error> +pub fn output_header(out: &mut T) -> Result<(), io::Error> where T: Write, { - if mode == WriteMode::Checkstyle { - let mut xml_heading = String::new(); - xml_heading.push_str(""); - xml_heading.push_str("\n"); - xml_heading.push_str(""); - write!(out, "{}", xml_heading)?; - } - Ok(()) + let mut xml_heading = String::new(); + xml_heading.push_str(""); + xml_heading.push_str("\n"); + xml_heading.push_str(""); + write!(out, "{}", xml_heading) } -pub fn output_footer(out: &mut T, mode: WriteMode) -> Result<(), io::Error> +pub fn output_footer(out: &mut T) -> Result<(), io::Error> where T: Write, { - if mode == WriteMode::Checkstyle { - let mut xml_tail = String::new(); - xml_tail.push_str("\n"); - write!(out, "{}", xml_tail)?; - } - Ok(()) + let mut xml_tail = String::new(); + xml_tail.push_str("\n"); + write!(out, "{}", xml_tail) } pub fn output_checkstyle_file( diff --git a/src/config/config_type.rs b/src/config/config_type.rs index 5fc032869556..3b9ca90350cf 100644 --- a/src/config/config_type.rs +++ b/src/config/config_type.rs @@ -80,6 +80,7 @@ macro_rules! is_nightly_channel { macro_rules! create_config { ($($i:ident: $ty:ty, $def:expr, $stb:expr, $( $dstring:expr ),+ );+ $(;)*) => ( + #[cfg(test)] use std::collections::HashSet; use std::io::Write; @@ -150,7 +151,7 @@ macro_rules! create_config { } impl Config { - pub fn version_meets_requirement(&self, error_summary: &mut Summary) -> bool { + pub(crate) fn version_meets_requirement(&self) -> bool { if self.was_set().required_version() { let version = env!("CARGO_PKG_VERSION"); let required_version = self.required_version(); @@ -160,7 +161,6 @@ macro_rules! create_config { version, required_version, ); - error_summary.add_formatting_error(); return false; } } @@ -207,7 +207,8 @@ macro_rules! create_config { } /// Returns a hash set initialized with every user-facing config option name. - pub fn hash_set() -> HashSet { + #[cfg(test)] + pub(crate) fn hash_set() -> HashSet { let mut hash_set = HashSet::new(); $( hash_set.insert(stringify!($i).to_owned()); @@ -215,7 +216,7 @@ macro_rules! create_config { hash_set } - pub fn is_valid_name(name: &str) -> bool { + pub(crate) fn is_valid_name(name: &str) -> bool { match name { $( stringify!($i) => true, @@ -224,7 +225,7 @@ macro_rules! create_config { } } - pub fn from_toml(toml: &str, dir: &Path) -> Result { + pub(crate) fn from_toml(toml: &str, dir: &Path) -> Result { let parsed: ::toml::Value = toml.parse().map_err(|e| format!("Could not parse TOML: {}", e))?; let mut err: String = String::new(); @@ -304,7 +305,7 @@ macro_rules! create_config { /// /// Return a `Config` if the config could be read and parsed from /// the file, Error otherwise. - pub fn from_toml_path(file_path: &Path) -> Result { + pub(super) fn from_toml_path(file_path: &Path) -> Result { let mut file = File::open(&file_path)?; let mut toml = String::new(); file.read_to_string(&mut toml)?; @@ -321,7 +322,7 @@ macro_rules! create_config { /// /// Returns the `Config` to use, and the path of the project file if there was /// one. - pub fn from_resolved_toml_path(dir: &Path) -> Result<(Config, Option), Error> { + pub(super) fn from_resolved_toml_path(dir: &Path) -> Result<(Config, Option), Error> { /// Try to find a project file in the given directory and its parents. /// Returns the path of a the nearest project file if one exists, diff --git a/src/config/file_lines.rs b/src/config/file_lines.rs index cf3f827c00da..5e29e0ff965c 100644 --- a/src/config/file_lines.rs +++ b/src/config/file_lines.rs @@ -54,6 +54,7 @@ impl Range { self.lo > self.hi } + #[allow(dead_code)] fn contains(self, other: Range) -> bool { if other.is_empty() { true @@ -128,12 +129,12 @@ fn normalize_ranges(ranges: &mut HashMap>) { impl FileLines { /// Creates a `FileLines` that contains all lines in all files. - pub fn all() -> FileLines { + pub(crate) fn all() -> FileLines { FileLines(None) } /// Returns true if this `FileLines` contains all lines in all files. - pub fn is_all(&self) -> bool { + pub(crate) fn is_all(&self) -> bool { self.0.is_none() } @@ -166,22 +167,23 @@ impl FileLines { } /// Returns true if `range` is fully contained in `self`. - pub fn contains(&self, range: &LineRange) -> bool { + #[allow(dead_code)] + pub(crate) fn contains(&self, range: &LineRange) -> bool { self.file_range_matches(range.file_name(), |r| r.contains(Range::from(range))) } /// Returns true if any lines in `range` are in `self`. - pub fn intersects(&self, range: &LineRange) -> bool { + pub(crate) fn intersects(&self, range: &LineRange) -> bool { self.file_range_matches(range.file_name(), |r| r.intersects(Range::from(range))) } /// Returns true if `line` from `file_name` is in `self`. - pub fn contains_line(&self, file_name: &FileName, line: usize) -> bool { + pub(crate) fn contains_line(&self, file_name: &FileName, line: usize) -> bool { self.file_range_matches(file_name, |r| r.lo <= line && r.hi >= line) } /// Returns true if any of the lines between `lo` and `hi` from `file_name` are in `self`. - pub fn intersects_range(&self, file_name: &FileName, lo: usize, hi: usize) -> bool { + pub(crate) fn intersects_range(&self, file_name: &FileName, lo: usize, hi: usize) -> bool { self.file_range_matches(file_name, |r| r.intersects(Range::new(lo, hi))) } } diff --git a/src/config/license.rs b/src/config/license.rs index d49fdbe7ebae..630399319c1b 100644 --- a/src/config/license.rs +++ b/src/config/license.rs @@ -82,8 +82,7 @@ impl TemplateParser { /// /// # Examples /// - /// ``` - /// # use rustfmt_nightly::config::license::TemplateParser; + /// ```ignore /// assert_eq!( /// TemplateParser::parse( /// r" diff --git a/src/config/mod.rs b/src/config/mod.rs index f81bbfccd1e1..8dde1e05c3dd 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -8,6 +8,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use regex::Regex; use std::cell::Cell; use std::default::Default; use std::fs::File; @@ -15,23 +16,22 @@ use std::io::{Error, ErrorKind, Read}; use std::path::{Path, PathBuf}; use std::{env, fs}; -use regex::Regex; - -#[macro_use] -mod config_type; -#[macro_use] -mod options; - -pub mod file_lines; -pub mod license; -pub mod lists; -pub mod summary; +use {FmtError, FmtResult}; use config::config_type::ConfigType; use config::file_lines::FileLines; pub use config::lists::*; pub use config::options::*; -use config::summary::Summary; + +#[macro_use] +pub mod config_type; +#[macro_use] +pub mod options; + +pub mod file_lines; +pub mod license; +pub mod lists; +pub mod summary; /// This macro defines configuration options used in rustfmt. Each option /// is defined as follows: @@ -151,10 +151,37 @@ create_config! { "'small' heuristic values"; } -/// Check for the presence of known config file names (`rustfmt.toml, `.rustfmt.toml`) in `dir` -/// -/// Return the path if a config file exists, empty if no file exists, and Error for IO errors -pub fn get_toml_path(dir: &Path) -> Result, Error> { +pub fn load_config( + file_path: Option<&Path>, + options: Option<&CliOptions>, +) -> FmtResult<(Config, Option)> { + let over_ride = match options { + Some(opts) => config_path(opts)?, + None => None, + }; + + let result = if let Some(over_ride) = over_ride { + Config::from_toml_path(over_ride.as_ref()) + .map(|p| (p, Some(over_ride.to_owned()))) + .map_err(FmtError::from) + } else if let Some(file_path) = file_path { + Config::from_resolved_toml_path(file_path).map_err(FmtError::from) + } else { + Ok((Config::default(), None)) + }; + + result.map(|(mut c, p)| { + if let Some(options) = options { + options.clone().apply_to(&mut c); + } + (c, p) + }) +} + +// Check for the presence of known config file names (`rustfmt.toml, `.rustfmt.toml`) in `dir` +// +// Return the path if a config file exists, empty if no file exists, and Error for IO errors +fn get_toml_path(dir: &Path) -> Result, Error> { const CONFIG_FILE_NAMES: [&str; 2] = [".rustfmt.toml", "rustfmt.toml"]; for config_file_name in &CONFIG_FILE_NAMES { let config_file = dir.join(config_file_name); @@ -175,6 +202,30 @@ pub fn get_toml_path(dir: &Path) -> Result, Error> { Ok(None) } +fn config_path(options: &CliOptions) -> FmtResult> { + let config_path_not_found = |path: &str| -> FmtResult> { + Err(FmtError::from(format!( + "Error: unable to find a config file for the given path: `{}`", + path + ))) + }; + + // Read the config_path and convert to parent dir if a file is provided. + // If a config file cannot be found from the given path, return error. + match options.config_path { + Some(ref path) if !path.exists() => config_path_not_found(path.to_str().unwrap()), + Some(ref path) if path.is_dir() => { + let config_file_path = get_toml_path(path)?; + if config_file_path.is_some() { + Ok(config_file_path) + } else { + config_path_not_found(path.to_str().unwrap()) + } + } + ref path => Ok(path.to_owned()), + } +} + #[cfg(test)] mod test { use super::Config; diff --git a/src/config/options.rs b/src/config/options.rs index 2992bf48a808..73dbdb88663c 100644 --- a/src/config/options.rs +++ b/src/config/options.rs @@ -11,10 +11,15 @@ use syntax::codemap::FileName; use config::config_type::ConfigType; +use config::file_lines::FileLines; use config::lists::*; +use config::Config; +use {FmtError, FmtResult, WRITE_MODE_LIST}; +use getopts::Matches; use std::collections::HashSet; use std::path::{Path, PathBuf}; +use std::str::FromStr; /// Macro for deriving implementations of Serialize/Deserialize for enums #[macro_export] @@ -301,3 +306,101 @@ impl ::std::str::FromStr for IgnoreList { Err("IgnoreList is not parsable") } } + +/// Parsed command line options. +#[derive(Clone, Debug, Default)] +pub struct CliOptions { + skip_children: Option, + verbose: bool, + verbose_diff: bool, + pub(super) config_path: Option, + write_mode: Option, + color: Option, + file_lines: FileLines, // Default is all lines in all files. + unstable_features: bool, + error_on_unformatted: Option, +} + +impl CliOptions { + pub fn from_matches(matches: &Matches) -> FmtResult { + let mut options = CliOptions::default(); + options.verbose = matches.opt_present("verbose"); + options.verbose_diff = matches.opt_present("verbose-diff"); + + let unstable_features = matches.opt_present("unstable-features"); + let rust_nightly = option_env!("CFG_RELEASE_CHANNEL") + .map(|c| c == "nightly") + .unwrap_or(false); + if unstable_features && !rust_nightly { + return Err(FmtError::from( + "Unstable features are only available on Nightly channel", + )); + } else { + options.unstable_features = unstable_features; + } + + options.config_path = matches.opt_str("config-path").map(PathBuf::from); + + if let Some(ref write_mode) = matches.opt_str("write-mode") { + if let Ok(write_mode) = WriteMode::from_str(write_mode) { + options.write_mode = Some(write_mode); + } else { + return Err(FmtError::from(format!( + "Invalid write-mode: {}, expected one of {}", + write_mode, WRITE_MODE_LIST + ))); + } + } + + if let Some(ref color) = matches.opt_str("color") { + match Color::from_str(color) { + Ok(color) => options.color = Some(color), + _ => return Err(FmtError::from(format!("Invalid color: {}", color))), + } + } + + if let Some(ref file_lines) = matches.opt_str("file-lines") { + options.file_lines = file_lines.parse()?; + } + + if matches.opt_present("skip-children") { + options.skip_children = Some(true); + } + if matches.opt_present("error-on-unformatted") { + options.error_on_unformatted = Some(true); + } + + Ok(options) + } + + pub fn apply_to(self, config: &mut Config) { + config.set().verbose(self.verbose); + config.set().verbose_diff(self.verbose_diff); + config.set().file_lines(self.file_lines); + config.set().unstable_features(self.unstable_features); + if let Some(skip_children) = self.skip_children { + config.set().skip_children(skip_children); + } + if let Some(error_on_unformatted) = self.error_on_unformatted { + config.set().error_on_unformatted(error_on_unformatted); + } + if let Some(write_mode) = self.write_mode { + config.set().write_mode(write_mode); + } + if let Some(color) = self.color { + config.set().color(color); + } + } + + pub fn verify_file_lines(&self, files: &[PathBuf]) { + for f in self.file_lines.files() { + match *f { + FileName::Real(ref f) if files.contains(f) => {} + FileName::Real(_) => { + eprintln!("Warning: Extra file listed in file_lines option '{}'", f) + } + _ => eprintln!("Warning: Not a file '{}'", f), + } + } + } +} diff --git a/src/config/summary.rs b/src/config/summary.rs index 3654ea89df4f..c906c77506f2 100644 --- a/src/config/summary.rs +++ b/src/config/summary.rs @@ -31,16 +31,16 @@ pub struct Summary { } impl Summary { - pub fn mark_parse_time(&mut self) { + pub(crate) fn mark_parse_time(&mut self) { self.timer = self.timer.done_parsing(); } - pub fn mark_format_time(&mut self) { + pub(crate) fn mark_format_time(&mut self) { self.timer = self.timer.done_formatting(); } /// Returns the time it took to parse the source files in nanoseconds. - pub fn get_parse_time(&self) -> Option { + pub(crate) fn get_parse_time(&self) -> Option { match self.timer { Timer::DoneParsing(init, parse_time) | Timer::DoneFormatting(init, parse_time, _) => { // This should never underflow since `Instant::now()` guarantees monotonicity. @@ -52,7 +52,7 @@ impl Summary { /// Returns the time it took to go from the parsed AST to the formatted output. Parsing time is /// not included. - pub fn get_format_time(&self) -> Option { + pub(crate) fn get_format_time(&self) -> Option { match self.timer { Timer::DoneFormatting(_init, parse_time, format_time) => { Some(format_time.duration_since(parse_time)) @@ -77,15 +77,15 @@ impl Summary { self.has_operational_errors = true; } - pub fn add_parsing_error(&mut self) { + pub(crate) fn add_parsing_error(&mut self) { self.has_parsing_errors = true; } - pub fn add_formatting_error(&mut self) { + pub(crate) fn add_formatting_error(&mut self) { self.has_formatting_errors = true; } - pub fn add_diff(&mut self) { + pub(crate) fn add_diff(&mut self) { self.has_diff = true; } diff --git a/src/filemap.rs b/src/filemap.rs index 54bfe7996b1f..f74918935129 100644 --- a/src/filemap.rs +++ b/src/filemap.rs @@ -14,11 +14,12 @@ use std::fs::{self, File}; use std::io::{self, BufWriter, Read, Write}; use std::path::Path; -use checkstyle::{output_checkstyle_file, output_footer, output_header}; +use checkstyle::output_checkstyle_file; use config::{Config, NewlineStyle, WriteMode}; use rustfmt_diff::{make_diff, output_modified, print_diff, Mismatch}; use syntax::codemap::FileName; +#[cfg(test)] use FileRecord; // Append a newline to the end of each file. @@ -26,7 +27,8 @@ pub fn append_newline(s: &mut String) { s.push_str("\n"); } -pub fn write_all_files( +#[cfg(test)] +pub(crate) fn write_all_files( file_map: &[FileRecord], out: &mut T, config: &Config, @@ -34,11 +36,15 @@ pub fn write_all_files( where T: Write, { - output_header(out, config.write_mode()).ok(); + if config.write_mode() == WriteMode::Checkstyle { + ::checkstyle::output_header(out)?; + } for &(ref filename, ref text) in file_map { write_file(text, filename, out, config)?; } - output_footer(out, config.write_mode()).ok(); + if config.write_mode() == WriteMode::Checkstyle { + ::checkstyle::output_footer(out)?; + } Ok(()) } diff --git a/src/git-rustfmt/main.rs b/src/git-rustfmt/main.rs index 3ff9455d5f79..390eeb4149a8 100644 --- a/src/git-rustfmt/main.rs +++ b/src/git-rustfmt/main.rs @@ -21,7 +21,7 @@ use std::str::FromStr; use getopts::{Matches, Options}; -use rustfmt::{config, run, Input}; +use rustfmt::{format_and_emit_report, load_config, Input}; fn prune_files(files: Vec<&str>) -> Vec<&str> { let prefixes: Vec<_> = files @@ -68,12 +68,11 @@ fn get_files(input: &str) -> Vec<&str> { } fn fmt_files(files: &[&str]) -> i32 { - let (config, _) = config::Config::from_resolved_toml_path(Path::new(".")) - .unwrap_or_else(|_| (config::Config::default(), None)); + let (config, _) = load_config(Some(Path::new(".")), None).expect("couldn't load config"); let mut exit_code = 0; for file in files { - let summary = run(Input::File(PathBuf::from(file)), &config); + let summary = format_and_emit_report(Input::File(PathBuf::from(file)), &config).unwrap(); if !summary.has_no_errors() { exit_code = 1; } diff --git a/src/lib.rs b/src/lib.rs index ddb3ebeb2e9a..506defad30e0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,7 +19,11 @@ #[macro_use] extern crate derive_new; extern crate diff; +extern crate getopts; extern crate itertools; +#[cfg(test)] +#[macro_use] +extern crate lazy_static; #[macro_use] extern crate log; extern crate regex; @@ -33,6 +37,7 @@ extern crate toml; extern crate unicode_segmentation; use std::collections::HashMap; +use std::error; use std::fmt; use std::io::{self, stdout, BufRead, Write}; use std::panic::{catch_unwind, AssertUnwindSafe}; @@ -53,21 +58,28 @@ use shape::Indent; use utils::use_colored_tty; use visitor::{FmtVisitor, SnippetProvider}; +pub use config::options::CliOptions; pub use config::summary::Summary; -pub use config::Config; +pub use config::{file_lines, load_config, Config, WriteMode}; + +pub type FmtError = Box; +pub type FmtResult = std::result::Result; + +pub const WRITE_MODE_LIST: &str = + "[replace|overwrite|display|plain|diff|coverage|checkstyle|check]"; #[macro_use] mod utils; mod attr; mod chains; -pub mod checkstyle; +pub(crate) mod checkstyle; mod closures; -pub mod codemap; +pub(crate) mod codemap; mod comment; -pub mod config; +pub(crate) mod config; mod expr; -pub mod filemap; +pub(crate) mod filemap; mod imports; mod issues; mod items; @@ -75,25 +87,27 @@ mod lists; mod macros; mod matches; mod missed_spans; -pub mod modules; +pub(crate) mod modules; mod overflow; mod patterns; mod reorder; mod rewrite; -pub mod rustfmt_diff; +pub(crate) mod rustfmt_diff; mod shape; mod spanned; mod string; +#[cfg(test)] +mod test; mod types; mod vertical; -pub mod visitor; +pub(crate) mod visitor; const STDIN: &str = ""; // A map of the files of a crate, with their new content -pub type FileMap = Vec; +pub(crate) type FileMap = Vec; -pub type FileRecord = (FileName, String); +pub(crate) type FileRecord = (FileName, String); #[derive(Clone, Copy)] pub enum ErrorKind { @@ -123,7 +137,7 @@ impl fmt::Display for ErrorKind { } // Formatting errors that are identified *after* rustfmt has run. -pub struct FormattingError { +struct FormattingError { line: usize, kind: ErrorKind, is_comment: bool, @@ -150,7 +164,7 @@ impl FormattingError { } // (space, target) - pub fn format_len(&self) -> (usize, usize) { + fn format_len(&self) -> (usize, usize) { match self.kind { ErrorKind::LineOverflow(found, max) => (max, found - max), ErrorKind::TrailingWhitespace => { @@ -180,18 +194,18 @@ impl FormatReport { } } - pub fn warning_count(&self) -> usize { + fn warning_count(&self) -> usize { self.file_error_map .iter() .map(|(_, errors)| errors.len()) .sum() } - pub fn has_warnings(&self) -> bool { + fn has_warnings(&self) -> bool { self.warning_count() > 0 } - pub fn print_warnings_fancy( + fn print_warnings_fancy( &self, mut t: Box>, ) -> Result<(), term::Error> { @@ -881,7 +895,10 @@ pub enum Input { Text(String), } -pub fn run(input: Input, config: &Config) -> Summary { +pub fn format_and_emit_report(input: Input, config: &Config) -> FmtResult { + if !config.version_meets_requirement() { + return Err(FmtError::from("Version mismatch")); + } let out = &mut stdout(); match format_input(input, config, Some(out)) { Ok((summary, _, report)) => { @@ -896,22 +913,38 @@ pub fn run(input: Input, config: &Config) -> Summary { Err(..) => panic!("Unable to write to stderr: {}", report), } } - _ => msg!("{}", report), + _ => eprintln!("{}", report), } } - summary + Ok(summary) } Err((msg, mut summary)) => { - msg!("Error writing files: {}", msg); + eprintln!("Error writing files: {}", msg); summary.add_operational_error(); - summary + Ok(summary) } } } +pub fn emit_pre_matter(config: &Config) -> FmtResult<()> { + if config.write_mode() == WriteMode::Checkstyle { + let mut out = &mut stdout(); + checkstyle::output_header(&mut out)?; + } + Ok(()) +} + +pub fn emit_post_matter(config: &Config) -> FmtResult<()> { + if config.write_mode() == WriteMode::Checkstyle { + let mut out = &mut stdout(); + checkstyle::output_footer(&mut out)?; + } + Ok(()) +} + #[cfg(test)] -mod test { +mod unit_tests { use super::{format_code_block, format_snippet, Config}; #[test] diff --git a/src/shape.rs b/src/shape.rs index c6a183216b80..8a17389672fc 100644 --- a/src/shape.rs +++ b/src/shape.rs @@ -197,14 +197,6 @@ impl Shape { } } - pub fn offset(width: usize, indent: Indent, offset: usize) -> Shape { - Shape { - width, - indent, - offset, - } - } - pub fn visual_indent(&self, extra_width: usize) -> Shape { let alignment = self.offset + extra_width; Shape { diff --git a/tests/lib.rs b/src/test/mod.rs similarity index 98% rename from tests/lib.rs rename to src/test/mod.rs index 7e2947929dea..b0971136352a 100644 --- a/tests/lib.rs +++ b/src/test/mod.rs @@ -9,13 +9,6 @@ // except according to those terms. extern crate assert_cli; -#[macro_use] -extern crate lazy_static; -#[macro_use] -extern crate log; -extern crate regex; -extern crate rustfmt_nightly as rustfmt; -extern crate term; use std::collections::{HashMap, HashSet}; use std::fs; @@ -24,11 +17,11 @@ use std::iter::{Enumerate, Peekable}; use std::path::{Path, PathBuf}; use std::str::Chars; -use rustfmt::config::summary::Summary; -use rustfmt::config::{Color, Config, ReportTactic}; -use rustfmt::filemap::write_system_newlines; -use rustfmt::rustfmt_diff::*; -use rustfmt::*; +use config::summary::Summary; +use config::{Color, Config, ReportTactic}; +use filemap::write_system_newlines; +use rustfmt_diff::*; +use *; const DIFF_CONTEXT_SIZE: usize = 3; const CONFIGURATIONS_FILE_NAME: &str = "Configurations.md"; diff --git a/src/utils.rs b/src/utils.rs index 00d6cf8ed08d..2ede562e79d7 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -255,15 +255,6 @@ pub fn count_newlines(input: &str) -> usize { input.as_bytes().iter().filter(|&&c| c == b'\n').count() } -macro_rules! msg { - ($($arg:tt)*) => ( - match writeln!(&mut ::std::io::stderr(), $($arg)* ) { - Ok(_) => {}, - Err(x) => panic!("Unable to write to stderr: {}", x), - } - ) -} - // For format_missing and last_pos, need to use the source callsite (if applicable). // Required as generated code spans aren't guaranteed to follow on from the last span. macro_rules! source {