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:
DonIsaac 2024-09-17 05:46:16 +00:00
parent a5f2e9ac04
commit 3bf7b24621
3 changed files with 102 additions and 26 deletions

View file

@ -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>> {

View file

@ -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,
));
}
}
_ => {}

View file

@ -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