Skip to content

Commit 4cfdc59

Browse files
committed
feat(linter): inherit rules via the extended config files
1 parent dfdbb48 commit 4cfdc59

14 files changed

+324
-15
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
console.log("test");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": ["./invalid_config.json"]
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": ["./rules_config.json"]
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
invalid
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"rules": {
3+
"no-debugger": "off",
4+
"no-console": "error",
5+
"unicorn/no-null": "error"
6+
}
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"rules": {
3+
"no-console": "off",
4+
"no-var": "off",
5+
"oxc/approx-constant": "off",
6+
"unicorn/no-null": "off",
7+
"unicorn/no-array-for-each": "off"
8+
}
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"rules": {
3+
"no-console": "error",
4+
"no-var": "error",
5+
"oxc/approx-constant": "error",
6+
"unicorn/no-null": "error",
7+
"unicorn/no-array-for-each": "error"
8+
}
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"rules": {
3+
"no-console": "warn",
4+
"no-var": "warn",
5+
"oxc/approx-constant": "warn",
6+
"unicorn/no-null": "warn",
7+
"unicorn/no-array-for-each": "warn"
8+
}
9+
}

apps/oxlint/src/lint.rs

+45-5
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::{
77

88
use cow_utils::CowUtils;
99
use ignore::{gitignore::Gitignore, overrides::OverrideBuilder};
10-
use oxc_diagnostics::{DiagnosticService, GraphicalReportHandler};
10+
use oxc_diagnostics::{DiagnosticService, GraphicalReportHandler, OxcDiagnostic};
1111
use oxc_linter::{
1212
AllowWarnDeny, ConfigStore, ConfigStoreBuilder, InvalidFilterKind, LintFilter, LintOptions,
1313
LintService, LintServiceOptions, Linter, Oxlintrc, loader::LINT_PARTIAL_LOADER_EXT,
@@ -200,9 +200,28 @@ impl Runner for LintRunner {
200200

201201
// iterate over each config and build the ConfigStore
202202
for (dir, oxlintrc) in nested_oxlintrc {
203+
// TODO(refactor): clean up all of the error handling in this function
204+
let builder = match ConfigStoreBuilder::from_oxlintrc(false, oxlintrc) {
205+
Ok(builder) => builder,
206+
Err(e) => {
207+
let handler = GraphicalReportHandler::new();
208+
let mut err = String::new();
209+
handler
210+
.render_report(&mut err, &OxcDiagnostic::error(e.to_string()))
211+
.unwrap();
212+
stdout
213+
.write_all(
214+
format!("Failed to parse configuration file.\n{err}\n").as_bytes(),
215+
)
216+
.or_else(Self::check_for_writer_error)
217+
.unwrap();
218+
stdout.flush().unwrap();
219+
220+
return CliRunResult::InvalidOptionConfig;
221+
}
222+
}
203223
// TODO(perf): figure out if we can avoid cloning `filter`
204-
let builder =
205-
ConfigStoreBuilder::from_oxlintrc(false, oxlintrc).with_filters(filter.clone());
224+
.with_filters(filter.clone());
206225
match builder.build() {
207226
Ok(config) => nested_configs.insert(dir.to_path_buf(), config),
208227
Err(diagnostic) => {
@@ -230,8 +249,22 @@ impl Runner for LintRunner {
230249
} else {
231250
None
232251
};
233-
let config_builder =
234-
ConfigStoreBuilder::from_oxlintrc(false, oxlintrc).with_filters(filter);
252+
let config_builder = match ConfigStoreBuilder::from_oxlintrc(false, oxlintrc) {
253+
Ok(builder) => builder,
254+
Err(e) => {
255+
let handler = GraphicalReportHandler::new();
256+
let mut err = String::new();
257+
handler.render_report(&mut err, &OxcDiagnostic::error(e.to_string())).unwrap();
258+
stdout
259+
.write_all(format!("Failed to parse configuration file.\n{err}\n").as_bytes())
260+
.or_else(Self::check_for_writer_error)
261+
.unwrap();
262+
stdout.flush().unwrap();
263+
264+
return CliRunResult::InvalidOptionConfig;
265+
}
266+
}
267+
.with_filters(filter);
235268

236269
if let Some(basic_config_file) = oxlintrc_for_print {
237270
let config_file = config_builder.resolve_final_config_file(basic_config_file);
@@ -982,4 +1015,11 @@ mod test {
9821015
];
9831016
Tester::new().with_cwd("fixtures/nested_config".into()).test_and_snapshot(args);
9841017
}
1018+
1019+
#[test]
1020+
fn test_extends_explicit_config() {
1021+
// Check that referencing a config file that extends other config files works as expected
1022+
let args = &["--config", "extends_rules_config.json", "console.js"];
1023+
Tester::new().with_cwd("fixtures/extends_config".into()).test_and_snapshot(args);
1024+
}
9851025
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
source: apps/oxlint/src/tester.rs
3+
---
4+
##########
5+
arguments: --config extends_rules_config.json console.js
6+
working directory: fixtures/extends_config
7+
----------
8+
9+
x ]8;;https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-console.html\eslint(no-console)]8;;\: eslint(no-console): Unexpected console statement.
10+
,-[console.js:1:1]
11+
1 | console.log("test");
12+
: ^^^^^^^^^^^
13+
`----
14+
help: Delete this console statement.
15+
16+
Found 0 warnings and 1 error.
17+
Finished in <variable>ms on 1 file with 100 rules using 1 threads.
18+
----------
19+
CLI result: LintFoundErrors
20+
----------

crates/oxc_language_server/src/main.rs

+1
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,7 @@ impl Backend {
497497
let config = Oxlintrc::from_file(&config_path)
498498
.expect("should have initialized linter with new options");
499499
let config_store = ConfigStoreBuilder::from_oxlintrc(true, config.clone())
500+
.expect("failed to build config")
500501
.build()
501502
.expect("failed to build config");
502503
*linter = ServerLinter::new_with_linter(

0 commit comments

Comments
 (0)