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 {