diff --git a/AGENTS.md b/AGENTS.md index e06ef2054b59f..84e56c26b297b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,7 +31,6 @@ Start from the `crates/` directory when exploring the codebase. Sub-directories named `generated` are code generated, do not edit files in these directories. - ### Important Crates Overview - **`oxc_parser`** - JavaScript/TypeScript parser diff --git a/crates/oxc_linter/tests/rule_configuration_test.rs b/crates/oxc_linter/tests/rule_configuration_test.rs new file mode 100644 index 0000000000000..09ecd82717989 --- /dev/null +++ b/crates/oxc_linter/tests/rule_configuration_test.rs @@ -0,0 +1,85 @@ +//! Test to ensure rule configuration consistency +//! +//! This test verifies that for all linter rules, the default configuration +//! obtained via `Default::default()` matches the configuration obtained via +//! `from_configuration(null)`. This helps prevent bugs where rules behave +//! differently depending on how they are initialized. + +use oxc_linter::rules::RULES; +use serde_json::Value; + +/// Test to ensure consistency between `rule::default()` and `rule::from_configuration(null)`. +/// +/// This test helps prevent issues where a rule's default configuration differs from +/// its configuration when initialized with a null value. Both should produce identical +/// results to maintain consistency in rule behavior. +/// +/// See issue #12718 for an example of why this test is important. +#[test] +fn test_rule_default_matches_from_configuration_null() { + let mut failures = Vec::new(); + + // Rules that are known to have configuration mismatches and need to be fixed + // TODO: These should be removed as the rules are fixed to have consistent defaults + // When fixing a rule, ensure that either: + // 1. The Default implementation returns the same values as from_configuration(null), or + // 2. The from_configuration method is updated to return Default::default() when given null + let exceptions = [ + "eslint/new-cap", + "eslint/no-unneeded-ternary", + "eslint/no-else-return", + "import/extensions", + "import/no-anonymous-default-export", + "jest/no-deprecated-functions", + "jest/no-large-snapshots", + "jest/prefer-lowercase-title", + "react/jsx-filename-extension", + "typescript/no-this-alias", + "unicorn/prefer-object-from-entries", + "unicorn/prefer-structured-clone", + ]; + + // Iterate through all available linter rules + for rule in RULES.iter() { + let plugin_name = rule.plugin_name(); + let rule_name = rule.name(); + + // Skip rules that are known to have issues + if exceptions.contains(&format!("{plugin_name}/{rule_name}").as_str()) { + continue; + } + + // Get the default configuration using rule::default + let default_rule = rule.clone(); + + // Get the configuration using rule::from_configuration(null) + let null_configured_rule = rule.read_json(Value::Null); + + // Compare the two configurations + // Since RuleEnum doesn't implement PartialEq for the inner rule types, + // we need to compare them by running them and checking their behavior + // For now, we'll use the debug representation as a proxy + let default_debug = format!("{default_rule:?}"); + let null_debug = format!("{null_configured_rule:?}"); + + if default_debug != null_debug { + failures.push(format!( + "Rule '{plugin_name}/{rule_name}' has different configurations between default() and from_configuration(null).\n\ + Default: {default_debug}\n\ + From null: {null_debug}", + + )); + } + } + + assert!( + failures.is_empty(), + "Found {} rules with configuration mismatches:\n\n{}\n\n\ +To fix these issues:\n\ +1. Update the Default implementation to match from_configuration(null), or\n\ +2. Update from_configuration to return Default::default() when given null\n\ +3. Add the rule to the exceptions list above if the mismatch is intentional", + failures.len(), + failures.join("\n\n") + ); +}