From 4c4da561f45db9bf8a1ff49ef73b22d3ace1937e Mon Sep 17 00:00:00 2001 From: Aza Walker Date: Mon, 29 Jul 2024 19:02:11 -0400 Subject: [PATCH] feat(linter): add typescript-eslint/prefer-keyword-namespce (#4438) --- crates/oxc_linter/src/rules.rs | 2 + .../typescript/prefer_namespace_keyword.rs | 106 ++++++++++++++++++ .../snapshots/prefer_namespace_keyword.snap | 44 ++++++++ 3 files changed, 152 insertions(+) create mode 100644 crates/oxc_linter/src/rules/typescript/prefer_namespace_keyword.rs create mode 100644 crates/oxc_linter/src/snapshots/prefer_namespace_keyword.snap diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index acc46c04f88ed..790e06a2b48fc 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -158,6 +158,7 @@ mod typescript { pub mod prefer_for_of; pub mod prefer_function_type; pub mod prefer_literal_enum_member; + pub mod prefer_namespace_keyword; pub mod prefer_ts_expect_error; pub mod triple_slash_reference; } @@ -569,6 +570,7 @@ oxc_macros::declare_all_lint_rules! { typescript::prefer_as_const, typescript::prefer_for_of, typescript::prefer_function_type, + typescript::prefer_namespace_keyword, typescript::prefer_ts_expect_error, typescript::triple_slash_reference, typescript::prefer_literal_enum_member, diff --git a/crates/oxc_linter/src/rules/typescript/prefer_namespace_keyword.rs b/crates/oxc_linter/src/rules/typescript/prefer_namespace_keyword.rs new file mode 100644 index 0000000000000..1cc117533b414 --- /dev/null +++ b/crates/oxc_linter/src/rules/typescript/prefer_namespace_keyword.rs @@ -0,0 +1,106 @@ +use oxc_ast::{ + ast::{TSModuleDeclarationKind, TSModuleDeclarationName}, + AstKind, +}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; + +use crate::{context::LintContext, rule::Rule, AstNode}; + +fn prefer_namespace_keyword_diagnostic(span: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("Use 'namespace' instead of 'module' to declare custom TypeScript modules.") + .with_label(span) +} + +#[derive(Debug, Default, Clone)] +pub struct PreferNamespaceKeyword; + +declare_oxc_lint!( + /// ### What it does + /// This rule reports when the module keyword is used instead of namespace. + /// This rule does not report on the use of TypeScript module declarations to describe external APIs (declare module 'foo' {}). + /// + /// ### Why is this bad? + /// Namespaces are an outdated way to organize TypeScript code. ES2015 module syntax is now preferred (import/export). + /// For projects still using custom modules / namespaces, it's preferred to refer to them as namespaces. + /// + /// ### Example + /// ```typescript + /// module Example {} + /// ``` + PreferNamespaceKeyword, + style +); + +impl Rule for PreferNamespaceKeyword { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + let AstKind::TSModuleDeclaration(module) = node.kind() else { return }; + if module.id.is_string_literal() + || !matches!(module.id, TSModuleDeclarationName::Identifier(_)) + || module.kind != TSModuleDeclarationKind::Module + { + return; + } + + ctx.diagnostic_with_fix(prefer_namespace_keyword_diagnostic(module.span), |fixer| { + let span_size = u32::try_from("module".len()).unwrap_or(6); + let span_start = if module.declare { + module.span.start + u32::try_from("declare ".len()).unwrap_or(8) + } else { + module.span.start + }; + fixer.replace(Span::sized(span_start, span_size), "namespace") + }); + } + + fn should_run(&self, ctx: &LintContext) -> bool { + ctx.source_type().is_typescript() + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + "declare module 'foo';", + "declare module 'foo' {}", + "namespace foo {}", + "declare namespace foo {}", + "declare global {}", + ]; + + let fail = vec![ + "module foo {}", + "declare module foo {}", + " + declare module foo { + declare module bar {} + } + ", + "declare global { + module foo {} + } + ", + ]; + + let fix = vec![ + ("module foo {}", "namespace foo {}", None), + ("declare module foo {}", "declare namespace foo {}", None), + ( + " + declare module foo { + declare module bar {} + } + ", + " + declare namespace foo { + declare namespace bar {} + } + ", + None, + ), + ]; + Tester::new(PreferNamespaceKeyword::NAME, pass, fail).expect_fix(fix).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/prefer_namespace_keyword.snap b/crates/oxc_linter/src/snapshots/prefer_namespace_keyword.snap new file mode 100644 index 0000000000000..ef176c17bffc4 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/prefer_namespace_keyword.snap @@ -0,0 +1,44 @@ +--- +source: crates/oxc_linter/src/tester.rs +--- + ⚠ typescript-eslint(prefer-namespace-keyword): Use 'namespace' instead of 'module' to declare custom TypeScript modules. + ╭─[prefer_namespace_keyword.tsx:1:1] + 1 │ module foo {} + · ───────────── + ╰──── + help: Replace `module` with `namespace`. + + ⚠ typescript-eslint(prefer-namespace-keyword): Use 'namespace' instead of 'module' to declare custom TypeScript modules. + ╭─[prefer_namespace_keyword.tsx:1:1] + 1 │ declare module foo {} + · ───────────────────── + ╰──── + help: Replace `module` with `namespace`. + + ⚠ typescript-eslint(prefer-namespace-keyword): Use 'namespace' instead of 'module' to declare custom TypeScript modules. + ╭─[prefer_namespace_keyword.tsx:2:4] + 1 │ + 2 │ ╭─▶ declare module foo { + 3 │ │ declare module bar {} + 4 │ ╰─▶ } + 5 │ + ╰──── + help: Replace `module` with `namespace`. + + ⚠ typescript-eslint(prefer-namespace-keyword): Use 'namespace' instead of 'module' to declare custom TypeScript modules. + ╭─[prefer_namespace_keyword.tsx:3:6] + 2 │ declare module foo { + 3 │ declare module bar {} + · ───────────────────── + 4 │ } + ╰──── + help: Replace `module` with `namespace`. + + ⚠ typescript-eslint(prefer-namespace-keyword): Use 'namespace' instead of 'module' to declare custom TypeScript modules. + ╭─[prefer_namespace_keyword.tsx:2:13] + 1 │ declare global { + 2 │ module foo {} + · ───────────── + 3 │ } + ╰──── + help: Replace `module` with `namespace`.