diff --git a/apps/oxlint/fixtures/overrides/directories-config.json b/apps/oxlint/fixtures/overrides/directories-config.json new file mode 100644 index 00000000000000..02c53eda8fcea4 --- /dev/null +++ b/apps/oxlint/fixtures/overrides/directories-config.json @@ -0,0 +1,19 @@ +{ + "rules": { + "no-debugger": "off" + }, + "overrides": [ + { + "files": ["lib/*.{js,ts}", "src/*"], + "rules": { + "no-debugger": "error" + } + }, + { + "files": ["**/tests/*"], + "rules": { + "no-debugger": "warn" + } + } + ] +} diff --git a/apps/oxlint/fixtures/overrides/lib/index.ts b/apps/oxlint/fixtures/overrides/lib/index.ts new file mode 100644 index 00000000000000..eab74692130a60 --- /dev/null +++ b/apps/oxlint/fixtures/overrides/lib/index.ts @@ -0,0 +1 @@ +debugger; diff --git a/apps/oxlint/fixtures/overrides/lib/tests/index.js b/apps/oxlint/fixtures/overrides/lib/tests/index.js new file mode 100644 index 00000000000000..eab74692130a60 --- /dev/null +++ b/apps/oxlint/fixtures/overrides/lib/tests/index.js @@ -0,0 +1 @@ +debugger; diff --git a/apps/oxlint/fixtures/overrides/src/oxlint.js b/apps/oxlint/fixtures/overrides/src/oxlint.js new file mode 100644 index 00000000000000..eab74692130a60 --- /dev/null +++ b/apps/oxlint/fixtures/overrides/src/oxlint.js @@ -0,0 +1 @@ +debugger; diff --git a/apps/oxlint/fixtures/overrides/src/tests/index.js b/apps/oxlint/fixtures/overrides/src/tests/index.js new file mode 100644 index 00000000000000..eab74692130a60 --- /dev/null +++ b/apps/oxlint/fixtures/overrides/src/tests/index.js @@ -0,0 +1 @@ +debugger; diff --git a/apps/oxlint/src/lint.rs b/apps/oxlint/src/lint.rs index 78e2f1b8bfb6ef..9cd252be2eba59 100644 --- a/apps/oxlint/src/lint.rs +++ b/apps/oxlint/src/lint.rs @@ -695,4 +695,13 @@ mod test { assert_eq!(result.number_of_warnings, 0); assert_eq!(result.number_of_errors, 1); } + + #[test] + fn test_overrides_directories() { + let result = + test(&["-c", "fixtures/overrides/directories-config.json", "fixtures/overrides"]); + assert_eq!(result.number_of_files, 7); + assert_eq!(result.number_of_warnings, 2); + assert_eq!(result.number_of_errors, 2); + } } diff --git a/crates/oxc_linter/src/builder.rs b/crates/oxc_linter/src/builder.rs index 873c4179dc6925..dfbe827274dd2a 100644 --- a/crates/oxc_linter/src/builder.rs +++ b/crates/oxc_linter/src/builder.rs @@ -94,9 +94,10 @@ impl LinterBuilder { categories, rules: oxlintrc_rules, overrides, + path, } = oxlintrc; - let config = LintConfig { plugins, settings, env, globals }; + let config = LintConfig { plugins, settings, env, globals, path: Some(path) }; let options = LintOptions::default(); let rules = if start_empty { FxHashSet::default() } else { Self::warn_correctness(plugins) }; diff --git a/crates/oxc_linter/src/config/flat.rs b/crates/oxc_linter/src/config/flat.rs index 1dfd48d5991e91..dea2b9129a6ac0 100644 --- a/crates/oxc_linter/src/config/flat.rs +++ b/crates/oxc_linter/src/config/flat.rs @@ -92,8 +92,24 @@ impl ConfigStore { let mut overrides_to_apply: Vec = Vec::new(); let mut hasher = FxBuildHasher.build_hasher(); + // Compute the path of the file relative to the configuration file for glob matching. Globs should match + // relative to the location of the configuration file. + // - path: /some/path/like/this/to/file.js + // - config_path: /some/path/like/.oxlintrc.json + // => relative_path: this/to/file.js + // TODO: Handle nested configuration file paths. + let relative_path = if let Some(config_path) = &self.base.config.path { + if let Some(parent) = config_path.parent() { + path.strip_prefix(parent).unwrap_or(path) + } else { + path + } + } else { + path + }; + for (id, override_config) in self.overrides.iter_enumerated() { - if override_config.files.is_match(path) { + if override_config.files.is_match(relative_path) { overrides_to_apply.push(id); id.hash(&mut hasher); } @@ -285,6 +301,7 @@ mod test { env: OxlintEnv::default(), settings: OxlintSettings::default(), globals: OxlintGlobals::default(), + path: None, }; let overrides = from_json!([{ "files": ["*.jsx", "*.tsx"], diff --git a/crates/oxc_linter/src/config/mod.rs b/crates/oxc_linter/src/config/mod.rs index cad1a00c72df1e..54f012be7b769c 100644 --- a/crates/oxc_linter/src/config/mod.rs +++ b/crates/oxc_linter/src/config/mod.rs @@ -8,6 +8,8 @@ mod plugins; mod rules; mod settings; +use std::path::PathBuf; + pub(crate) use self::flat::ResolvedLinterState; pub use self::{ env::OxlintEnv, @@ -29,6 +31,8 @@ pub(crate) struct LintConfig { pub(crate) env: OxlintEnv, /// Enabled or disabled specific global variables. pub(crate) globals: OxlintGlobals, + /// Absolute path to the configuration file (may be `None` if there is no file). + pub(crate) path: Option, } impl From for LintConfig { @@ -38,6 +42,7 @@ impl From for LintConfig { settings: config.settings, env: config.env, globals: config.globals, + path: Some(config.path), } } } @@ -58,6 +63,7 @@ mod test { let fixture_path = env::current_dir().unwrap().join("fixtures/eslint_config.json"); let config = Oxlintrc::from_file(&fixture_path).unwrap(); assert!(!config.rules.is_empty()); + assert!(config.path.ends_with("fixtures/eslint_config.json")); } #[test] diff --git a/crates/oxc_linter/src/config/overrides.rs b/crates/oxc_linter/src/config/overrides.rs index dc4ca11acfb22c..6a64222584aa07 100644 --- a/crates/oxc_linter/src/config/overrides.rs +++ b/crates/oxc_linter/src/config/overrides.rs @@ -153,6 +153,26 @@ impl JsonSchema for GlobSet { } mod test { + #[test] + fn test_globset() { + use super::*; + use serde_json::{from_value, json}; + + let config: OxlintOverride = from_value(json!({ + "files": ["*.tsx",], + })) + .unwrap(); + assert!(config.files.globs.is_match("/some/path/foo.tsx")); + assert!(!config.files.globs.is_match("/some/path/foo.ts")); + + let config: OxlintOverride = from_value(json!({ + "files": ["lib/*.ts",], + })) + .unwrap(); + assert!(config.files.globs.is_match("lib/foo.ts")); + assert!(!config.files.globs.is_match("src/foo.ts")); + } + #[test] fn test_parsing_plugins() { use super::*; diff --git a/crates/oxc_linter/src/config/oxlintrc.rs b/crates/oxc_linter/src/config/oxlintrc.rs index fc96ea1b2d41b6..1fc3f64e32055a 100644 --- a/crates/oxc_linter/src/config/oxlintrc.rs +++ b/crates/oxc_linter/src/config/oxlintrc.rs @@ -1,4 +1,4 @@ -use std::path::Path; +use std::path::{Path, PathBuf}; use oxc_diagnostics::OxcDiagnostic; use schemars::JsonSchema; @@ -85,6 +85,9 @@ pub struct Oxlintrc { /// Add, remove, or otherwise reconfigure rules for specific files or groups of files. #[serde(skip_serializing_if = "OxlintOverrides::is_empty")] pub overrides: OxlintOverrides, + /// Absolute path to the configuration file. + #[serde(skip)] + pub path: PathBuf, } impl Oxlintrc { @@ -116,10 +119,15 @@ impl Oxlintrc { OxcDiagnostic::error(format!("Failed to parse eslint config {path:?}.\n{err}")) })?; - let config = Self::deserialize(&json).map_err(|err| { + let mut config = Self::deserialize(&json).map_err(|err| { OxcDiagnostic::error(format!("Failed to parse config with error {err:?}")) })?; + // Get absolute path from `path` + let absolute_path = path.canonicalize().unwrap_or_else(|_| path.to_path_buf()); + + config.path = absolute_path; + Ok(config) } } @@ -137,6 +145,7 @@ mod test { assert!(config.rules.is_empty()); assert_eq!(config.settings, OxlintSettings::default()); assert_eq!(config.env, OxlintEnv::default()); + assert_eq!(config.path, PathBuf::default()); } #[test]