From 0d2c977c11de85de07591f524a1d84ec15fac26b Mon Sep 17 00:00:00 2001 From: Wang Wenzhe Date: Tue, 28 May 2024 22:50:54 +0800 Subject: [PATCH] feat(linter): add `oxc/no-const-enum` rule (#3435) --- crates/oxc_ast/src/ast/ts.rs | 7 ++ crates/oxc_linter/src/rules.rs | 2 + .../oxc_linter/src/rules/oxc/no_const_enum.rs | 84 +++++++++++++++++++ .../src/snapshots/no_const_enum.snap | 10 +++ 4 files changed, 103 insertions(+) create mode 100644 crates/oxc_linter/src/rules/oxc/no_const_enum.rs create mode 100644 crates/oxc_linter/src/snapshots/no_const_enum.snap diff --git a/crates/oxc_ast/src/ast/ts.rs b/crates/oxc_ast/src/ast/ts.rs index e0b7b5cf4..7cbbdb32c 100644 --- a/crates/oxc_ast/src/ast/ts.rs +++ b/crates/oxc_ast/src/ast/ts.rs @@ -1288,6 +1288,13 @@ impl<'a> Modifiers<'a> { .map_or(false, |modifiers| modifiers.iter().any(|modifier| modifier.kind == target)) } + pub fn find(&self, f: F) -> Option<&Modifier> + where + F: Fn(&Modifier) -> bool, + { + self.0.as_ref().and_then(|modifiers| modifiers.iter().find(|modifier| f(modifier))) + } + pub fn is_contains_declare(&self) -> bool { self.contains(ModifierKind::Declare) } diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index d1400ca8a..cc0a1e80e 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -350,6 +350,7 @@ mod oxc { pub mod no_accumulating_spread; pub mod no_async_await; pub mod no_barrel_file; + pub mod no_const_enum; pub mod number_arg_out_of_range; pub mod only_used_in_recursion; pub mod uninvoked_array_callback; @@ -718,6 +719,7 @@ oxc_macros::declare_all_lint_rules! { oxc::missing_throw, oxc::no_accumulating_spread, oxc::no_barrel_file, + oxc::no_const_enum, oxc::number_arg_out_of_range, oxc::only_used_in_recursion, oxc::no_async_await, diff --git a/crates/oxc_linter/src/rules/oxc/no_const_enum.rs b/crates/oxc_linter/src/rules/oxc/no_const_enum.rs new file mode 100644 index 000000000..f3156b0fd --- /dev/null +++ b/crates/oxc_linter/src/rules/oxc/no_const_enum.rs @@ -0,0 +1,84 @@ +use oxc_ast::{ast::ModifierKind, AstKind}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; + +use crate::{context::LintContext, fixer::Fix, rule::Rule, AstNode}; + +fn no_const_enum_diagnostic(span0: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("oxc(no-const-enum): Unexpected const enum") + .with_help("Const enums are not supported by bundlers and are incompatible with the isolatedModules mode. Their use can lead to import nonexistent values (because const enums are erased).") + .with_labels([span0.into()]) +} + +#[derive(Debug, Default, Clone)] +pub struct NoConstEnum; + +// Ported from +declare_oxc_lint!( + /// ### What it does + /// + /// Disallow TypeScript `const enum` + /// + /// ### Why is this bad? + /// + /// Const enums are enums that should be inlined at use sites. + /// Const enums are not supported by bundlers and are incompatible with the isolatedModules mode. + /// Their use can lead to import nonexistent values (because const enums are erased). + /// + /// ### Example + /// ```javascript + /// const enum Color { + /// Red, + /// Green, + /// Blue + /// } + /// ``` + NoConstEnum, + restriction, +); + +impl Rule for NoConstEnum { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + if let AstKind::TSEnumDeclaration(enum_decl) = node.kind() { + let Some(const_enum) = + enum_decl.modifiers.find(|modifier| matches!(modifier.kind, ModifierKind::Const)) + else { + return; + }; + + ctx.diagnostic_with_fix(no_const_enum_diagnostic(const_enum.span), || { + // const enum Color { Red, Green, Blue } + // ^ + let start = const_enum.span.start; + + // const enum Color { Red, Green, Blue } + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + let text = Span::new(start, enum_decl.span.end).source_text(ctx.source_text()); + + // const enum Color { Red, Green, Blue } + // ^^^^^^ + let offset = u32::try_from(text.find("enum").unwrap_or(1)).unwrap_or(1); // 1 is the default offset + + let end = start + offset; + Fix::delete(Span::new(start, end)) + }); + } + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec!["enum Color { Red, Green, Blue }"]; + + let fail = vec!["const enum Color { Red, Green, Blue }"]; + + let fix = vec![ + ("const enum Color { Red, Green, Blue }", "enum Color { Red, Green, Blue }", None), + ("const enum Color { Red, Green, Blue }", "enum Color { Red, Green, Blue }", None), + ]; + + Tester::new(NoConstEnum::NAME, pass, fail).expect_fix(fix).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/no_const_enum.snap b/crates/oxc_linter/src/snapshots/no_const_enum.snap new file mode 100644 index 000000000..abf712fee --- /dev/null +++ b/crates/oxc_linter/src/snapshots/no_const_enum.snap @@ -0,0 +1,10 @@ +--- +source: crates/oxc_linter/src/tester.rs +expression: no_const_enum +--- + ⚠ oxc(no-const-enum): Unexpected const enum + ╭─[no_const_enum.tsx:1:1] + 1 │ const enum Color { Red, Green, Blue } + · ───── + ╰──── + help: Const enums are not supported by bundlers and are incompatible with the isolatedModules mode. Their use can lead to import nonexistent values (because const enums are erased).