diff --git a/src/config/config_type.rs b/src/config/config_type.rs index 40da2986808..7c4298dcf1c 100644 --- a/src/config/config_type.rs +++ b/src/config/config_type.rs @@ -141,7 +141,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 { @@ -160,6 +160,7 @@ macro_rules! create_config { )+ self.set_heuristics(); self.set_license_template(); + self.set_ignore(dir); self } @@ -286,6 +287,9 @@ macro_rules! create_config { } } + fn set_ignore(&mut self, dir: &Path) { + self.ignore.2.add_prefix(dir); + } /// Returns `true` if the config key was explicitly set and is the default value. pub fn is_default(&self, key: &str) -> bool { diff --git a/src/config/mod.rs b/src/config/mod.rs index 839e9bb6e21..c0221e93e02 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -190,7 +190,8 @@ impl 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)) } /// Resolves the config for input in `dir`. @@ -252,7 +253,7 @@ impl Config { } } - pub(crate) fn from_toml(toml: &str) -> 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))?; @@ -271,7 +272,7 @@ impl 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)) } Err(e) => { err.push_str("Error: Decoding config file failed:\n"); @@ -425,7 +426,7 @@ mod test { #[test] fn test_was_set() { - let config = Config::from_toml("hard_tabs = true").unwrap(); + 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 bd8c5056854..bded90b8693 100644 --- a/src/config/options.rs +++ b/src/config/options.rs @@ -1,7 +1,9 @@ use std::collections::{hash_set, HashSet}; +use std::fmt; use std::path::{Path, PathBuf}; use atty; +use serde::de::{Deserialize, Deserializer, SeqAccess, Visitor}; use crate::config::config_type::ConfigType; use crate::config::lists::*; @@ -396,33 +398,63 @@ impl Default for EmitMode { } /// A set of directories, files and modules that rustfmt should ignore. -#[derive(Default, Deserialize, Serialize, Clone, Debug, PartialEq)] -pub struct IgnoreList(HashSet); +#[derive(Default, Serialize, Clone, Debug, PartialEq)] +pub struct IgnoreList { + /// A set of path specified in rustfmt.toml. + #[serde(flatten)] + path_set: HashSet, + /// A path to rustfmt.toml. + #[serde(skip_serializing)] + rustfmt_toml_path: PathBuf, +} + +impl<'de> Deserialize<'de> for IgnoreList { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct HashSetVisitor; + impl<'v> Visitor<'v> for HashSetVisitor { + type Value = HashSet; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a sequence of path") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'v>, + { + let mut path_set = HashSet::new(); + while let Some(elem) = seq.next_element()? { + path_set.insert(elem); + } + Ok(path_set) + } + } + Ok(IgnoreList { + path_set: deserializer.deserialize_seq(HashSetVisitor)?, + rustfmt_toml_path: PathBuf::new(), + }) + } +} impl<'a> IntoIterator for &'a IgnoreList { type Item = &'a PathBuf; type IntoIter = hash_set::Iter<'a, PathBuf>; fn into_iter(self) -> Self::IntoIter { - self.0.iter() + self.path_set.iter() } } impl IgnoreList { pub fn add_prefix(&mut self, dir: &Path) { - self.0 = self - .0 - .iter() - .map(|s| { - if s.has_root() { - s.clone() - } else { - let mut path = PathBuf::from(dir); - path.push(s); - path - } - }) - .collect(); + self.rustfmt_toml_path = dir.to_path_buf(); + } + + pub fn rustfmt_toml_path(&self) -> &Path { + &self.rustfmt_toml_path } } diff --git a/src/formatting.rs b/src/formatting.rs index 47ff9021c97..9776359bef3 100644 --- a/src/formatting.rs +++ b/src/formatting.rs @@ -67,6 +67,14 @@ fn format_project( let main_file = input.file_name(); let input_is_stdin = main_file == FileName::Stdin; + let ignore_path_set = match IgnorePathSet::from_ignore_list(&config.ignore()) { + Ok(set) => set, + Err(e) => return Err(ErrorKind::InvalidGlobPattern(e)), + }; + if config.skip_children() && ignore_path_set.is_match(&main_file) { + return Ok(FormatReport::new()); + } + // Parse the crate. let source_map = Rc::new(SourceMap::new(FilePathMapping::empty())); let mut parse_session = make_parse_sess(source_map.clone(), config); @@ -91,11 +99,6 @@ fn format_project( parse_session.span_diagnostic = Handler::with_emitter(true, None, silent_emitter); let mut context = FormatContext::new(&krate, report, parse_session, config, handler); - let ignore_path_set = match IgnorePathSet::from_ignore_list(&config.ignore()) { - Ok(set) => set, - Err(e) => return Err(ErrorKind::InvalidGlobPattern(e)), - }; - let files = modules::ModResolver::new( context.parse_session.source_map(), directory_ownership.unwrap_or(parse::DirectoryOwnership::UnownedViaMod(false)), diff --git a/src/ignore_path.rs b/src/ignore_path.rs index 6299aedf4a7..844ea427f49 100644 --- a/src/ignore_path.rs +++ b/src/ignore_path.rs @@ -1,5 +1,4 @@ use ignore::{self, gitignore}; -use std::path::PathBuf; use crate::config::{FileName, IgnoreList}; @@ -9,7 +8,7 @@ pub struct IgnorePathSet { impl IgnorePathSet { pub fn from_ignore_list(ignore_list: &IgnoreList) -> Result { - let mut ignore_builder = gitignore::GitignoreBuilder::new(PathBuf::from("")); + let mut ignore_builder = gitignore::GitignoreBuilder::new(ignore_list.rustfmt_toml_path()); for ignore_path in ignore_list { ignore_builder.add_line(None, ignore_path.to_str().unwrap())?; @@ -33,16 +32,19 @@ impl IgnorePathSet { #[cfg(test)] mod test { + use std::path::{Path, PathBuf}; + use crate::config::{Config, FileName}; use crate::ignore_path::IgnorePathSet; - use std::path::PathBuf; #[test] fn test_ignore_path_set() { match option_env!("CFG_RELEASE_CHANNEL") { // this test requires nightly None | Some("nightly") => { - let config = Config::from_toml(r#"ignore = ["foo.rs", "bar_dir/*"]"#).unwrap(); + let config = + Config::from_toml(r#"ignore = ["foo.rs", "bar_dir/*"]"#, Path::new("")) + .unwrap(); let ignore_path_set = IgnorePathSet::from_ignore_list(&config.ignore()).unwrap(); assert!(ignore_path_set.is_match(&FileName::Real(PathBuf::from("src/foo.rs")))); diff --git a/src/test/mod.rs b/src/test/mod.rs index c084742a217..6766ba2af54 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -564,7 +564,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` into a hash map.