Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions apps/oxlint/fixtures/linter/config-max-warnings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"rules": {
"no-debugger": "warn"
},
"options": {
"maxWarnings": 0
}
}
6 changes: 6 additions & 0 deletions apps/oxlint/src-js/package/config.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,12 @@ export interface OxlintOptions {
* Equivalent to passing `--type-check` on the CLI.
*/
typeCheck?: boolean | null;
/**
* Specify a warning threshold. Exits with an error status if warnings exceed this value.
*
* Equivalent to passing `--max-warnings` on the CLI.
*/
maxWarnings?: number | null;
}
export interface OxlintOverride {
/**
Expand Down
14 changes: 14 additions & 0 deletions apps/oxlint/src/config_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,12 @@ impl<'a> ConfigLoader<'a> {
.push(ConfigLoadError::Diagnostic(nested_type_check_not_supported(&path)));
continue;
}
if builder.max_warnings().is_some() {
errors.push(ConfigLoadError::Diagnostic(nested_max_warnings_not_supported(
&path,
)));
continue;
}
}

let extended_paths = builder.extended_paths.clone();
Expand Down Expand Up @@ -649,6 +655,14 @@ fn nested_type_check_not_supported(path: &Path) -> OxcDiagnostic {
.with_help("Move `options.typeCheck` to the root configuration file.")
}

fn nested_max_warnings_not_supported(path: &Path) -> OxcDiagnostic {
OxcDiagnostic::error(format!(
"The `options.maxWarnings` option is only supported in the root config, but it was found in {}.",
path.display()
))
.with_help("Move `options.maxWarnings` to the root configuration file.")
}

#[cfg(test)]
mod test {
use std::path::{Path, PathBuf};
Expand Down
32 changes: 25 additions & 7 deletions apps/oxlint/src/lint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,17 +338,22 @@ impl CliRunner {
let mut options =
LintServiceOptions::new(self.cwd.clone()).with_cross_module(use_cross_module);

let config_store = ConfigStore::new(lint_config, nested_configs, external_plugin_store);
let type_aware = self.options.type_aware || config_store.type_aware_enabled();
let type_check = self.options.type_check || config_store.type_check_enabled();
let max_warnings = warning_options.max_warnings.or(config_store.max_warnings());

let report_unused_directives = match inline_config_options.report_unused_directives {
ReportUnusedDirectives::WithoutSeverity(true) => Some(AllowWarnDeny::Warn),
ReportUnusedDirectives::WithSeverity(Some(severity)) => Some(severity),
_ => None,
};
let (mut diagnostic_service, tx_error) =
Self::get_diagnostic_service(&output_formatter, &warning_options, &misc_options);

let config_store = ConfigStore::new(lint_config, nested_configs, external_plugin_store);
let type_aware = self.options.type_aware || config_store.type_aware_enabled();
let type_check = self.options.type_check || config_store.type_check_enabled();
let (mut diagnostic_service, tx_error) = Self::get_diagnostic_service(
&output_formatter,
&warning_options,
&misc_options,
max_warnings,
);

// Send JS plugins config to JS side
if let Some(external_linter) = &external_linter {
Expand Down Expand Up @@ -466,13 +471,14 @@ impl CliRunner {
reporter: &OutputFormatter,
warning_options: &WarningOptions,
misc_options: &MiscOptions,
max_warnings: Option<usize>,
) -> (DiagnosticService, DiagnosticSender) {
let (service, sender) = DiagnosticService::new(reporter.get_diagnostic_reporter());
(
service
.with_quiet(warning_options.quiet)
.with_silent(misc_options.silent)
.with_max_warnings(warning_options.max_warnings),
.with_max_warnings(max_warnings),
sender,
)
}
Expand Down Expand Up @@ -1311,6 +1317,18 @@ mod test {
Tester::new().with_cwd("fixtures/tsgolint_type_error".into()).test_and_snapshot(args);
}

#[test]
fn test_max_warnings_via_config_file() {
let args = &["-c", "config-max-warnings.json", "debugger.js"];
Tester::new().with_cwd("fixtures/linter".into()).test_and_snapshot(args);
}

#[test]
fn test_max_warnings_overridden_by_cli_flag() {
let args = &["--max-warnings", "1", "-c", "config-max-warnings.json", "debugger.js"];
Tester::new().with_cwd("fixtures/linter".into()).test_and_snapshot(args);
}

#[test]
#[cfg(not(target_endian = "big"))]
fn test_tsgolint_no_typescript_files() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
source: apps/oxlint/src/tester.rs
assertion_line: 134
---
##########
arguments: --max-warnings 1 -c config-max-warnings.json debugger.js
working directory: fixtures/linter
----------

! eslint(no-debugger): `debugger` statement is not allowed
,-[debugger.js:1:1]
1 | debugger;
: ^^^^^^^^^
`----
help: Remove the debugger statement

Found 1 warning and 0 errors.
Finished in <variable>ms on 1 file with 93 rules using 1 threads.
----------
CLI result: LintSucceeded
----------
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
source: apps/oxlint/src/tester.rs
assertion_line: 134
---
##########
arguments: -c config-max-warnings.json debugger.js
working directory: fixtures/linter
----------

! eslint(no-debugger): `debugger` statement is not allowed
,-[debugger.js:1:1]
1 | debugger;
: ^^^^^^^^^
`----
help: Remove the debugger statement

Found 1 warning and 0 errors.
Exceeded maximum number of warnings. Found 1.
Finished in <variable>ms on 1 file with 93 rules using 1 threads.
----------
CLI result: LintMaxWarningsExceeded
----------
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
debugger;
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Exit code
1

# stdout
```
! eslint(no-debugger): `debugger` statement is not allowed
,-[files/test.js:1:1]
1 | debugger;
: ^^^^^^^^^
`----
help: Remove the debugger statement

Found 1 warning and 0 errors.
Exceeded maximum number of warnings. Found 1.
Finished in Xms on 1 file with 1 rules using X threads.
```

# stderr
```
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { defineConfig } from "#oxlint";

const warningLimit = 0;
const options = {
maxWarnings: warningLimit,
};

export default defineConfig({
categories: { correctness: "off" },
rules: {
"no-debugger": "warn",
},
options,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"options": {
"maxWarnings": 0
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"options": {
"maxWarnings": 10
}
}
25 changes: 25 additions & 0 deletions crates/oxc_linter/src/config/config_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,11 @@ impl ConfigStoreBuilder {
self.config.options.type_check
}

#[inline]
pub fn max_warnings(&self) -> Option<usize> {
self.config.options.max_warnings
}

#[cfg(test)]
pub(crate) fn with_rule(mut self, rule: RuleEnum, severity: AllowWarnDeny) -> Self {
self.rules.insert(rule, severity);
Expand Down Expand Up @@ -1415,6 +1420,26 @@ mod test {
r#"{ "extends": ["fixtures/extends_config/options/type_check_false.json"] }"#,
);
assert_eq!(config.base.config.options.type_check, Some(false));

let config = config_store_from_str(
r#"{ "extends": ["fixtures/extends_config/options/max_warnings_10.json"] }"#,
);
assert_eq!(config.base.config.options.max_warnings, Some(10));

let config = config_store_from_str(
r#"
{
"extends": ["fixtures/extends_config/options/max_warnings_10.json"],
"options": {"maxWarnings": 1 }
}
"#,
);
assert_eq!(config.base.config.options.max_warnings, Some(1));

let config = config_store_from_str(
r#"{ "extends": ["fixtures/extends_config/options/max_warnings_0.json"] }"#,
);
assert_eq!(config.base.config.options.max_warnings, Some(0));
}

#[test]
Expand Down
34 changes: 34 additions & 0 deletions crates/oxc_linter/src/config/config_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,11 @@ impl ConfigStore {
self.base.base.config.options.type_check.unwrap_or(false)
}

/// Max warnings configured in the root config.
pub fn max_warnings(&self) -> Option<usize> {
self.base.base.config.options.max_warnings
}

pub(crate) fn get_related_config(&self, path: &Path) -> &Config {
if self.nested_configs.is_empty() {
&self.base
Expand Down Expand Up @@ -1209,4 +1214,33 @@ mod test {
let store = ConfigStore::new(base, FxHashMap::default(), ExternalPluginStore::default());
assert!(!store.type_check_enabled());
}

#[test]
fn test_max_warnings_from_root_config() {
let base = Config::new(
vec![],
vec![],
OxlintCategories::default(),
LintConfig {
options: OxlintOptions { max_warnings: Some(3), ..OxlintOptions::default() },
..LintConfig::default()
},
ResolvedOxlintOverrides::new(vec![]),
);
let store = ConfigStore::new(base, FxHashMap::default(), ExternalPluginStore::default());
assert_eq!(store.max_warnings(), Some(3));
}

#[test]
fn test_max_warnings_disabled_by_default() {
let base = Config::new(
vec![],
vec![],
OxlintCategories::default(),
LintConfig::default(),
ResolvedOxlintOverrides::new(vec![]),
);
let store = ConfigStore::new(base, FxHashMap::default(), ExternalPluginStore::default());
assert_eq!(store.max_warnings(), None);
}
}
35 changes: 28 additions & 7 deletions crates/oxc_linter/src/config/oxlintrc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,25 @@ pub struct OxlintOptions {
/// Equivalent to passing `--type-check` on the CLI.
#[serde(skip_serializing_if = "Option::is_none")]
pub type_check: Option<bool>,
/// Specify a warning threshold. Exits with an error status if warnings exceed this value.
///
/// Equivalent to passing `--max-warnings` on the CLI.
#[serde(skip_serializing_if = "Option::is_none")]
pub max_warnings: Option<usize>,
}

impl OxlintOptions {
#[must_use]
pub fn is_empty(&self) -> bool {
self.type_aware.is_none() && self.type_check.is_none()
self.type_aware.is_none() && self.type_check.is_none() && self.max_warnings.is_none()
}

#[must_use]
pub fn merge(&self, other: &OxlintOptions) -> Self {
Self {
type_aware: self.type_aware.or(other.type_aware),
type_check: self.type_check.or(other.type_check),
max_warnings: self.max_warnings.or(other.max_warnings),
}
}
}
Expand Down Expand Up @@ -399,6 +405,7 @@ mod test {
assert_eq!(config.extends, Vec::<PathBuf>::default());
assert_eq!(config.options.type_aware, None);
assert_eq!(config.options.type_check, None);
assert_eq!(config.options.max_warnings, None);
}

#[test]
Expand All @@ -418,6 +425,14 @@ mod test {
let config: Oxlintrc =
serde_json::from_value(json!({ "options": { "typeCheck": false } })).unwrap();
assert_eq!(config.options.type_check, Some(false));

let config: Oxlintrc =
serde_json::from_value(json!({ "options": { "maxWarnings": 10 } })).unwrap();
assert_eq!(config.options.max_warnings, Some(10));

let config: Result<Oxlintrc, _> =
serde_json::from_value(json!({ "options": { "maxWarnings": -1 } }));
assert!(config.is_err());
}

#[test]
Expand All @@ -427,23 +442,29 @@ mod test {

let config: Result<Oxlintrc, _> = serde_json::from_value(json!({ "typeCheck": true }));
assert!(config.is_err());

let config: Result<Oxlintrc, _> = serde_json::from_value(json!({ "maxWarnings": 1 }));
assert!(config.is_err());
}

#[test]
fn test_oxlintrc_merge_options() {
let mut root: Oxlintrc =
serde_json::from_value(json!({ "options": { "typeAware": true, "typeCheck": false } }))
.unwrap();
let mut root: Oxlintrc = serde_json::from_value(
json!({ "options": { "typeAware": true, "typeCheck": false, "maxWarnings": 1 } }),
)
.unwrap();
root.path = PathBuf::from("/root/.oxlintrc.json");

let mut base: Oxlintrc =
serde_json::from_value(json!({ "options": { "typeAware": false, "typeCheck": true } }))
.unwrap();
let mut base: Oxlintrc = serde_json::from_value(
json!({ "options": { "typeAware": false, "typeCheck": true, "maxWarnings": 10 } }),
)
.unwrap();
base.path = PathBuf::from("/root/base.json");

let merged = root.merge(base);
assert_eq!(merged.options.type_aware, Some(true));
assert_eq!(merged.options.type_check, Some(false));
assert_eq!(merged.options.max_warnings, Some(1));
}

#[test]
Expand Down
Loading
Loading