Skip to content

Commit 61bf388

Browse files
wagenetclaudeCopilotcamc314autofix-ci[bot]
authored
feat(linter): add options.reportUnusedDisableDirectives to config file (#19799)
See #15972. ## Summary - Adds `options.reportUnusedDisableDirectives` to `.oxlintrc.json`, following the same pattern as `typeAware` (#19614) and `typeCheck` (#19764) - Root-config-only — nested configs are rejected with a clear diagnostic error - CLI `--report-unused-disable-directives` / `--report-unused-disable-directives-severity` take precedence over the config file value when both are set - Schema (`npm/oxlint/configuration_schema.json`), TypeScript types (`config.generated.ts`), and website snapshots regenerated ## Test plan - [ ] Unit tests in `oxlintrc.rs`: deserialization, `is_empty`, `merge` - [ ] Unit tests in `config_store.rs`: accessor returns value / `None` by default - [ ] Unit tests in `config_loader.rs`: nested config rejection - [ ] Integration tests in `config_builder.rs`: `extends` inheritance and override precedence - [ ] `cargo test -p oxc_linter && cargo test -p oxlint` ## Note on AI usage This PR was implemented with Claude Code assistance and has been reviewed and tested by the contributor. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: wagenet <9835+wagenet@users.noreply.github.com> Co-authored-by: Cameron Clark <cameron.clark@hey.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 6ea49a0 commit 61bf388

15 files changed

+676
-3
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"options": {
3+
"reportUnusedDisableDirectives": "warn"
4+
},
5+
"rules": {
6+
"no-console": "warn",
7+
"no-debugger": "warn"
8+
}
9+
}

