mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
feat(linter): implement @typescript-eslint/no-duplicate-enum-values (#726)
implement [@typescript-eslint/no-duplicate-enum-values](https://typescript-eslint.io/rules/no-duplicate-enum-values) Related issue: #503.
This commit is contained in:
parent
37efbd7af3
commit
4f5e4c1bac
3 changed files with 243 additions and 0 deletions
|
|
@ -77,6 +77,7 @@ mod typescript {
|
|||
pub mod adjacent_overload_signatures;
|
||||
pub mod consistent_type_exports;
|
||||
pub mod isolated_declaration;
|
||||
pub mod no_duplicate_enum_values;
|
||||
pub mod no_empty_interface;
|
||||
pub mod no_extra_non_null_assertion;
|
||||
pub mod no_misused_new;
|
||||
|
|
@ -161,6 +162,7 @@ oxc_macros::declare_all_lint_rules! {
|
|||
typescript::adjacent_overload_signatures,
|
||||
typescript::consistent_type_exports,
|
||||
typescript::isolated_declaration,
|
||||
typescript::no_duplicate_enum_values,
|
||||
typescript::no_empty_interface,
|
||||
typescript::no_extra_non_null_assertion,
|
||||
typescript::no_non_null_asserted_optional_chain,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,203 @@
|
|||
use oxc_ast::ast::Expression;
|
||||
use oxc_ast::AstKind;
|
||||
use oxc_diagnostics::{
|
||||
miette::{self, Diagnostic},
|
||||
thiserror::Error,
|
||||
};
|
||||
use oxc_macros::declare_oxc_lint;
|
||||
use oxc_span::{Atom, Span};
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use crate::{context::LintContext, rule::Rule, AstNode};
|
||||
|
||||
#[derive(Debug, Error, Diagnostic)]
|
||||
#[error("Disallow duplicate enum member values")]
|
||||
#[diagnostic(severity(warning))]
|
||||
struct NoDuplicateEnumValuesDiagnostic(#[label] pub Span);
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct NoDuplicateEnumValues;
|
||||
|
||||
declare_oxc_lint!(
|
||||
/// ### What it does
|
||||
/// Disallow duplicate enum member values.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Although TypeScript supports duplicate enum member values, people usually expect members to have unique values within the same enum. Duplicate values can lead to bugs that are hard to track down.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```javascript
|
||||
/// enum E {
|
||||
// A = 0,
|
||||
// B = 0,
|
||||
// }
|
||||
/// ```
|
||||
NoDuplicateEnumValues,
|
||||
correctness
|
||||
);
|
||||
|
||||
impl Rule for NoDuplicateEnumValues {
|
||||
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
|
||||
let AstKind::TSEnumBody(enum_body) = node.kind() else { return };
|
||||
let mut seen_number_values: Vec<f64> = Vec::new();
|
||||
let mut seen_string_values: FxHashSet<&Atom> = FxHashSet::default();
|
||||
for enum_member in &enum_body.members {
|
||||
let Some(initializer) = &enum_member.initializer else { continue };
|
||||
match initializer {
|
||||
Expression::NumberLiteral(num) => {
|
||||
if seen_number_values.contains(&num.value) {
|
||||
ctx.diagnostic(NoDuplicateEnumValuesDiagnostic(num.span));
|
||||
}
|
||||
seen_number_values.push(num.value);
|
||||
}
|
||||
Expression::StringLiteral(str) => {
|
||||
if seen_string_values.contains(&str.value) {
|
||||
ctx.diagnostic(NoDuplicateEnumValuesDiagnostic(str.span));
|
||||
}
|
||||
seen_string_values.insert(&str.value);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
#[test]
|
||||
fn test() {
|
||||
use crate::tester::Tester;
|
||||
|
||||
let pass = vec![
|
||||
(
|
||||
"
|
||||
enum E {
|
||||
A,
|
||||
B,
|
||||
}
|
||||
",
|
||||
None,
|
||||
),
|
||||
(
|
||||
"
|
||||
enum E {
|
||||
A = 1,
|
||||
B,
|
||||
}
|
||||
",
|
||||
None,
|
||||
),
|
||||
(
|
||||
"
|
||||
enum E {
|
||||
A = 1,
|
||||
B = 2,
|
||||
}
|
||||
",
|
||||
None,
|
||||
),
|
||||
(
|
||||
"
|
||||
enum E {
|
||||
A = 'A',
|
||||
B = 'B',
|
||||
}
|
||||
",
|
||||
None,
|
||||
),
|
||||
(
|
||||
"
|
||||
enum E {
|
||||
A = 'A',
|
||||
B = 'B',
|
||||
C,
|
||||
}
|
||||
",
|
||||
None,
|
||||
),
|
||||
(
|
||||
"
|
||||
enum E {
|
||||
A = 'A',
|
||||
B = 'B',
|
||||
C = 2,
|
||||
D = 1 + 1,
|
||||
}
|
||||
",
|
||||
None,
|
||||
),
|
||||
(
|
||||
"
|
||||
enum E {
|
||||
A = 3,
|
||||
B = 2,
|
||||
C,
|
||||
}
|
||||
",
|
||||
None,
|
||||
),
|
||||
(
|
||||
"
|
||||
enum E {
|
||||
A = 'A',
|
||||
B = 'B',
|
||||
C = 2,
|
||||
D = foo(),
|
||||
}
|
||||
",
|
||||
None,
|
||||
),
|
||||
(
|
||||
"
|
||||
enum E {
|
||||
A = '',
|
||||
B = 0,
|
||||
}
|
||||
",
|
||||
None,
|
||||
),
|
||||
(
|
||||
"
|
||||
enum E {
|
||||
A = 0,
|
||||
B = -0,
|
||||
C = NaN,
|
||||
}
|
||||
",
|
||||
None,
|
||||
),
|
||||
];
|
||||
|
||||
let fail = vec![
|
||||
(
|
||||
"
|
||||
enum E {
|
||||
A = 1,
|
||||
B = 1,
|
||||
}
|
||||
",
|
||||
None,
|
||||
),
|
||||
(
|
||||
"
|
||||
enum E {
|
||||
A = 'A',
|
||||
B = 'A',
|
||||
}
|
||||
",
|
||||
None,
|
||||
),
|
||||
(
|
||||
"
|
||||
enum E {
|
||||
A = 'A',
|
||||
B = 'A',
|
||||
C = 1,
|
||||
D = 1,
|
||||
}
|
||||
",
|
||||
None,
|
||||
),
|
||||
];
|
||||
|
||||
Tester::new(NoDuplicateEnumValues::NAME, pass, fail).test_and_snapshot();
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
---
|
||||
source: crates/oxc_linter/src/tester.rs
|
||||
assertion_line: 80
|
||||
expression: no_duplicate_enum_values
|
||||
---
|
||||
⚠ Disallow duplicate enum member values
|
||||
╭─[no_duplicate_enum_values.tsx:3:1]
|
||||
3 │ A = 1,
|
||||
4 │ B = 1,
|
||||
· ─
|
||||
5 │ }
|
||||
╰────
|
||||
|
||||
⚠ Disallow duplicate enum member values
|
||||
╭─[no_duplicate_enum_values.tsx:3:1]
|
||||
3 │ A = 'A',
|
||||
4 │ B = 'A',
|
||||
· ───
|
||||
5 │ }
|
||||
╰────
|
||||
|
||||
⚠ Disallow duplicate enum member values
|
||||
╭─[no_duplicate_enum_values.tsx:3:1]
|
||||
3 │ A = 'A',
|
||||
4 │ B = 'A',
|
||||
· ───
|
||||
5 │ C = 1,
|
||||
╰────
|
||||
|
||||
⚠ Disallow duplicate enum member values
|
||||
╭─[no_duplicate_enum_values.tsx:5:1]
|
||||
5 │ C = 1,
|
||||
6 │ D = 1,
|
||||
· ─
|
||||
7 │ }
|
||||
╰────
|
||||
|
||||
|
||||
Loading…
Reference in a new issue