Skip to content

Commit

Permalink
feat(linter): add typescript-eslint/prefer-keyword-namespce (#4438)
Browse files Browse the repository at this point in the history
  • Loading branch information
aowalke2 authored Jul 29, 2024
1 parent e6a8af6 commit 4c4da56
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 0 deletions.
2 changes: 2 additions & 0 deletions crates/oxc_linter/src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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,
Expand Down
106 changes: 106 additions & 0 deletions crates/oxc_linter/src/rules/typescript/prefer_namespace_keyword.rs
Original file line number Diff line number Diff line change
@@ -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();
}
44 changes: 44 additions & 0 deletions crates/oxc_linter/src/snapshots/prefer_namespace_keyword.snap
Original file line number Diff line number Diff line change
@@ -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]
1module 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]
1declare 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]
2declare module foo {
3declare 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]
1declare global {
2module foo {}
· ─────────────
3 │ }
╰────
help: Replace `module` with `namespace`.

0 comments on commit 4c4da56

Please sign in to comment.