Skip to content

Commit 158dff0

Browse files
committed
test(linter): add rule configuration consistency test
1 parent b25406f commit 158dff0

File tree

1 file changed

+85
-0
lines changed

1 file changed

+85
-0
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
//! Test to ensure rule configuration consistency
2+
//!
3+
//! This test verifies that for all linter rules, the default configuration
4+
//! obtained via `Default::default()` matches the configuration obtained via
5+
//! `from_configuration(null)`. This helps prevent bugs where rules behave
6+
//! differently depending on how they are initialized.
7+
8+
use oxc_linter::rules::RULES;
9+
use serde_json::Value;
10+
11+
/// Test to ensure consistency between `rule::default()` and `rule::from_configuration(null)`.
12+
///
13+
/// This test helps prevent issues where a rule's default configuration differs from
14+
/// its configuration when initialized with a null value. Both should produce identical
15+
/// results to maintain consistency in rule behavior.
16+
///
17+
/// See issue #12718 for an example of why this test is important.
18+
#[test]
19+
fn test_rule_default_matches_from_configuration_null() {
20+
let mut failures = Vec::new();
21+
22+
// Rules that are known to have configuration mismatches and need to be fixed
23+
// TODO: These should be removed as the rules are fixed to have consistent defaults
24+
// When fixing a rule, ensure that either:
25+
// 1. The Default implementation returns the same values as from_configuration(null), or
26+
// 2. The from_configuration method is updated to return Default::default() when given null
27+
let exceptions = [
28+
"eslint/new-cap",
29+
"eslint/no-unneeded-ternary",
30+
"eslint/no-else-return",
31+
"import/extensions",
32+
"import/no-anonymous-default-export",
33+
"jest/no-deprecated-functions",
34+
"jest/no-large-snapshots",
35+
"jest/prefer-lowercase-title",
36+
"react/jsx-filename-extension",
37+
"typescript/no-this-alias",
38+
"unicorn/prefer-object-from-entries",
39+
"unicorn/prefer-structured-clone",
40+
];
41+
42+
// Iterate through all available linter rules
43+
for rule in RULES.iter() {
44+
let plugin_name = rule.plugin_name();
45+
let rule_name = rule.name();
46+
47+
// Skip rules that are known to have issues
48+
if exceptions.contains(&format!("{plugin_name}/{rule_name}").as_str()) {
49+
continue;
50+
}
51+
52+
// Get the default configuration using rule::default
53+
let default_rule = rule.clone();
54+
55+
// Get the configuration using rule::from_configuration(null)
56+
let null_configured_rule = rule.read_json(Value::Null);
57+
58+
// Compare the two configurations
59+
// Since RuleEnum doesn't implement PartialEq for the inner rule types,
60+
// we need to compare them by running them and checking their behavior
61+
// For now, we'll use the debug representation as a proxy
62+
let default_debug = format!("{default_rule:?}");
63+
let null_debug = format!("{null_configured_rule:?}");
64+
65+
if default_debug != null_debug {
66+
failures.push(format!(
67+
"Rule '{plugin_name}/{rule_name}' has different configurations between default() and from_configuration(null).\n\
68+
Default: {default_debug}\n\
69+
From null: {null_debug}",
70+
71+
));
72+
}
73+
}
74+
75+
assert!(
76+
failures.is_empty(),
77+
"Found {} rules with configuration mismatches:\n\n{}\n\n\
78+
To fix these issues:\n\
79+
1. Update the Default implementation to match from_configuration(null), or\n\
80+
2. Update from_configuration to return Default::default() when given null\n\
81+
3. Add the rule to the exceptions list above if the mismatch is intentional",
82+
failures.len(),
83+
failures.join("\n\n")
84+
);
85+
}

0 commit comments

Comments
 (0)