feat(linter): add no-duplicate-case rule (#179)

This commit is contained in:
Xuan 2023-03-14 16:55:04 +08:00 committed by GitHub
parent 5d3a52c1bb
commit fca882085f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 323 additions and 0 deletions

View file

@ -7,6 +7,7 @@ oxc_macros::declare_all_lint_rules! {
eq_eq_eq,
for_direction,
no_debugger,
no_duplicate_case,
no_array_constructor,
no_caller,
no_empty,

View file

@ -0,0 +1,172 @@
use oxc_ast::{AstKind, GetSpan, Span};
use oxc_diagnostics::{
miette::{self, Diagnostic},
thiserror::Error,
};
use oxc_macros::declare_oxc_lint;
use rustc_hash::FxHashMap;
use crate::{ast_util::calculate_hash, context::LintContext, rule::Rule, AstNode};
#[derive(Debug, Error, Diagnostic)]
#[error("eslint(no-duplicate-case): Disallow duplicate case labels")]
#[diagnostic(severity(warning), help("Remove the duplicated case"))]
struct NoDuplicateCaseDiagnostic(#[label] pub Span, #[label] pub Span);
#[derive(Debug, Default, Clone)]
pub struct NoDuplicateCase;
declare_oxc_lint!(
/// ### What it does
///
/// Disallow duplicate case labels
///
/// ### Why is this bad?
///
/// If a switch statement has duplicate test expressions in case clauses,
/// it is likely that a programmer copied a case clause but forgot to change the test expression.
///
/// ### Example
/// ```javascript
/// var a = 1;
/// switch (a) {
/// case 1:
/// break;
/// case 1:
/// break;
/// case 2:
/// break;
/// default:
/// break;
/// }
/// ```
NoDuplicateCase,
correctness
);
impl Rule for NoDuplicateCase {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
if let AstKind::SwitchStatement(ss) = node.get().kind() {
let mut map = FxHashMap::default();
map.reserve(ss.cases.len());
for case in ss.cases.iter() {
if let Some(test) = case.test.as_ref() {
let hash = calculate_hash(test);
if let Some(prev_span) = map.insert(hash, test.span()) {
ctx.diagnostic(NoDuplicateCaseDiagnostic(prev_span, test.span()));
}
}
}
}
}
}
#[test]
#[allow(clippy::too_many_lines)]
fn test() {
use crate::tester::Tester;
let pass = vec![
("var a = 1; switch (a) {case 1: break; case 2: break; default: break;}", None),
("var a = 1; switch (a) {case 1: break; case '1': break; default: break;}", None),
("var a = 1; switch (a) {case 1: break; case true: break; default: break;}", None),
("var a = 1; switch (a) {default: break;}", None),
(
"var a = 1, p = {p: {p1: 1, p2: 1}}; switch (a) {case p.p.p1: break; case p.p.p2: break; default: break;}",
None,
),
(
"var a = 1, f = function(b) { return b ? { p1: 1 } : { p1: 2 }; }; switch (a) {case f(true).p1: break; case f(true, false).p1: break; default: break;}",
None,
),
(
"var a = 1, f = function(s) { return { p1: s } }; switch (a) {case f(a + 1).p1: break; case f(a + 2).p1: break; default: break;}",
None,
),
(
"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;}",
None,
),
(
"var a = 1, f1 = function() { return { p1: 1 } }, f2 = function() { return { p1: 2 } }; switch (a) {case f1().p1: break; case f2().p1: break; default: break;}",
None,
),
(
"var a = [1,2]; switch(a.toString()){case ([1,2]).toString():break; case ([1]).toString():break; default:break;}",
None,
),
("switch(a) { case a: break; } switch(a) { case a: break; }", None),
("switch(a) { case toString: break; }", None),
];
let fail = vec![
(
"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, one = 1; switch (a) {case one: break; case one: break; case 2: break; default: break;}",
None,
),
(
"var a = 1, p = {p: {p1: 1, p2: 1}}; switch (a) {case p.p.p1: break; case p.p.p1: break; default: break;}",
None,
),
(
"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;}",
None,
),
(
"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;}",
None,
),
(
"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;}",
None,
),
(
"var a = 1, f1 = function() { return { p1: 1 } }; switch (a) {case f1().p1: break; case f1().p1: break; default: break;}",
None,
),
(
"var a = [1, 2]; switch(a.toString()){case ([1, 2]).toString():break; case ([1, 2]).toString():break; default:break;}",
None,
),
("switch (a) { case a: case a: }", None),
(
"switch (a) { case a: break; case b: break; case a: break; case c: break; case a: break; }",
None,
),
(
"var a = 1, p = {p: {p1: 1, p2: 1}}; switch (a) {case p.p.p1: break; case p. p // comment\n .p1: break; default: break;}",
None,
),
(
"var a = 1, p = {p: {p1: 1, p2: 1}}; switch (a) {case p .p\n/* comment */\n.p1: break; case p.p.p1: break; default: break;}",
None,
),
(
"var a = 1, p = {p: {p1: 1, p2: 1}}; switch (a) {case p .p\n/* comment */\n.p1: break; case p. p // comment\n .p1: break; default: break;}",
None,
),
(
"var a = 1, p = {p: {p1: 1, p2: 1}}; switch (a) {case p.p.p1: break; case p. p // comment\n .p1: break; case p .p\n/* comment */\n.p1: break; default: break;}",
None,
),
(
"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;}",
None,
),
(
"var a = 1, f = function(s) { return { p1: s } }; switch (a) {case f(\na + 1 // comment\n).p1: break; case f(a+1)\n.p1: break; default: break;}",
None,
),
];
Tester::new(NoDuplicateCase::NAME, pass, fail).test_and_snapshot();
}

View file

@ -0,0 +1,150 @@
---
source: crates/oxc_linter/src/tester.rs
assertion_line: 53
expression: no_duplicate_case
---
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
╭─[no_duplicate_case.tsx:1:1]
1 │ var a = 1; switch (a) {case 1: break; case 1: break; case 2: break; default: break;}
· ─ ─
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
╭─[no_duplicate_case.tsx:1:1]
1 │ var a = '1'; switch (a) {case '1': break; case '1': break; case '2': break; default: break;}
· ─── ───
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
╭─[no_duplicate_case.tsx:1:1]
1 │ var a = 1, one = 1; switch (a) {case one: break; case one: break; case 2: break; default: break;}
· ─── ───
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
╭─[no_duplicate_case.tsx:1:1]
1 │ var a = 1, p = {p: {p1: 1, p2: 1}}; switch (a) {case p.p.p1: break; case p.p.p1: break; default: break;}
· ────── ──────
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
╭─[no_duplicate_case.tsx:1:1]
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;}
· ────────── ──────────
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
╭─[no_duplicate_case.tsx:1:1]
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;}
· ─────────── ───────────
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
╭─[no_duplicate_case.tsx:1:1]
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;}
· ───────────────────── ─────────────────────
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
╭─[no_duplicate_case.tsx:1:1]
1 │ var a = 1, f1 = function() { return { p1: 1 } }; switch (a) {case f1().p1: break; case f1().p1: break; default: break;}
· ─────── ───────
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
╭─[no_duplicate_case.tsx:1:1]
1 │ var a = [1, 2]; switch(a.toString()){case ([1, 2]).toString():break; case ([1, 2]).toString():break; default:break;}
· ─────────────────── ───────────────────
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
╭─[no_duplicate_case.tsx:1:1]
1 │ switch (a) { case a: case a: }
· ─ ─
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
╭─[no_duplicate_case.tsx:1:1]
1 │ switch (a) { case a: break; case b: break; case a: break; case c: break; case a: break; }
· ─ ─
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
╭─[no_duplicate_case.tsx:1:1]
1 │ switch (a) { case a: break; case b: break; case a: break; case c: break; case a: break; }
· ─ ─
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
╭─[no_duplicate_case.tsx:1:1]
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;}
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
╭─[no_duplicate_case.tsx:1:1]
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;}
· ╰─── ──────
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
╭─[no_duplicate_case.tsx:1:1]
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;}
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
╭─[no_duplicate_case.tsx:1:1]
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
3 │ /* comment */
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
╭─[no_duplicate_case.tsx:1:1]
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
3 │ │ /* comment */
4 │ ╰──▶ .p1: break; default: break;}
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
╭─[no_duplicate_case.tsx:1:1]
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;}
· ─────────── ─────────
╰────
help: Remove the duplicated case
⚠ eslint(no-duplicate-case): Disallow duplicate case labels
╭─[no_duplicate_case.tsx:1:1]
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;}
╰────
help: Remove the duplicated case