diff --git a/apps/oxlint/fixtures/two_rules_with_same_name/.oxlintrc.json b/apps/oxlint/fixtures/two_rules_with_same_name/.oxlintrc.json new file mode 100644 index 0000000000000..0d72913f56ac7 --- /dev/null +++ b/apps/oxlint/fixtures/two_rules_with_same_name/.oxlintrc.json @@ -0,0 +1,10 @@ +{ + "plugins": [ + "oxc", + "unicorn" + ], + "rules": { + "eslint/no-nested-ternary": "off", + "unicorn/no-nested-ternary": "off" + } +} diff --git a/apps/oxlint/fixtures/two_rules_with_same_name/test.js b/apps/oxlint/fixtures/two_rules_with_same_name/test.js new file mode 100644 index 0000000000000..498f6f9b237f8 --- /dev/null +++ b/apps/oxlint/fixtures/two_rules_with_same_name/test.js @@ -0,0 +1 @@ +console.log(bar ? baz : qux === quxx ? bing : bam); diff --git a/apps/oxlint/src/lint.rs b/apps/oxlint/src/lint.rs index 8a910bca13365..00e7e764fd642 100644 --- a/apps/oxlint/src/lint.rs +++ b/apps/oxlint/src/lint.rs @@ -824,6 +824,13 @@ mod test { .test_and_snapshot_multiple(&[args_1, args_2]); } + #[test] + fn test_two_rules_with_same_name_from_different_plugin_names() { + // Issue: + let args = &["-c", ".oxlintrc.json", "test.js"]; + Tester::new().with_cwd("fixtures/two_rules_with_same_name".into()).test_and_snapshot(args); + } + #[test] fn test_adjust_ignore_patterns() { let base = PathBuf::from("/project/root"); diff --git a/apps/oxlint/src/snapshots/fixtures__two_rules_with_same_name_-c .oxlintrc.json test.js@oxlint.snap b/apps/oxlint/src/snapshots/fixtures__two_rules_with_same_name_-c .oxlintrc.json test.js@oxlint.snap new file mode 100644 index 0000000000000..9ecde3130497d --- /dev/null +++ b/apps/oxlint/src/snapshots/fixtures__two_rules_with_same_name_-c .oxlintrc.json test.js@oxlint.snap @@ -0,0 +1,12 @@ +--- +source: apps/oxlint/src/tester.rs +--- +########## +arguments: -c .oxlintrc.json test.js +working directory: fixtures/two_rules_with_same_name +---------- +Found 0 warnings and 0 errors. +Finished in ms on 1 file with 74 rules using 1 threads. +---------- +CLI result: LintSucceeded +---------- diff --git a/crates/oxc_linter/src/config/rules.rs b/crates/oxc_linter/src/config/rules.rs index 251026b5f088c..b085ffd63b155 100644 --- a/crates/oxc_linter/src/config/rules.rs +++ b/crates/oxc_linter/src/config/rules.rs @@ -110,28 +110,43 @@ impl OxlintRules { } } _ => { - // For overlapping rule names, use the "error" one - // "no-loss-of-precision": "off", - // "@typescript-eslint/no-loss-of-precision": "error" - if let Some(rule_config) = - rule_configs.iter().find(|r| r.severity.is_warn_deny()) - { - let config = rule_config.config.clone().unwrap_or_default(); - - if let Some(rule) = rules_for_override.iter().find(|r| r.name() == *name) { - rules_to_replace - .push(RuleWithSeverity::new(rule.read_json(config), rule.severity)); - } - // If the given rule is not found in the rule list (for example, if all rules are disabled), - // then look it up in the entire rules list and add it. - else if let Some(rule) = all_rules.iter().find(|r| r.name() == *name) { - rules_to_replace.push(RuleWithSeverity::new( - rule.read_json(config), - rule_config.severity, - )); - } - } else if rule_configs.iter().all(|r| r.severity.is_allow()) { - if let Some(rule) = rules_for_override.iter().find(|r| r.name() == *name) { + let rules = rules_for_override + .iter() + .filter_map(|r| { + if r.name() == *name { + Some((r.plugin_name(), r)) + } else { + None + } + }) + .collect::>(); + + for rule_config in rule_configs { + let (rule_name, plugin_name) = transform_rule_and_plugin_name( + &rule_config.rule_name, + &rule_config.plugin_name, + ); + + if rule_config.severity.is_warn_deny() { + let config = rule_config.config.clone().unwrap_or_default(); + if let Some(rule) = rules.get(&plugin_name) { + rules_to_replace.push(RuleWithSeverity::new( + rule.read_json(config), + rule.severity, + )); + } + // If the given rule is not found in the rule list (for example, if all rules are disabled), + // then look it up in the entire rules list and add it. + else if let Some(rule) = all_rules + .iter() + .find(|r| r.name() == rule_name && r.plugin_name() == plugin_name) + { + rules_to_replace.push(RuleWithSeverity::new( + rule.read_json(config), + rule_config.severity, + )); + } + } else if let Some(&rule) = rules.get(&plugin_name) { rules_to_remove.push(rule.clone()); } } @@ -152,17 +167,12 @@ fn transform_rule_and_plugin_name<'a>( rule_name: &'a str, plugin_name: &'a str, ) -> (&'a str, &'a str) { - if plugin_name == "vitest" && is_jest_rule_adapted_to_vitest(rule_name) { - return (rule_name, "jest"); - } - - if plugin_name == "typescript" && is_eslint_rule_adapted_to_typescript(rule_name) { - return (rule_name, "eslint"); - } - - if plugin_name == "unicorn" && rule_name == "no-negated-condition" { - return ("no-negated-condition", "eslint"); - } + let plugin_name = match plugin_name { + "vitest" if is_jest_rule_adapted_to_vitest(rule_name) => "jest", + "unicorn" if rule_name == "no-negated-condition" => "eslint", + "typescript" if is_eslint_rule_adapted_to_typescript(rule_name) => "eslint", + _ => plugin_name, + }; (rule_name, plugin_name) }