From a45f7f237be46ae20553d34441c61cb1d01398cb Mon Sep 17 00:00:00 2001 From: scarf Date: Fri, 19 May 2023 05:55:10 +0900 Subject: [PATCH] feat(cli): top-level `exclude` field in `deno.json` (#17778) --- cli/args/config_file.rs | 211 +++++++++++++++++++++++++++----- cli/schemas/config-file.v1.json | 7 ++ 2 files changed, 188 insertions(+), 30 deletions(-) diff --git a/cli/args/config_file.rs b/cli/args/config_file.rs index 2855199b97..8b52d7825e 100644 --- a/cli/args/config_file.rs +++ b/cli/args/config_file.rs @@ -348,6 +348,13 @@ impl FilesConfig { self.include.is_empty() || self.include.iter().any(|i| file_path.starts_with(i)) } + + fn extend(self, rhs: Self) -> Self { + Self { + include: [self.include, rhs.include].concat(), + exclude: [self.exclude, rhs.exclude].concat(), + } + } } /// Choose between flat and nested files configuration. @@ -428,6 +435,13 @@ pub struct LintConfig { pub report: Option, } +impl LintConfig { + pub fn with_files(self, files: FilesConfig) -> Self { + let files = self.files.extend(files); + Self { files, ..self } + } +} + #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)] #[serde(deny_unknown_fields, rename_all = "camelCase")] pub enum ProseWrap { @@ -548,6 +562,13 @@ pub struct FmtConfig { pub files: FilesConfig, } +impl FmtConfig { + pub fn with_files(self, files: FilesConfig) -> Self { + let files = self.files.extend(files); + Self { files, ..self } + } +} + /// `test` config representation for serde /// /// fields `include` and `exclude` are expanded from [SerializedFilesConfig]. @@ -580,6 +601,13 @@ pub struct TestConfig { pub files: FilesConfig, } +impl TestConfig { + pub fn with_files(self, files: FilesConfig) -> Self { + let files = self.files.extend(files); + Self { files } + } +} + /// `bench` config representation for serde /// /// fields `include` and `exclude` are expanded from [SerializedFilesConfig]. @@ -612,6 +640,13 @@ pub struct BenchConfig { pub files: FilesConfig, } +impl BenchConfig { + pub fn with_files(self, files: FilesConfig) -> Self { + let files = self.files.extend(files); + Self { files } + } +} + #[derive(Clone, Debug, Deserialize, PartialEq)] #[serde(untagged)] pub enum LockConfig { @@ -632,6 +667,7 @@ pub struct ConfigFileJson { pub test: Option, pub bench: Option, pub lock: Option, + pub exclude: Option, } #[derive(Clone, Debug)] @@ -844,44 +880,105 @@ impl ConfigFile { self.json.imports.is_some() || self.json.scopes.is_some() } - pub fn to_fmt_config(&self) -> Result, AnyError> { - if let Some(config) = self.json.fmt.clone() { - let fmt_config: SerializedFmtConfig = serde_json::from_value(config) - .context("Failed to parse \"fmt\" configuration")?; - Ok(Some(fmt_config.into_resolved(&self.specifier)?)) + pub fn to_files_config(&self) -> Result, AnyError> { + let exclude: Vec = if let Some(exclude) = self.json.exclude.clone() + { + serde_json::from_value(exclude) + .context("Failed to parse \"exclude\" configuration")? } else { - Ok(None) + Vec::new() + }; + + let raw_files_config = SerializedFilesConfig { + exclude, + ..Default::default() + }; + Ok(Some(raw_files_config.into_resolved(&self.specifier)?)) + } + + pub fn to_fmt_config(&self) -> Result, AnyError> { + let files_config = self.to_files_config()?; + let fmt_config = match self.json.fmt.clone() { + Some(config) => { + let fmt_config: SerializedFmtConfig = serde_json::from_value(config) + .context("Failed to parse \"fmt\" configuration")?; + Some(fmt_config.into_resolved(&self.specifier)?) + } + None => None, + }; + + if files_config.is_none() && fmt_config.is_none() { + return Ok(None); } + + let fmt_config = fmt_config.unwrap_or_default(); + let files_config = files_config.unwrap_or_default(); + + Ok(Some(fmt_config.with_files(files_config))) } pub fn to_lint_config(&self) -> Result, AnyError> { - if let Some(config) = self.json.lint.clone() { - let lint_config: SerializedLintConfig = serde_json::from_value(config) - .context("Failed to parse \"lint\" configuration")?; - Ok(Some(lint_config.into_resolved(&self.specifier)?)) - } else { - Ok(None) + let files_config = self.to_files_config()?; + let lint_config = match self.json.lint.clone() { + Some(config) => { + let lint_config: SerializedLintConfig = serde_json::from_value(config) + .context("Failed to parse \"lint\" configuration")?; + Some(lint_config.into_resolved(&self.specifier)?) + } + None => None, + }; + + if files_config.is_none() && lint_config.is_none() { + return Ok(None); } + + let lint_config = lint_config.unwrap_or_default(); + let files_config = files_config.unwrap_or_default(); + + Ok(Some(lint_config.with_files(files_config))) } pub fn to_test_config(&self) -> Result, AnyError> { - if let Some(config) = self.json.test.clone() { - let test_config: SerializedTestConfig = serde_json::from_value(config) - .context("Failed to parse \"test\" configuration")?; - Ok(Some(test_config.into_resolved(&self.specifier)?)) - } else { - Ok(None) + let files_config = self.to_files_config()?; + let test_config = match self.json.test.clone() { + Some(config) => { + let test_config: SerializedTestConfig = serde_json::from_value(config) + .context("Failed to parse \"test\" configuration")?; + Some(test_config.into_resolved(&self.specifier)?) + } + None => None, + }; + + if files_config.is_none() && test_config.is_none() { + return Ok(None); } + + let test_config = test_config.unwrap_or_default(); + let files_config = files_config.unwrap_or_default(); + + Ok(Some(test_config.with_files(files_config))) } pub fn to_bench_config(&self) -> Result, AnyError> { - if let Some(config) = self.json.bench.clone() { - let bench_config: SerializedBenchConfig = serde_json::from_value(config) - .context("Failed to parse \"bench\" configuration")?; - Ok(Some(bench_config.into_resolved(&self.specifier)?)) - } else { - Ok(None) + let files_config = self.to_files_config()?; + let bench_config = match self.json.bench.clone() { + Some(config) => { + let bench_config: SerializedBenchConfig = + serde_json::from_value(config) + .context("Failed to parse \"bench\" configuration")?; + Some(bench_config.into_resolved(&self.specifier)?) + } + None => None, + }; + + if files_config.is_none() && bench_config.is_none() { + return Ok(None); } + + let bench_config = bench_config.unwrap_or_default(); + let files_config = files_config.unwrap_or_default(); + + Ok(Some(bench_config.with_files(files_config))) } /// Return any tasks that are defined in the configuration file as a sequence @@ -1223,8 +1320,7 @@ mod tests { let config_dir = ModuleSpecifier::parse("file:///deno/").unwrap(); let config_specifier = config_dir.join("tsconfig.json").unwrap(); let config_file = ConfigFile::new(config_text, &config_specifier).unwrap(); - let (options_value, ignored) = - config_file.to_compiler_options().expect("error parsing"); + let (options_value, ignored) = config_file.to_compiler_options().unwrap(); assert!(options_value.is_object()); let options = options_value.as_object().unwrap(); assert!(options.contains_key("strict")); @@ -1409,8 +1505,7 @@ mod tests { let config_specifier = ModuleSpecifier::parse("file:///deno/tsconfig.json").unwrap(); let config_file = ConfigFile::new(config_text, &config_specifier).unwrap(); - let (options_value, _) = - config_file.to_compiler_options().expect("error parsing"); + let (options_value, _) = config_file.to_compiler_options().unwrap(); assert!(options_value.is_object()); } @@ -1420,11 +1515,67 @@ mod tests { let config_specifier = ModuleSpecifier::parse("file:///deno/tsconfig.json").unwrap(); let config_file = ConfigFile::new(config_text, &config_specifier).unwrap(); - let (options_value, _) = - config_file.to_compiler_options().expect("error parsing"); + let (options_value, _) = config_file.to_compiler_options().unwrap(); assert!(options_value.is_object()); } + #[test] + fn test_parse_config_with_global_files() { + let config_text = r#"{ + "exclude": ["foo/"], + "test": { + "exclude": ["npm/"], + }, + "bench": {} + }"#; + let config_specifier = + ModuleSpecifier::parse("file:///deno/tsconfig.json").unwrap(); + let config_file = ConfigFile::new(config_text, &config_specifier).unwrap(); + + let (options_value, _) = config_file.to_compiler_options().unwrap(); + assert!(options_value.is_object()); + + let test_config = config_file.to_test_config().unwrap().unwrap(); + assert_eq!(test_config.files.include, Vec::::new()); + assert_eq!( + test_config.files.exclude, + vec![PathBuf::from("/deno/npm/"), PathBuf::from("/deno/foo/")] + ); + + let bench_config = config_file.to_bench_config().unwrap().unwrap(); + assert_eq!( + bench_config.files.exclude, + vec![PathBuf::from("/deno/foo/")] + ); + } + + #[test] + fn test_parse_config_with_global_files_only() { + let config_text = r#"{ + "exclude": ["npm/"] + }"#; + let config_specifier = + ModuleSpecifier::parse("file:///deno/tsconfig.json").unwrap(); + let config_file = ConfigFile::new(config_text, &config_specifier).unwrap(); + + let (options_value, _) = config_file.to_compiler_options().unwrap(); + assert!(options_value.is_object()); + + let empty_include = Vec::::new(); + + let files_config = config_file.to_files_config().unwrap().unwrap(); + assert_eq!(files_config.include, empty_include); + assert_eq!(files_config.exclude, vec![PathBuf::from("/deno/npm/")]); + + let lint_config = config_file.to_lint_config().unwrap().unwrap(); + assert_eq!(lint_config.files.include, empty_include); + assert_eq!(lint_config.files.exclude, vec![PathBuf::from("/deno/npm/")]); + + let fmt_config = config_file.to_fmt_config().unwrap().unwrap(); + assert_eq!(fmt_config.files.include, empty_include); + assert_eq!(fmt_config.files.exclude, vec![PathBuf::from("/deno/npm/")],); + } + #[test] fn test_parse_config_with_invalid_file() { let config_text = "{foo:bar}"; diff --git a/cli/schemas/config-file.v1.json b/cli/schemas/config-file.v1.json index 7978a25977..0006997677 100644 --- a/cli/schemas/config-file.v1.json +++ b/cli/schemas/config-file.v1.json @@ -223,6 +223,13 @@ } } }, + "exclude": { + "type": "array", + "description": "List of files or directories that will be ignored by all other configurations.", + "items": { + "type": "string" + } + }, "lint": { "description": "Configuration for linter", "type": "object",