diff --git a/apps/oxlint/fixtures/extends_config/overrides/.oxlintrc.json b/apps/oxlint/fixtures/extends_config/overrides/.oxlintrc.json new file mode 100644 index 0000000000000..cb4ff12307233 --- /dev/null +++ b/apps/oxlint/fixtures/extends_config/overrides/.oxlintrc.json @@ -0,0 +1,6 @@ +{ + "extends": ["jsx.json", "./typescript.json"], + "rules": { + "no-unused-vars": "off" + } +} diff --git a/apps/oxlint/fixtures/extends_config/overrides/jsx.json b/apps/oxlint/fixtures/extends_config/overrides/jsx.json new file mode 100644 index 0000000000000..0a20cfab61de2 --- /dev/null +++ b/apps/oxlint/fixtures/extends_config/overrides/jsx.json @@ -0,0 +1,11 @@ +{ + "plugins": ["jsx-a11y"], + "overrides": [ + { + "files": ["*.{jsx,tsx}"], + "rules": { + "jsx-a11y/anchor-ambiguous-text": "error" + } + } + ] +} diff --git a/apps/oxlint/fixtures/extends_config/overrides/test.ts b/apps/oxlint/fixtures/extends_config/overrides/test.ts new file mode 100644 index 0000000000000..47f1b9a1591dd --- /dev/null +++ b/apps/oxlint/fixtures/extends_config/overrides/test.ts @@ -0,0 +1 @@ +const x: any = 3; diff --git a/apps/oxlint/fixtures/extends_config/overrides/test.tsx b/apps/oxlint/fixtures/extends_config/overrides/test.tsx new file mode 100644 index 0000000000000..0129f4e1ce044 --- /dev/null +++ b/apps/oxlint/fixtures/extends_config/overrides/test.tsx @@ -0,0 +1,3 @@ +function component(): any { + return click here; +} diff --git a/apps/oxlint/fixtures/extends_config/overrides/typescript.json b/apps/oxlint/fixtures/extends_config/overrides/typescript.json new file mode 100644 index 0000000000000..57918a285d238 --- /dev/null +++ b/apps/oxlint/fixtures/extends_config/overrides/typescript.json @@ -0,0 +1,11 @@ +{ + "overrides": [ + { + "files": ["*.{ts,tsx}"], + "plugins": ["typescript"], + "rules": { + "@typescript-eslint/no-explicit-any": "error" + } + } + ] +} diff --git a/apps/oxlint/fixtures/extends_config/overrides_same_directory/.oxlintrc.json b/apps/oxlint/fixtures/extends_config/overrides_same_directory/.oxlintrc.json new file mode 100644 index 0000000000000..fd5c03a8d112e --- /dev/null +++ b/apps/oxlint/fixtures/extends_config/overrides_same_directory/.oxlintrc.json @@ -0,0 +1,11 @@ +{ + "rules": { + "no-debugger": "error" + }, + "overrides": [ + { + "files": ["config/*.{ts,js}"], + "rules": { "no-debugger": "off" } + } + ] +} diff --git a/apps/oxlint/fixtures/extends_config/overrides_same_directory/config/.oxlintrc.json b/apps/oxlint/fixtures/extends_config/overrides_same_directory/config/.oxlintrc.json new file mode 100644 index 0000000000000..7bb5320a89c73 --- /dev/null +++ b/apps/oxlint/fixtures/extends_config/overrides_same_directory/config/.oxlintrc.json @@ -0,0 +1,4 @@ +{ + "extends": ["../.oxlintrc.json"], + "rules": { "no-debugger": "warn" } // This should take priority over the overrides in the parent config +} diff --git a/apps/oxlint/fixtures/extends_config/overrides_same_directory/config/test.js b/apps/oxlint/fixtures/extends_config/overrides_same_directory/config/test.js new file mode 100644 index 0000000000000..eab74692130a6 --- /dev/null +++ b/apps/oxlint/fixtures/extends_config/overrides_same_directory/config/test.js @@ -0,0 +1 @@ +debugger; diff --git a/apps/oxlint/src/lint.rs b/apps/oxlint/src/lint.rs index 9c75325d25e65..41fd0f83e5944 100644 --- a/apps/oxlint/src/lint.rs +++ b/apps/oxlint/src/lint.rs @@ -1029,4 +1029,15 @@ mod test { let args = &["--config", "relative_paths/extends_extends_config.json", "console.js"]; Tester::new().with_cwd("fixtures/extends_config".into()).test_and_snapshot(args); } + + #[test] + fn test_extends_overrides() { + // Check that using a config with overrides works as expected + let args = &["--experimental-nested-config", "overrides"]; + Tester::new().with_cwd("fixtures/extends_config".into()).test_and_snapshot(args); + + // Check that using a config which extends a config with overrides works as expected + let args = &["--experimental-nested-config", "overrides_same_directory"]; + Tester::new().with_cwd("fixtures/extends_config".into()).test_and_snapshot(args); + } } diff --git a/apps/oxlint/src/snapshots/fixtures__extends_config_--experimental-nested-config overrides@oxlint.snap b/apps/oxlint/src/snapshots/fixtures__extends_config_--experimental-nested-config overrides@oxlint.snap new file mode 100644 index 0000000000000..07eca6579ce6d --- /dev/null +++ b/apps/oxlint/src/snapshots/fixtures__extends_config_--experimental-nested-config overrides@oxlint.snap @@ -0,0 +1,36 @@ +--- +source: apps/oxlint/src/tester.rs +--- +########## +arguments: --experimental-nested-config overrides +working directory: fixtures/extends_config +---------- + + x ]8;;https://oxc.rs/docs/guide/usage/linter/rules/typescript/no-explicit-any.html\typescript-eslint(no-explicit-any)]8;;\: Unexpected any. Specify a different type. + ,-[overrides/test.ts:1:10] + 1 | const x: any = 3; + : ^^^ + `---- + help: Use `unknown` instead, this will force you to explicitly, and safely, assert the type is correct. + + x ]8;;https://oxc.rs/docs/guide/usage/linter/rules/typescript/no-explicit-any.html\typescript-eslint(no-explicit-any)]8;;\: Unexpected any. Specify a different type. + ,-[overrides/test.tsx:1:23] + 1 | function component(): any { + : ^^^ + 2 | return click here; + `---- + help: Use `unknown` instead, this will force you to explicitly, and safely, assert the type is correct. + + x ]8;;https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/anchor-ambiguous-text.html\eslint-plugin-jsx-a11y(anchor-ambiguous-text)]8;;\: Unexpected ambagious anchor link text. + ,-[overrides/test.tsx:2:10] + 1 | function component(): any { + 2 | return click here; + : ^^^^^^^^^^^^^^^^^ + 3 | } + `---- + +Found 0 warnings and 3 errors. +Finished in ms on 2 files with 100 rules using 1 threads. +---------- +CLI result: LintFoundErrors +---------- diff --git a/apps/oxlint/src/snapshots/fixtures__extends_config_--experimental-nested-config overrides_same_directory@oxlint.snap b/apps/oxlint/src/snapshots/fixtures__extends_config_--experimental-nested-config overrides_same_directory@oxlint.snap new file mode 100644 index 0000000000000..c23916a3c30ec --- /dev/null +++ b/apps/oxlint/src/snapshots/fixtures__extends_config_--experimental-nested-config overrides_same_directory@oxlint.snap @@ -0,0 +1,20 @@ +--- +source: apps/oxlint/src/tester.rs +--- +########## +arguments: --experimental-nested-config overrides_same_directory +working directory: fixtures/extends_config +---------- + + ! ]8;;https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-debugger.html\eslint(no-debugger)]8;;\: `debugger` statement is not allowed + ,-[overrides_same_directory/config/test.js:1:1] + 1 | debugger; + : ^^^^^^^^^ + `---- + help: Delete this code. + +Found 1 warning and 0 errors. +Finished in ms on 1 file with 100 rules using 1 threads. +---------- +CLI result: LintSucceeded +---------- diff --git a/apps/oxlint/src/snapshots/fixtures__extends_config_overrides@oxlint.snap b/apps/oxlint/src/snapshots/fixtures__extends_config_overrides@oxlint.snap new file mode 100644 index 0000000000000..e76a4da59378e --- /dev/null +++ b/apps/oxlint/src/snapshots/fixtures__extends_config_overrides@oxlint.snap @@ -0,0 +1,30 @@ +--- +source: apps/oxlint/src/tester.rs +--- +########## +arguments: overrides +working directory: fixtures/extends_config +---------- + + ! ]8;;https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-unused-vars.html\eslint(no-unused-vars)]8;;\: Variable 'x' is declared but never used. Unused variables should start with a '_'. + ,-[overrides/test.ts:1:7] + 1 | const x: any = 3; + : | + : `-- 'x' is declared here + `---- + help: Consider removing this declaration. + + ! ]8;;https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-unused-vars.html\eslint(no-unused-vars)]8;;\: Function 'component' is declared but never used. + ,-[overrides/test.tsx:1:10] + 1 | function component(): any { + : ^^^^|^^^^ + : `-- 'component' is declared here + 2 | return click here; + `---- + help: Consider removing this declaration. + +Found 2 warnings and 0 errors. +Finished in ms on 2 files with 100 rules using 1 threads. +---------- +CLI result: LintSucceeded +---------- diff --git a/crates/oxc_linter/src/config/config_builder.rs b/crates/oxc_linter/src/config/config_builder.rs index c1842be3feff0..0d98aa0ab680f 100644 --- a/crates/oxc_linter/src/config/config_builder.rs +++ b/crates/oxc_linter/src/config/config_builder.rs @@ -10,7 +10,10 @@ use rustc_hash::FxHashSet; use crate::{ AllowWarnDeny, LintConfig, LintFilter, LintFilterKind, Oxlintrc, RuleCategory, RuleEnum, RuleWithSeverity, - config::{ConfigStore, ESLintRule, LintPlugins, OxlintOverrides, OxlintRules}, + config::{ + ConfigStore, ESLintRule, LintPlugins, OxlintOverrides, OxlintRules, + overrides::OxlintOverride, + }, rules::RULES, }; @@ -135,6 +138,11 @@ impl ConfigStoreBuilder { let rules = std::mem::take(&mut extended_config_store.rules); builder = builder.with_rules(rules); builder = builder.and_plugins(extended_config_store.plugins(), true); + if !extended_config_store.overrides.is_empty() { + let overrides = + std::mem::take(&mut extended_config_store.overrides); + builder = builder.with_overrides(overrides); + } } Err(err) => { return Err(ConfigBuilderError::InvalidConfigFile { @@ -201,6 +209,12 @@ impl ConfigStoreBuilder { self } + /// Appends an override to the end of the current list of overrides. + pub fn with_overrides>(mut self, overrides: O) -> Self { + self.overrides.extend(overrides); + self + } + pub fn with_filters>(mut self, filters: I) -> Self { for filter in filters { self = self.with_filter(filter); diff --git a/crates/oxc_linter/src/config/overrides.rs b/crates/oxc_linter/src/config/overrides.rs index 222c9e7104397..a8abf779f12ff 100644 --- a/crates/oxc_linter/src/config/overrides.rs +++ b/crates/oxc_linter/src/config/overrides.rs @@ -1,4 +1,8 @@ -use std::{borrow::Cow, ops::Deref, path::Path}; +use std::{ + borrow::Cow, + ops::{Deref, DerefMut}, + path::Path, +}; use nonmax::NonMaxU32; use schemars::{JsonSchema, r#gen, schema::Schema}; @@ -36,6 +40,21 @@ impl Deref for OxlintOverrides { } } +impl DerefMut for OxlintOverrides { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl IntoIterator for OxlintOverrides { + type Item = OxlintOverride; + type IntoIter = as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + impl OxlintOverrides { #[inline] pub fn empty() -> Self {