apps/oxlint/src-js/package/config.generated.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,14 @@ export interface OxlintGlobals {
402402
* Options for the linter.
403403
*/
404404
export interface OxlintOptions {
405+
/**
406+
* Report unused disable directives (e.g. `// oxlint-disable-line` or `// eslint-disable-line`).
407+
*
408+
* Equivalent to passing `--report-unused-disable-directives-severity` on the CLI.
409+
* CLI flags take precedence over this value when both are set.
410+
* Only supported in the root configuration file.
411+
*/
412+
reportUnusedDisableDirectives?: AllowWarnDeny | null;
405413
/**
406414
* Ensure warnings produce a non-zero exit code.
407415
*

apps/oxlint/src/config_loader.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,12 @@ impl<'a> ConfigLoader<'a> {
399399
)));
400400
continue;
401401
}
402+
if builder.report_unused_disable_directives().is_some() {
403+
errors.push(ConfigLoadError::Diagnostic(
404+
nested_report_unused_disable_directives_not_supported(&path),
405+
));
406+
continue;
407+
}
402408
}
403409

404410
let extended_paths = builder.extended_paths.clone();
@@ -677,6 +683,14 @@ fn nested_max_warnings_not_supported(path: &Path) -> OxcDiagnostic {
677683
.with_help("Move `options.maxWarnings` to the root configuration file.")
678684
}
679685

686+
fn nested_report_unused_disable_directives_not_supported(path: &Path) -> OxcDiagnostic {
687+
OxcDiagnostic::error(format!(
688+
"The `options.reportUnusedDisableDirectives` option is only supported in the root config, but it was found in {}.",
689+
path.display()
690+
))
691+
.with_help("Move `options.reportUnusedDisableDirectives` to the root configuration file.")
692+
}
693+
680694
#[cfg(test)]
681695
mod test {
682696
use std::path::{Path, PathBuf};
@@ -815,6 +829,24 @@ mod test {
815829
let nested_path = root_dir.path().join("nested/.oxlintrc.json");
816830
std::fs::create_dir_all(nested_path.parent().unwrap()).unwrap();
817831
std::fs::write(&nested_path, r#"{ "options": { "denyWarnings": true } }"#).unwrap();
832+
let mut external_plugin_store = ExternalPluginStore::new(false);
833+
let mut loader = ConfigLoader::new(None, &mut external_plugin_store, &[], None);
834+
let (_configs, errors) = loader
835+
.load_discovered_with_root_dir(root_dir.path(), [DiscoveredConfig::Json(nested_path)]);
836+
assert_eq!(errors.len(), 1);
837+
assert!(matches!(errors[0], ConfigLoadError::Diagnostic(_)));
838+
}
839+
840+
#[test]
841+
fn test_nested_json_config_rejects_report_unused_disable_directives() {
842+
let root_dir = tempfile::tempdir().unwrap();
843+
let nested_path = root_dir.path().join("nested/.oxlintrc.json");
844+
std::fs::create_dir_all(nested_path.parent().unwrap()).unwrap();
845+
std::fs::write(
846+
&nested_path,
847+
r#"{ "options": { "reportUnusedDisableDirectives": "warn" } }"#,
848+
)
849+
.unwrap();
818850

819851
let mut external_plugin_store = ExternalPluginStore::new(false);
820852
let mut loader = ConfigLoader::new(None, &mut external_plugin_store, &[], None);

apps/oxlint/src/lint.rs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,10 +344,17 @@ impl CliRunner {
344344
let deny_warnings = warning_options.deny_warnings || config_store.deny_warnings();
345345
let max_warnings = warning_options.max_warnings.or(config_store.max_warnings());
346346

347+
// Only propagate Warn/Deny; treat Allow (off) as disabling reports.
347348
let report_unused_directives = match inline_config_options.report_unused_directives {
348349
ReportUnusedDirectives::WithoutSeverity(true) => Some(AllowWarnDeny::Warn),
349-
ReportUnusedDirectives::WithSeverity(Some(severity)) => Some(severity),
350-
_ => None,
350+
ReportUnusedDirectives::WithSeverity(Some(severity)) if severity.is_warn_deny() => {
351+
Some(severity)
352+
}
353+
ReportUnusedDirectives::WithSeverity(Some(_)) => None,
354+
_ => match config_store.report_unused_disable_directives() {
355+
Some(severity) if severity.is_warn_deny() => Some(severity),
356+
_ => None,
357+
},
351358
};
352359
let (mut diagnostic_service, tx_error) = Self::get_diagnostic_service(
353360
&output_formatter,
@@ -1059,6 +1066,26 @@ mod test {
10591066
Tester::new().with_cwd("fixtures/report_unused_directives".into()).test_and_snapshot(args);
10601067
}
10611068

1069+
#[test]
1070+
fn test_report_unused_directives_from_config() {
1071+
// Verify that `reportUnusedDisableDirectives` in the config file enables reporting
1072+
// without needing a CLI flag.
1073+
let args = &["-c", ".oxlintrc-with-rudd.json"];
1074+
1075+
Tester::new().with_cwd("fixtures/report_unused_directives".into()).test_and_snapshot(args);
1076+
}
1077+
1078+
#[test]
1079+
fn test_report_unused_directives_cli_overrides_config() {
1080+
// Verify that the CLI flag takes precedence over the config file value.
1081+
// Config has `reportUnusedDisableDirectives: "warn"`, but CLI passes `off`,
1082+
// so no unused-directive diagnostics should be reported.
1083+
let args =
1084+
&["-c", ".oxlintrc-with-rudd.json", "--report-unused-disable-directives-severity=off"];
1085+
1086+
Tester::new().with_cwd("fixtures/report_unused_directives".into()).test_and_snapshot(args);
1087+
}
1088+
10621089
#[test]
10631090
fn test_nested_config() {
10641091
let args = &[];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
---
2+
source: apps/oxlint/src/tester.rs
3+
---
4+
##########
5+
arguments: -c .oxlintrc-with-rudd.json --report-unused-disable-directives-severity=off
6+
working directory: fixtures/report_unused_directives
7+
----------
8+
9+
! eslint(no-console): Unexpected console statement.
10+
,-[test-multiple-scripts.vue:7:1]
11+
6 | // eslint-disable-next-line no-debugger
12+
7 | console.log('regular script');
13+
: ^^^^^^^^^^^
14+
8 |
15+
`----
16+
help: Delete this console statement.
17+
18+
! eslint(no-debugger): `debugger` statement is not allowed
19+
,-[test-multiple-scripts.vue:10:1]
20+
9 | // eslint-disable-next-line no-console
21+
10 | debugger;
22+
: ^^^^^^^^^
23+
11 | </script>
24+
`----
25+
help: Remove the debugger statement
26+
27+
! eslint(no-console): Unexpected console statement.
28+
,-[test-multiple-scripts.vue:31:1]
29+
30 | // oxlint-disable-next-line no-debugger, no-for-loop
30+
31 | console.log("complete line");
31+
: ^^^^^^^^^^^
32+
32 |
33+
`----
34+
help: Delete this console statement.
35+
36+
! eslint(no-debugger): `debugger` statement is not allowed
37+
,-[test.astro:11:1]
38+
10 | // eslint-disable-next-line no-console
39+
11 | debugger;
40+
: ^^^^^^^^^
41+
12 | ---
42+
`----
43+
help: Remove the debugger statement
44+
45+
! eslint(no-console): Unexpected console statement.
46+
,-[test.astro:32:1]
47+
31 | // oxlint-disable-next-line no-debugger, no-for-loop
48+
32 | console.log("complete line");
49+
: ^^^^^^^^^^^
50+
33 |
51+
`----
52+
help: Delete this console statement.
53+
54+
! eslint(no-debugger): `debugger` statement is not allowed
55+
,-[test.js:10:1]
56+
9 | // eslint-disable-next-line no-console
57+
10 | debugger;
58+
: ^^^^^^^^^
59+
11 |
60+
`----
61+
help: Remove the debugger statement
62+
63+
! eslint(no-console): Unexpected console statement.
64+
,-[test.js:27:1]
65+
26 | // oxlint-disable-next-line no-debugger, no-for-loop
66+
27 | console.log("complete line");
67+
: ^^^^^^^^^^^
68+
28 |
69+
`----
70+
help: Delete this console statement.
71+
72+
! eslint(no-debugger): `debugger` statement is not allowed
73+
,-[test.svelte:11:1]
74+
10 | // eslint-disable-next-line no-console
75+
11 | debugger;
76+
: ^^^^^^^^^
77+
12 |
78+
`----
79+
help: Remove the debugger statement
80+
81+
! eslint(no-console): Unexpected console statement.
82+
,-[test.svelte:28:1]
83+
27 | // oxlint-disable-next-line no-debugger, no-for-loop
84+
28 | console.log("complete line");
85+
: ^^^^^^^^^^^
86+
29 |
87+
`----
88+
help: Delete this console statement.
89+
90+
! eslint(no-debugger): `debugger` statement is not allowed
91+
,-[test.vue:15:1]
92+
14 | // eslint-disable-next-line no-console
93+
15 | debugger;
94+
: ^^^^^^^^^
95+
16 |
96+
`----
97+
help: Remove the debugger statement
98+
99+
! eslint(no-console): Unexpected console statement.
100+
,-[test.vue:32:1]
101+
31 | // oxlint-disable-next-line no-debugger, no-for-loop
102+
32 | console.log("complete line");
103+
: ^^^^^^^^^^^
104+
33 |
105+
`----
106+
help: Delete this console statement.
107+
108+
Found 11 warnings and 0 errors.
109+
Finished in <variable>ms on 5 files with 94 rules using 1 threads.
110+
----------
111+
CLI result: LintSucceeded
112+
----------

0 commit comments

Comments
 (0)