mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
feat(linter): make typescript/no-duplicate-enum-values a correctness rule (#5810)
This rule is enabled by default in typescript-eslint's recommended config, so we should enable it by default too. I checked this change with `oxlint-ecosystem-ci` locally and saw no problems. This PR also improves diagnostic messages for this rule.
This commit is contained in:
parent
a5f2e9ac04
commit
3bf7b24621
3 changed files with 102 additions and 26 deletions
|
|
@ -21,6 +21,17 @@ impl<'a> TSEnumDeclaration<'a> {
|
|||
Self { span, id, members, r#const, declare, scope_id: Cell::default() }
|
||||
}
|
||||
}
|
||||
impl<'a> TSEnumMemberName<'a> {
|
||||
pub fn static_name(&self) -> Option<&'a str> {
|
||||
match self {
|
||||
Self::StaticIdentifier(ident) => Some(ident.name.as_str()),
|
||||
Self::StaticStringLiteral(lit) => Some(lit.value.as_str()),
|
||||
Self::NumericLiteral(lit) => Some(lit.raw),
|
||||
Self::StaticTemplateLiteral(lit) => lit.quasi().map(Into::into),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TSType<'a> {
|
||||
pub fn get_identifier_reference(&self) -> Option<IdentifierReference<'a>> {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
use oxc_ast::{ast::Expression, AstKind};
|
||||
use oxc_ast::{
|
||||
ast::{Expression, TSEnumMember},
|
||||
AstKind,
|
||||
};
|
||||
use oxc_diagnostics::OxcDiagnostic;
|
||||
use oxc_macros::declare_oxc_lint;
|
||||
use oxc_span::Span;
|
||||
use oxc_span::{GetSpan, Span};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::{
|
||||
|
|
@ -10,10 +13,22 @@ use crate::{
|
|||
AstNode,
|
||||
};
|
||||
|
||||
fn no_duplicate_enum_values_diagnostic(span: Span, span1: Span) -> OxcDiagnostic {
|
||||
OxcDiagnostic::warn("Disallow duplicate enum member values")
|
||||
.with_help("Duplicate values can lead to bugs that are hard to track down")
|
||||
.with_labels([span, span1])
|
||||
fn no_duplicate_enum_values_diagnostic(
|
||||
first_init_span: Span,
|
||||
second_member: &TSEnumMember,
|
||||
value: &str,
|
||||
) -> OxcDiagnostic {
|
||||
let second_name = second_member.id.static_name().unwrap_or("the second member");
|
||||
// Unwrap will never panic since violations are only reported for members
|
||||
// with initializers.
|
||||
let second_init_span = second_member.initializer.as_ref().map(GetSpan::span).unwrap();
|
||||
|
||||
OxcDiagnostic::warn(format!("Duplicate enum value `{value}`"))
|
||||
.with_help(format!("Give {second_name} a unique value"))
|
||||
.with_labels([
|
||||
first_init_span.label(format!("{value} is first used as an initializer here")),
|
||||
second_init_span.label("and is re-used here"),
|
||||
])
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
|
|
@ -28,15 +43,46 @@ declare_oxc_lint!(
|
|||
/// usually expect members to have unique values within the same enum.
|
||||
/// Duplicate values can lead to bugs that are hard to track down.
|
||||
///
|
||||
/// ### Example
|
||||
/// ### Examples
|
||||
///
|
||||
/// This rule disallows defining an enum with multiple members initialized
|
||||
/// to the same value. Members without initializers will not be checked.
|
||||
///
|
||||
/// Example of **incorrect** code:
|
||||
/// ```ts
|
||||
/// enum E {
|
||||
/// A = 0,
|
||||
/// B = 0,
|
||||
/// }
|
||||
/// ```
|
||||
/// ```ts
|
||||
/// enum E {
|
||||
/// A = 'A',
|
||||
/// B = 'A',
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Example of **correct** code:
|
||||
/// ```ts
|
||||
/// enum E {
|
||||
/// A = 0,
|
||||
/// B = 1,
|
||||
/// }
|
||||
/// ```
|
||||
/// ```ts
|
||||
/// enum E {
|
||||
/// A = 'A',
|
||||
/// B = 'B',
|
||||
/// }
|
||||
/// ```
|
||||
/// ```ts
|
||||
/// enum E {
|
||||
/// A,
|
||||
/// B,
|
||||
/// }
|
||||
/// ```
|
||||
NoDuplicateEnumValues,
|
||||
pedantic
|
||||
correctness
|
||||
);
|
||||
|
||||
impl Rule for NoDuplicateEnumValues {
|
||||
|
|
@ -56,14 +102,25 @@ impl Rule for NoDuplicateEnumValues {
|
|||
if let Some((_, old_span)) =
|
||||
seen_number_values.iter().find(|(v, _)| *v == num.value)
|
||||
{
|
||||
ctx.diagnostic(no_duplicate_enum_values_diagnostic(*old_span, num.span));
|
||||
ctx.diagnostic(no_duplicate_enum_values_diagnostic(
|
||||
*old_span,
|
||||
enum_member,
|
||||
num.raw,
|
||||
));
|
||||
} else {
|
||||
seen_number_values.push((num.value, num.span));
|
||||
}
|
||||
}
|
||||
Expression::StringLiteral(s) => {
|
||||
if let Some(old_span) = seen_string_values.insert(s.value.as_str(), s.span) {
|
||||
ctx.diagnostic(no_duplicate_enum_values_diagnostic(old_span, s.span));
|
||||
// Formatting here for prettier messages. This makes it
|
||||
// look like "Duplicate enum value 'A'"
|
||||
let v = format!("'{}'", s.value);
|
||||
ctx.diagnostic(no_duplicate_enum_values_diagnostic(
|
||||
old_span,
|
||||
enum_member,
|
||||
&v,
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
|
|
|||
|
|
@ -1,46 +1,54 @@
|
|||
---
|
||||
source: crates/oxc_linter/src/tester.rs
|
||||
---
|
||||
⚠ typescript-eslint(no-duplicate-enum-values): Disallow duplicate enum member values
|
||||
⚠ typescript-eslint(no-duplicate-enum-values): Duplicate enum value `1`
|
||||
╭─[no_duplicate_enum_values.tsx:3:10]
|
||||
2 │ enum E {
|
||||
3 │ A = 1,
|
||||
· ─
|
||||
· ┬
|
||||
· ╰── 1 is first used as an initializer here
|
||||
4 │ B = 1,
|
||||
· ─
|
||||
· ┬
|
||||
· ╰── and is re-used here
|
||||
5 │ }
|
||||
╰────
|
||||
help: Duplicate values can lead to bugs that are hard to track down
|
||||
help: Give B a unique value
|
||||
|
||||
⚠ typescript-eslint(no-duplicate-enum-values): Disallow duplicate enum member values
|
||||
⚠ typescript-eslint(no-duplicate-enum-values): Duplicate enum value `'A'`
|
||||
╭─[no_duplicate_enum_values.tsx:3:10]
|
||||
2 │ enum E {
|
||||
3 │ A = 'A',
|
||||
· ───
|
||||
· ─┬─
|
||||
· ╰── 'A' is first used as an initializer here
|
||||
4 │ B = 'A',
|
||||
· ───
|
||||
· ─┬─
|
||||
· ╰── and is re-used here
|
||||
5 │ }
|
||||
╰────
|
||||
help: Duplicate values can lead to bugs that are hard to track down
|
||||
help: Give B a unique value
|
||||
|
||||
⚠ typescript-eslint(no-duplicate-enum-values): Disallow duplicate enum member values
|
||||
⚠ typescript-eslint(no-duplicate-enum-values): Duplicate enum value `'A'`
|
||||
╭─[no_duplicate_enum_values.tsx:3:10]
|
||||
2 │ enum E {
|
||||
3 │ A = 'A',
|
||||
· ───
|
||||
· ─┬─
|
||||
· ╰── 'A' is first used as an initializer here
|
||||
4 │ B = 'A',
|
||||
· ───
|
||||
· ─┬─
|
||||
· ╰── and is re-used here
|
||||
5 │ C = 1,
|
||||
╰────
|
||||
help: Duplicate values can lead to bugs that are hard to track down
|
||||
help: Give B a unique value
|
||||
|
||||
⚠ typescript-eslint(no-duplicate-enum-values): Disallow duplicate enum member values
|
||||
⚠ typescript-eslint(no-duplicate-enum-values): Duplicate enum value `1`
|
||||
╭─[no_duplicate_enum_values.tsx:5:10]
|
||||
4 │ B = 'A',
|
||||
5 │ C = 1,
|
||||
· ─
|
||||
· ┬
|
||||
· ╰── 1 is first used as an initializer here
|
||||
6 │ D = 1,
|
||||
· ─
|
||||
· ┬
|
||||
· ╰── and is re-used here
|
||||
7 │ }
|
||||
╰────
|
||||
help: Duplicate values can lead to bugs that are hard to track down
|
||||
help: Give D a unique value
|
||||
|
|
|
|||
Loading…
Reference in a new issue