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

This commit is contained in:
Aza Walker 2024-07-29 19:02:11 -04:00 committed by GitHub
parent e6a8af6112
commit 4c4da561f4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 152 additions and 0 deletions

View file

@ -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,

View file

@ -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();
}

View file

@ -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`.