From 84ad70c1517f49e8881c9fffbdceaeee39c48bd4 Mon Sep 17 00:00:00 2001 From: Seiichi Uchida Date: Fri, 9 Mar 2018 13:27:43 +0900 Subject: [PATCH] Add ignore config option For example, with the following config file, rustfmt will ignore `src/types.rs`, `src/foo/bar.rs` and every file under `examples/` directory. ```toml [ignore] files = [ "src/types.rs", "src/foo/bar.rs", ] directories = [ "examples", ] ``` --- Configurations.md | 29 ++++++++++++++++ src/bin/main.rs | 3 +- src/config/config_type.rs | 22 +++++++++--- src/config/mod.rs | 5 ++- src/config/options.rs | 70 +++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 +- tests/lib.rs | 4 +-- 7 files changed, 124 insertions(+), 11 deletions(-) diff --git a/Configurations.md b/Configurations.md index 8540bc36383..0b2048958e1 100644 --- a/Configurations.md +++ b/Configurations.md @@ -2135,3 +2135,32 @@ Copyright 2018 The Rust Project Developers.`, etc.: ``` `\{`, `\}` and `\\` match literal braces / backslashes. + +## `ignore` + +Skip formatting the specified files and directories. + +- **Default value**: format every files +- **Possible values**: See an example below +- **Stable**: No + +### Example + +If you want to ignore specific files, put the following to your config file: + +```toml +[ignore] +files = [ + "src/types.rs", + "src/foo/bar.rs", +] +``` + +If you want to ignore every file under `examples/`, put the following to your config file: + +```toml +[ignore] +directories = [ + "examples", +] +``` diff --git a/src/bin/main.rs b/src/bin/main.rs index b9d413bbed0..4a49ce5a2e6 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -18,6 +18,7 @@ use std::{env, error}; use std::fs::File; use std::io::{self, Read, Write}; use std::path::{Path, PathBuf}; +use std::str::FromStr; use getopts::{Matches, Options}; @@ -25,8 +26,6 @@ use rustfmt::config::{get_toml_path, Color, Config, WriteMode}; use rustfmt::config::file_lines::FileLines; use rustfmt::{run, FileName, Input, Summary}; -use std::str::FromStr; - type FmtError = Box; type FmtResult = std::result::Result; diff --git a/src/config/config_type.rs b/src/config/config_type.rs index dc768490fbb..53a4fdb8e57 100644 --- a/src/config/config_type.rs +++ b/src/config/config_type.rs @@ -9,7 +9,7 @@ // except according to those terms. use config::file_lines::FileLines; -use config::options::WidthHeuristics; +use config::options::{IgnoreList, WidthHeuristics}; /// Trait for types that can be used in `Config`. pub trait ConfigType: Sized { @@ -54,6 +54,12 @@ impl ConfigType for WidthHeuristics { } } +impl ConfigType for IgnoreList { + fn doc_hint() -> String { + String::from("[,..]") + } +} + /// Check if we're in a nightly build. /// /// The environment variable `CFG_RELEASE_CHANNEL` is set during the rustc bootstrap @@ -176,7 +182,7 @@ macro_rules! create_config { ConfigWasSet(self) } - fn fill_from_parsed_config(mut self, parsed: PartialConfig) -> Config { + fn fill_from_parsed_config(mut self, parsed: PartialConfig, dir: &Path) -> Config { $( if let Some(val) = parsed.$i { if self.$i.3 { @@ -195,6 +201,7 @@ macro_rules! create_config { )+ self.set_heuristics(); self.set_license_template(); + self.set_ignore(dir); self } @@ -216,7 +223,7 @@ macro_rules! create_config { } } - pub fn from_toml(toml: &str) -> Result { + pub 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(); @@ -236,7 +243,7 @@ macro_rules! create_config { if !err.is_empty() { eprint!("{}", err); } - Ok(Config::default().fill_from_parsed_config(parsed_config)) + Ok(Config::default().fill_from_parsed_config(parsed_config, dir: &Path)) } Err(e) => { err.push_str("Error: Decoding config file failed:\n"); @@ -300,7 +307,8 @@ macro_rules! create_config { let mut file = File::open(&file_path)?; let mut toml = String::new(); file.read_to_string(&mut toml)?; - Config::from_toml(&toml).map_err(|err| Error::new(ErrorKind::InvalidData, err)) + Config::from_toml(&toml, file_path.parent().unwrap()) + .map_err(|err| Error::new(ErrorKind::InvalidData, err)) } /// Resolve the config for input in `dir`. @@ -401,6 +409,10 @@ macro_rules! create_config { } } } + + fn set_ignore(&mut self, dir: &Path) { + self.ignore.2.add_prefix(dir); + } } // Template for the default configuration diff --git a/src/config/mod.rs b/src/config/mod.rs index 0d4ec8557d3..301b9e06b01 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -141,6 +141,8 @@ create_config! { "Report all, none or unnumbered occurrences of TODO in source file comments"; report_fixme: ReportTactic, ReportTactic::Never, false, "Report all, none or unnumbered occurrences of FIXME in source file comments"; + ignore: IgnoreList, IgnoreList::default(), false, + "Skip formatting the specified files and directories."; // Not user-facing. verbose: bool, false, false, "Use verbose output"; @@ -208,7 +210,8 @@ mod test { #[test] fn test_was_set() { - let config = Config::from_toml("hard_tabs = true").unwrap(); + use std::path::Path; + let config = Config::from_toml("hard_tabs = true", Path::new("")).unwrap(); assert_eq!(config.was_set().hard_tabs(), true); assert_eq!(config.was_set().verbose(), false); diff --git a/src/config/options.rs b/src/config/options.rs index dcea4706ace..7a5f9785b9b 100644 --- a/src/config/options.rs +++ b/src/config/options.rs @@ -8,9 +8,14 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use syntax::codemap::FileName; + use config::config_type::ConfigType; use config::lists::*; +use std::collections::HashSet; +use std::path::{Path, PathBuf}; + /// Macro for deriving implementations of Serialize/Deserialize for enums #[macro_export] macro_rules! impl_enum_serialize_and_deserialize { @@ -244,3 +249,68 @@ impl ::std::str::FromStr for WidthHeuristics { Err("WidthHeuristics is not parsable") } } + +/// A set of directories, files and modules that rustfmt should ignore. +#[derive(Default, Deserialize, Serialize, Clone, Debug)] +pub struct IgnoreList { + directories: Option>, + files: Option>, +} + +impl IgnoreList { + fn add_prefix_inner(set: &HashSet, dir: &Path) -> HashSet { + set.iter() + .map(|s| { + if s.has_root() { + s.clone() + } else { + let mut path = PathBuf::from(dir); + path.push(s); + path + } + }) + .collect() + } + + pub fn add_prefix(&mut self, dir: &Path) { + macro add_prefix_inner_with ($($field: ident),* $(,)*) { + $(if let Some(set) = self.$field.as_mut() { + *set = IgnoreList::add_prefix_inner(set, dir); + })* + } + + add_prefix_inner_with!(directories, files); + } + + fn is_ignore_file(&self, path: &Path) -> bool { + self.files.as_ref().map_or(false, |set| set.contains(path)) + } + + fn is_under_ignore_dir(&self, path: &Path) -> bool { + if let Some(ref dirs) = self.directories { + for dir in dirs { + if path.starts_with(dir) { + return true; + } + } + } + + false + } + + pub fn skip_file(&self, file: &FileName) -> bool { + if let FileName::Real(ref path) = file { + self.is_ignore_file(path) || self.is_under_ignore_dir(path) + } else { + false + } + } +} + +impl ::std::str::FromStr for IgnoreList { + type Err = &'static str; + + fn from_str(_: &str) -> Result { + Err("IgnoreList is not parsable") + } +} diff --git a/src/lib.rs b/src/lib.rs index 9447155ee92..ebd94e1b3d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -338,7 +338,7 @@ where // nothing to distinguish the nested module contents. let skip_children = config.skip_children() || config.write_mode() == config::WriteMode::Plain; for (path, module) in modules::list_files(krate, parse_session.codemap())? { - if skip_children && path != *main_file { + if (skip_children && path != *main_file) || config.ignore().skip_file(&path) { continue; } should_emit_verbose(&path, config, || println!("Formatting {}", path)); diff --git a/tests/lib.rs b/tests/lib.rs index 9abe08ba9e0..9309941983d 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -293,7 +293,7 @@ fn format_lines_errors_are_reported() { fn format_lines_errors_are_reported_with_tabs() { let long_identifier = String::from_utf8(vec![b'a'; 97]).unwrap(); let input = Input::Text(format!("fn a() {{\n\t{}\n}}", long_identifier)); - let config = Config::from_toml("hard_tabs = true").unwrap(); + let config = Config::from_toml("hard_tabs = true", Path::new("")).unwrap(); let (error_summary, _file_map, _report) = format_input::(input, &config, None).unwrap(); assert!(error_summary.has_formatting_errors()); @@ -433,7 +433,7 @@ fn get_config(config_file: Option<&Path>) -> Config { .read_to_string(&mut def_config) .expect("Couldn't read config"); - Config::from_toml(&def_config).expect("Invalid toml") + Config::from_toml(&def_config, Path::new("tests/config/")).expect("Invalid toml") } // Reads significant comments of the form: // rustfmt-key: value