Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(linter): add typescript-eslint/prefer-keyword-namespce #4438

Merged
merged 15 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]
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`.