feat(linter/eslint): improve no-duplicate-case rule (#4942)

- ignore parethesis when comparing case test expressions
- make labels in diagnostics more friendly
- add more examples to documentation
This commit is contained in:
DonIsaac 2024-08-17 01:21:49 +00:00
parent e99836d7d8
commit e1582a5dd3
2 changed files with 149 additions and 60 deletions

View file

@ -6,10 +6,10 @@ use rustc_hash::FxHashMap;
use crate::{ast_util::calculate_hash, context::LintContext, rule::Rule, AstNode};
fn no_duplicate_case_diagnostic(span0: Span, span1: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("Disallow duplicate case labels")
fn no_duplicate_case_diagnostic(first: Span, second: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("Duplicate case label")
.with_help("Remove the duplicated case")
.with_labels([span0, span1])
.with_labels([first.label("This label here"), second.label("is duplicated here")])
}
#[derive(Debug, Default, Clone)]
@ -26,18 +26,54 @@ declare_oxc_lint!(
/// it is likely that a programmer copied a case clause but forgot to change the test expression.
///
/// ### Example
/// ```javascript
/// var a = 1;
///
/// Examples of **incorrect** code for this rule:
/// ```js
/// var a = 1, one = 1;
/// switch (a) {
/// case 1:
/// break;
/// case 1:
/// break;
/// case 2:
/// break;
/// case 1: // duplicate test expression
/// break;
/// default:
/// break;
/// }
///
/// switch (a) {
/// case one:
/// break;
/// case 2:
/// break;
/// case one: // duplicate test expression
/// break;
/// default:
/// break;
/// }
/// ```
///
/// Examples of **correct** code for this rule:
/// ```js
/// var a = 1,
/// one = 1
/// switch (a) {
/// case 1:
/// break
/// case 2:
/// break
/// default:
/// break
/// }
///
/// switch (a) {
/// case '1':
/// break
/// case '2':
/// break
/// default:
/// break
/// }
/// ```
NoDuplicateCase,
correctness
@ -45,16 +81,18 @@ declare_oxc_lint!(
impl Rule for NoDuplicateCase {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
if let AstKind::SwitchStatement(ss) = node.kind() {
let mut map = FxHashMap::default();
map.reserve(ss.cases.len());
for case in &ss.cases {
if let Some(test) = case.test.as_ref() {
let hash = calculate_hash(test);
let AstKind::SwitchStatement(ss) = node.kind() else {
return;
};
if let Some(prev_span) = map.insert(hash, test.span()) {
ctx.diagnostic(no_duplicate_case_diagnostic(prev_span, test.span()));
}
let mut map = FxHashMap::default();
map.reserve(ss.cases.len());
for case in &ss.cases {
if let Some(test) = case.test.as_ref() {
let hash = calculate_hash(test.get_inner_expression());
if let Some(prev_span) = map.insert(hash, test.span()) {
ctx.diagnostic(no_duplicate_case_diagnostic(prev_span, test.span()));
}
}
}
@ -103,6 +141,10 @@ fn test() {
"var a = 1; switch (a) {case 1: break; case 1: break; case 2: break; default: break;}",
None,
),
(
"var a = 1; switch (a) {case 1: break; case (1): break; case 2: break; default: break;}",
None,
),
(
"var a = '1'; switch (a) {case '1': break; case '1': break; case '2': break; default: break;}",
None,

View file

@ -1,146 +1,193 @@
---
source: crates/oxc_linter/src/tester.rs
---
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
⚠ eslint(no-duplicate-case): Duplicate case label
╭─[no_duplicate_case.tsx:1:29]
1 │ var a = 1; switch (a) {case 1: break; case 1: break; case 2: break; default: break;}
· ─ ─
· ┬ ┬
· │ ╰── is duplicated here
· ╰── This label here
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
⚠ eslint(no-duplicate-case): Duplicate case label
╭─[no_duplicate_case.tsx:1:29]
1 │ var a = 1; switch (a) {case 1: break; case (1): break; case 2: break; default: break;}
· ┬ ─┬─
· │ ╰── is duplicated here
· ╰── This label here
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Duplicate case label
╭─[no_duplicate_case.tsx:1:31]
1 │ var a = '1'; switch (a) {case '1': break; case '1': break; case '2': break; default: break;}
· ─── ───
· ─┬─ ─┬─
· │ ╰── is duplicated here
· ╰── This label here
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
⚠ eslint(no-duplicate-case): Duplicate case label
╭─[no_duplicate_case.tsx:1:38]
1 │ var a = 1, one = 1; switch (a) {case one: break; case one: break; case 2: break; default: break;}
· ─── ───
· ─┬─ ─┬─
· │ ╰── is duplicated here
· ╰── This label here
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
⚠ eslint(no-duplicate-case): Duplicate case label
╭─[no_duplicate_case.tsx:1:54]
1 │ var a = 1, p = {p: {p1: 1, p2: 1}}; switch (a) {case p.p.p1: break; case p.p.p1: break; default: break;}
· ────── ──────
· ───┬── ───┬──
· │ ╰── is duplicated here
· ╰── This label here
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
⚠ eslint(no-duplicate-case): Duplicate case label
╭─[no_duplicate_case.tsx:1:84]
1 │ var a = 1, f = function(b) { return b ? { p1: 1 } : { p1: 2 }; }; switch (a) {case f(true).p1: break; case f(true).p1: break; default: break;}
· ────────── ──────────
· ─────┬──── ─────┬────
· │ ╰── is duplicated here
· ╰── This label here
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
⚠ eslint(no-duplicate-case): Duplicate case label
╭─[no_duplicate_case.tsx:1:67]
1 │ var a = 1, f = function(s) { return { p1: s } }; switch (a) {case f(a + 1).p1: break; case f(a + 1).p1: break; default: break;}
· ─────────── ───────────
· ─────┬───── ─────┬─────
· │ ╰── is duplicated here
· ╰── This label here
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
⚠ eslint(no-duplicate-case): Duplicate case label
╭─[no_duplicate_case.tsx:1:67]
1 │ var a = 1, f = function(s) { return { p1: s } }; switch (a) {case f(a === 1 ? 2 : 3).p1: break; case f(a === 1 ? 2 : 3).p1: break; default: break;}
· ───────────────────── ─────────────────────
· ──────────┬────────── ──────────┬──────────
· │ ╰── is duplicated here
· ╰── This label here
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
⚠ eslint(no-duplicate-case): Duplicate case label
╭─[no_duplicate_case.tsx:1:67]
1 │ var a = 1, f1 = function() { return { p1: 1 } }; switch (a) {case f1().p1: break; case f1().p1: break; default: break;}
· ─────── ───────
· ───┬─── ───┬───
· │ ╰── is duplicated here
· ╰── This label here
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
⚠ eslint(no-duplicate-case): Duplicate case label
╭─[no_duplicate_case.tsx:1:43]
1 │ var a = [1, 2]; switch(a.toString()){case ([1, 2]).toString():break; case ([1, 2]).toString():break; default:break;}
· ─────────────────── ───────────────────
· ─────────┬───────── ─────────┬─────────
· │ ╰── is duplicated here
· ╰── This label here
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
⚠ eslint(no-duplicate-case): Duplicate case label
╭─[no_duplicate_case.tsx:1:19]
1 │ switch (a) { case a: case a: }
· ─ ─
· ┬ ┬
· │ ╰── is duplicated here
· ╰── This label here
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
⚠ eslint(no-duplicate-case): Duplicate case label
╭─[no_duplicate_case.tsx:1:19]
1 │ switch (a) { case a: break; case b: break; case a: break; case c: break; case a: break; }
· ─ ─
· ┬ ┬
· │ ╰── is duplicated here
· ╰── This label here
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
⚠ eslint(no-duplicate-case): Duplicate case label
╭─[no_duplicate_case.tsx:1:49]
1 │ switch (a) { case a: break; case b: break; case a: break; case c: break; case a: break; }
· ─ ─
· ┬ ┬
· │ ╰── is duplicated here
· ╰── This label here
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
⚠ eslint(no-duplicate-case): Duplicate case label
╭─[no_duplicate_case.tsx:1:54]
1 │ ╭─▶ var a = 1, p = {p: {p1: 1, p2: 1}}; switch (a) {case p.p.p1: break; case p. p // comment
· │ ──────
2 │ ╰─▶ .p1: break; default: break;}
· │ ───┬──
· │ ╰── This label here
2 │ ├─▶ .p1: break; default: break;}
· ╰──── is duplicated here
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
⚠ eslint(no-duplicate-case): Duplicate case label
╭─[no_duplicate_case.tsx:1:54]
1 │ ╭─▶ var a = 1, p = {p: {p1: 1, p2: 1}}; switch (a) {case p .p
2 │ │ /* comment */
3 │ ╰─▶ .p1: break; case p.p.p1: break; default: break;}
· ╰─── ──────
3 │ ├─▶ .p1: break; case p.p.p1: break; default: break;}
· ╰─── ───┬──
· ╰─── ╰── is duplicated here
· ╰──── This label here
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
⚠ eslint(no-duplicate-case): Duplicate case label
╭─[no_duplicate_case.tsx:1:54]
1 │ ╭──▶ var a = 1, p = {p: {p1: 1, p2: 1}}; switch (a) {case p .p
2 │ │ /* comment */
3 │ ╰──▶ .p1: break; case p. p // comment
4 │ ╰──▶ .p1: break; default: break;}
3 │ ├──▶ .p1: break; case p. p // comment
· ╰───── This label here
4 │ ├──▶ .p1: break; default: break;}
· ╰───── is duplicated here
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
⚠ eslint(no-duplicate-case): Duplicate case label
╭─[no_duplicate_case.tsx:1:54]
1 │ ╭─▶ var a = 1, p = {p: {p1: 1, p2: 1}}; switch (a) {case p.p.p1: break; case p. p // comment
· │ ──────
2 │ ╰─▶ .p1: break; case p .p
· │ ───┬──
· │ ╰── This label here
2 │ ├─▶ .p1: break; case p .p
· ╰──── is duplicated here
3 │ /* comment */
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
⚠ eslint(no-duplicate-case): Duplicate case label
╭─[no_duplicate_case.tsx:1:74]
1 │ ╭──▶ var a = 1, p = {p: {p1: 1, p2: 1}}; switch (a) {case p.p.p1: break; case p. p // comment
2 │ ╰──▶ .p1: break; case p .p
2 │ ├──▶ .p1: break; case p .p
· ╰───── This label here
3 │ │ /* comment */
4 │ ╰──▶ .p1: break; default: break;}
4 │ ├──▶ .p1: break; default: break;}
· ╰───── is duplicated here
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
⚠ eslint(no-duplicate-case): Duplicate case label
╭─[no_duplicate_case.tsx:1:67]
1 │ var a = 1, f = function(s) { return { p1: s } }; switch (a) {case f(a + 1).p1: break; case f(a+1).p1: break; default: break;}
· ─────────── ─────────
· ─────┬───── ────┬────
· │ ╰── is duplicated here
· ╰── This label here
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
⚠ eslint(no-duplicate-case): Duplicate case label
╭─[no_duplicate_case.tsx:1:67]
1 │ ╭──▶ var a = 1, f = function(s) { return { p1: s } }; switch (a) {case f(
2 │ │ a + 1 // comment
3 │ ╰──▶ ).p1: break; case f(a+1)
4 │ ╰──▶ .p1: break; default: break;}
3 │ ├──▶ ).p1: break; case f(a+1)
· ╰───── This label here
4 │ ├──▶ .p1: break; default: break;}
· ╰───── is duplicated here
╰────
help: Remove the duplicated case