diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 86a791a7e088..92baa17f597d 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -138,6 +138,7 @@ mod typescript { pub mod no_unsafe_declaration_merging; pub mod no_var_requires; pub mod prefer_as_const; + pub mod prefer_enum_initializers; pub mod prefer_for_of; pub mod prefer_function_type; pub mod prefer_ts_expect_error; @@ -470,6 +471,7 @@ oxc_macros::declare_all_lint_rules! { typescript::array_type, typescript::ban_ts_comment, typescript::ban_tslint_comment, + typescript::prefer_enum_initializers, typescript::ban_types, typescript::consistent_type_definitions, typescript::no_duplicate_enum_values, diff --git a/crates/oxc_linter/src/rules/typescript/prefer_enum_initializers.rs b/crates/oxc_linter/src/rules/typescript/prefer_enum_initializers.rs new file mode 100644 index 000000000000..c986df5b9cb2 --- /dev/null +++ b/crates/oxc_linter/src/rules/typescript/prefer_enum_initializers.rs @@ -0,0 +1,110 @@ +use oxc_ast::{ast::TSEnumMemberName, AstKind}; +use oxc_diagnostics::{ + miette::{self, Diagnostic}, + thiserror::Error, +}; +use oxc_macros::declare_oxc_lint; +use oxc_span::{CompactStr, Span}; + +use crate::{context::LintContext, rule::Rule, AstNode}; + +#[derive(Debug, Error, Diagnostic)] +#[error("typescript-eslint(prefer-enum-initializers):The value of the member {0:?} should be explicitly defined.")] +#[diagnostic(severity(warning), help("Can be fixed to {0:?} = {1:?}."))] +struct PreferEnumInitializersDiagnostic(CompactStr, usize, #[label] pub Span); + +#[derive(Debug, Default, Clone)] +pub struct PreferEnumInitializers; + +declare_oxc_lint!( + /// ### What it does + /// Require each enum member value to be explicitly initialized. + /// + /// ### Why is this bad? + /// In projects where the value of `enum` members are important, allowing implicit values for enums can cause bugs if enums are modified over time. + /// + /// ### Example + /// ```typescript + /// // wrong, the value of `Close` is not constant + /// enum Status { + /// Open = 1, + /// Close, + /// } + /// ``` + PreferEnumInitializers, + pedantic +); + +impl Rule for PreferEnumInitializers { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + let AstKind::TSEnumDeclaration(decl) = node.kind() else { return }; + + for (index, member) in decl.members.iter().enumerate() { + if member.initializer.is_none() { + if let TSEnumMemberName::Identifier(i) = &member.id { + ctx.diagnostic(PreferEnumInitializersDiagnostic( + i.name.to_compact_str(), + index + 1, + member.span, + )); + } + } + } + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + " + enum Direction {} + ", + " + enum Direction { + Up = 1, + } + ", + " + enum Direction { + Up = 1, + Down = 2, + } + ", + " + enum Direction { + Up = 'Up', + Down = 'Down', + } + ", + ]; + + let fail = vec![ + " + enum Direction { + Up, + } + ", + " + enum Direction { + Up, + Down, + } + ", + " + enum Direction { + Up = 'Up', + Down, + } + ", + " + enum Direction { + Up, + Down = 'Down', + } + ", + ]; + + Tester::new(PreferEnumInitializers::NAME, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/prefer_enum_initializers.snap b/crates/oxc_linter/src/snapshots/prefer_enum_initializers.snap new file mode 100644 index 000000000000..b9eb6c5d179e --- /dev/null +++ b/crates/oxc_linter/src/snapshots/prefer_enum_initializers.snap @@ -0,0 +1,48 @@ +--- +source: crates/oxc_linter/src/tester.rs +expression: prefer_enum_initializers +--- + ⚠ typescript-eslint(prefer-enum-initializers):The value of the member "Up" should be explicitly defined. + ╭─[prefer_enum_initializers.tsx:3:6] + 2 │ enum Direction { + 3 │ Up, + · ── + 4 │ } + ╰──── + help: Can be fixed to "Up" = 1. + + ⚠ typescript-eslint(prefer-enum-initializers):The value of the member "Up" should be explicitly defined. + ╭─[prefer_enum_initializers.tsx:3:6] + 2 │ enum Direction { + 3 │ Up, + · ── + 4 │ Down, + ╰──── + help: Can be fixed to "Up" = 1. + + ⚠ typescript-eslint(prefer-enum-initializers):The value of the member "Down" should be explicitly defined. + ╭─[prefer_enum_initializers.tsx:4:6] + 3 │ Up, + 4 │ Down, + · ──── + 5 │ } + ╰──── + help: Can be fixed to "Down" = 2. + + ⚠ typescript-eslint(prefer-enum-initializers):The value of the member "Down" should be explicitly defined. + ╭─[prefer_enum_initializers.tsx:4:6] + 3 │ Up = 'Up', + 4 │ Down, + · ──── + 5 │ } + ╰──── + help: Can be fixed to "Down" = 2. + + ⚠ typescript-eslint(prefer-enum-initializers):The value of the member "Up" should be explicitly defined. + ╭─[prefer_enum_initializers.tsx:3:6] + 2 │ enum Direction { + 3 │ Up, + · ── + 4 │ Down = 'Down', + ╰──── + help: Can be fixed to "Up" = 1.