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): implement @typescript-eslint/prefer-namespace-keyword #3881

Closed
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 @@ -148,6 +148,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 @@ -542,6 +543,7 @@ oxc_macros::declare_all_lint_rules! {
typescript::prefer_literal_enum_member,
typescript::explicit_function_return_type,
typescript::no_non_null_assertion,
typescript::prefer_namespace_keyword,
jest::expect_expect,
jest::max_expects,
jest::max_nested_describe,
Expand Down
95 changes: 95 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,95 @@
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};

#[derive(Debug, Default, Clone)]
pub struct PreferNamespaceKeyword;

declare_oxc_lint!(
/// ### What it does
/// Require using namespace keyword over module keyword to declare custom TypeScript modules.
///
/// ### Why is this bad?
/// TypeScript historically allowed a form of code organization called "custom modules" (module Example {}), later renamed to "namespaces" (namespace Example).
///
/// 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. This rule reports when the module keyword is used instead of namespace.
///
/// ### Example
/// ```javascript
/// module foo {}
/// declare module foo {}
/// ```
PreferNamespaceKeyword,
restriction,
);

fn prefer_namespace_keyword_diagnostic(span0: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("typescript-eslint(prefer-namespace-keyword): Use 'namespace' instead of 'module' to declare custom TypeScript modules.")
.with_help("'Require using `namespace` keyword over `module` keyword to declare custom TypeScript modules")
.with_labels([span0.into()])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prefer with_label when only labelling a single span

}

impl Rule for PreferNamespaceKeyword {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
let AstKind::TSModuleDeclaration(decl) = node.kind() else { return };
if decl.kind != TSModuleDeclarationKind::Module {
return;
}
let TSModuleDeclarationName::Identifier(id) = &decl.id else { return };

ctx.diagnostic_with_fix(prefer_namespace_keyword_diagnostic(decl.span), |fixer| {
// replace `module` with `namespace`
fixer.replace(Span::new(id.span.start - 7, id.span.start - 1), "namespace")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like it will break if there is more than once space between module and the identifier. Is there no span for the module keyword?

});
}
}

#[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 {}
}
",
];

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();
}
35 changes: 35 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,35 @@
---
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: 'Require using `namespace` keyword over `module` keyword to declare custom TypeScript modules

⚠ 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: 'Require using `namespace` keyword over `module` keyword to declare custom TypeScript modules

⚠ typescript-eslint(prefer-namespace-keyword): Use 'namespace' instead of 'module' to declare custom TypeScript modules.
╭─[prefer_namespace_keyword.tsx:2:10]
1 │
2 │ ╭─▶ declare module foo {
3 │ │ declare module bar {}
4 │ ╰─▶ }
5 │
╰────
help: 'Require using `namespace` keyword over `module` keyword to declare custom TypeScript modules

⚠ typescript-eslint(prefer-namespace-keyword): Use 'namespace' instead of 'module' to declare custom TypeScript modules.
╭─[prefer_namespace_keyword.tsx:3:12]
2 │ declare module foo {
3 │ declare module bar {}
· ─────────────────────
4 │ }
╰────
help: 'Require using `namespace` keyword over `module` keyword to declare custom TypeScript modules
Loading