Skip to content

Commit 2919313

Browse files
committed
feat(linter): introduce denyWarnings config options (#19926)
1 parent d197587 commit 2919313

File tree

18 files changed

+295
-11
lines changed

18 files changed

+295
-11
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"rules": {
3+
"no-debugger": "warn"
4+
},
5+
"options": {
6+
"denyWarnings": false
7+
}
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"rules": {
3+
"no-debugger": "warn"
4+
},
5+
"options": {
6+
"denyWarnings": true
7+
}
8+
}

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

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,18 @@ export interface OxlintGlobals {
312312
* Options for the linter.
313313
*/
314314
export interface OxlintOptions {
315+
/**
316+
* Ensure warnings produce a non-zero exit code.
317+
*
318+
* Equivalent to passing `--deny-warnings` on the CLI.
319+
*/
320+
denyWarnings?: boolean | null;
321+
/**
322+
* Specify a warning threshold. Exits with an error status if warnings exceed this value.
323+
*
324+
* Equivalent to passing `--max-warnings` on the CLI.
325+
*/
326+
maxWarnings?: number | null;
315327
/**
316328
* Enable rules that require type information.
317329
*
@@ -324,12 +336,6 @@ export interface OxlintOptions {
324336
* Equivalent to passing `--type-check` on the CLI.
325337
*/
326338
typeCheck?: boolean | null;
327-
/**
328-
* Specify a warning threshold. Exits with an error status if warnings exceed this value.
329-
*
330-
* Equivalent to passing `--max-warnings` on the CLI.
331-
*/
332-
maxWarnings?: number | null;
333339
}
334340
export interface OxlintOverride {
335341
/**

apps/oxlint/src/config_loader.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,12 @@ impl<'a> ConfigLoader<'a> {
387387
.push(ConfigLoadError::Diagnostic(nested_type_check_not_supported(&path)));
388388
continue;
389389
}
390+
if builder.deny_warnings().is_some() {
391+
errors.push(ConfigLoadError::Diagnostic(nested_deny_warnings_not_supported(
392+
&path,
393+
)));
394+
continue;
395+
}
390396
if builder.max_warnings().is_some() {
391397
errors.push(ConfigLoadError::Diagnostic(nested_max_warnings_not_supported(
392398
&path,
@@ -655,6 +661,14 @@ fn nested_type_check_not_supported(path: &Path) -> OxcDiagnostic {
655661
.with_help("Move `options.typeCheck` to the root configuration file.")
656662
}
657663

664+
fn nested_deny_warnings_not_supported(path: &Path) -> OxcDiagnostic {
665+
OxcDiagnostic::error(format!(
666+
"The `options.denyWarnings` option is only supported in the root config, but it was found in {}.",
667+
path.display()
668+
))
669+
.with_help("Move `options.denyWarnings` to the root configuration file.")
670+
}
671+
658672
fn nested_max_warnings_not_supported(path: &Path) -> OxcDiagnostic {
659673
OxcDiagnostic::error(format!(
660674
"The `options.maxWarnings` option is only supported in the root config, but it was found in {}.",
@@ -795,6 +809,21 @@ mod test {
795809
assert!(matches!(errors[0], ConfigLoadError::Diagnostic(_)));
796810
}
797811

812+
#[test]
813+
fn test_nested_json_config_rejects_deny_warnings() {
814+
let root_dir = tempfile::tempdir().unwrap();
815+
let nested_path = root_dir.path().join("nested/.oxlintrc.json");
816+
std::fs::create_dir_all(nested_path.parent().unwrap()).unwrap();
817+
std::fs::write(&nested_path, r#"{ "options": { "denyWarnings": true } }"#).unwrap();
818+
819+
let mut external_plugin_store = ExternalPluginStore::new(false);
820+
let mut loader = ConfigLoader::new(None, &mut external_plugin_store, &[], None);
821+
let (_configs, errors) = loader
822+
.load_discovered_with_root_dir(root_dir.path(), [DiscoveredConfig::Json(nested_path)]);
823+
assert_eq!(errors.len(), 1);
824+
assert!(matches!(errors[0], ConfigLoadError::Diagnostic(_)));
825+
}
826+
798827
#[cfg(feature = "napi")]
799828
#[test]
800829
fn test_root_oxlint_config_ts_allows_type_aware() {
@@ -895,6 +924,36 @@ mod test {
895924
assert!(matches!(errors[0], ConfigLoadError::Diagnostic(_)));
896925
}
897926

927+
#[cfg(feature = "napi")]
928+
#[test]
929+
fn test_nested_oxlint_config_ts_rejects_deny_warnings() {
930+
let root_dir = tempfile::tempdir().unwrap();
931+
let nested_path = root_dir.path().join("nested/oxlint.config.ts");
932+
std::fs::create_dir_all(nested_path.parent().unwrap()).unwrap();
933+
std::fs::write(&nested_path, "export default {};").unwrap();
934+
935+
let mut external_plugin_store = ExternalPluginStore::new(false);
936+
let mut loader = ConfigLoader::new(None, &mut external_plugin_store, &[], None);
937+
938+
let js_loader = make_js_loader(move |paths| {
939+
Ok(paths
940+
.into_iter()
941+
.map(|path| {
942+
let path = PathBuf::from(path);
943+
let mut config = make_js_config(path.clone(), None, None).config;
944+
config.options.deny_warnings = Some(true);
945+
JsConfigResult { path, config }
946+
})
947+
.collect())
948+
});
949+
loader = loader.with_js_config_loader(Some(&js_loader));
950+
951+
let (_configs, errors) = loader
952+
.load_discovered_with_root_dir(root_dir.path(), [DiscoveredConfig::Js(nested_path)]);
953+
assert_eq!(errors.len(), 1);
954+
assert!(matches!(errors[0], ConfigLoadError::Diagnostic(_)));
955+
}
956+
898957
#[cfg(feature = "napi")]
899958
#[test]
900959
fn test_nested_oxlint_config_ts_rejects_type_aware_from_extends() {

apps/oxlint/src/lint.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,7 @@ impl CliRunner {
341341
let config_store = ConfigStore::new(lint_config, nested_configs, external_plugin_store);
342342
let type_aware = self.options.type_aware || config_store.type_aware_enabled();
343343
let type_check = self.options.type_check || config_store.type_check_enabled();
344+
let deny_warnings = warning_options.deny_warnings || config_store.deny_warnings();
344345
let max_warnings = warning_options.max_warnings.or(config_store.max_warnings());
345346

346347
let report_unused_directives = match inline_config_options.report_unused_directives {
@@ -443,7 +444,7 @@ impl CliRunner {
443444

444445
if diagnostic_result.errors_count() > 0 {
445446
CliRunResult::LintFoundErrors
446-
} else if warning_options.deny_warnings && diagnostic_result.warnings_count() > 0 {
447+
} else if deny_warnings && diagnostic_result.warnings_count() > 0 {
447448
CliRunResult::LintNoWarningsAllowed
448449
} else if diagnostic_result.max_warnings_exceeded() {
449450
CliRunResult::LintMaxWarningsExceeded
@@ -1329,6 +1330,18 @@ mod test {
13291330
Tester::new().with_cwd("fixtures/linter".into()).test_and_snapshot(args);
13301331
}
13311332

1333+
#[test]
1334+
fn test_deny_warnings_via_config_file() {
1335+
let args = &["-c", "config-deny-warnings.json", "debugger.js"];
1336+
Tester::new().with_cwd("fixtures/linter".into()).test_and_snapshot(args);
1337+
}
1338+
1339+
#[test]
1340+
fn test_deny_warnings_overridden_by_cli_flag() {
1341+
let args = &["--deny-warnings", "-c", "config-deny-warnings-false.json", "debugger.js"];
1342+
Tester::new().with_cwd("fixtures/linter".into()).test_and_snapshot(args);
1343+
}
1344+
13321345
#[test]
13331346
#[cfg(not(target_endian = "big"))]
13341347
fn test_tsgolint_no_typescript_files() {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
source: apps/oxlint/src/tester.rs
3+
---
4+
##########
5+
arguments: --deny-warnings -c config-deny-warnings-false.json debugger.js
6+
working directory: fixtures/linter
7+
----------
8+
9+
! eslint(no-debugger): `debugger` statement is not allowed
10+
,-[debugger.js:1:1]
11+
1 | debugger;
12+
: ^^^^^^^^^
13+
`----
14+
help: Remove the debugger statement
15+
16+
Found 1 warning and 0 errors.
17+
Finished in <variable>ms on 1 file with 93 rules using 1 threads.
18+
----------
19+
CLI result: LintNoWarningsAllowed
20+
----------
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
source: apps/oxlint/src/tester.rs
3+
---
4+
##########
5+
arguments: -c config-deny-warnings.json debugger.js
6+
working directory: fixtures/linter
7+
----------
8+
9+
! eslint(no-debugger): `debugger` statement is not allowed
10+
,-[debugger.js:1:1]
11+
1 | debugger;
12+
: ^^^^^^^^^
13+
`----
14+
help: Remove the debugger statement
15+
16+
Found 1 warning and 0 errors.
17+
Finished in <variable>ms on 1 file with 93 rules using 1 threads.
18+
----------
19+
CLI result: LintNoWarningsAllowed
20+
----------
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
debugger;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Exit code
2+
1
3+
4+
# stdout
5+
```
6+
! eslint(no-debugger): `debugger` statement is not allowed
7+
,-[files/test.js:1:1]
8+
1 | debugger;
9+
: ^^^^^^^^^
10+
`----
11+
help: Remove the debugger statement
12+
13+
Found 1 warning and 0 errors.
14+
Finished in Xms on 1 file with 1 rules using X threads.
15+
```
16+
17+
# stderr
18+
```
19+
```
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { defineConfig } from "#oxlint";
2+
3+
const options = {
4+
denyWarnings: true,
5+
};
6+
7+
export default defineConfig({
8+
categories: { correctness: "off" },
9+
rules: {
10+
"no-debugger": "warn",
11+
},
12+
options,
13+
});

0 commit comments

Comments
 (0)