-
-
Notifications
You must be signed in to change notification settings - Fork 482
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(linter): @typescript-eslint/no-namespace (#703)
- Loading branch information
Showing
6 changed files
with
715 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,337 @@ | ||
use oxc_ast::{ | ||
ast::{ModifierKind, TSModuleDeclarationName}, | ||
AstKind, | ||
}; | ||
use oxc_diagnostics::{ | ||
miette::{self, Diagnostic}, | ||
thiserror::Error, | ||
}; | ||
use oxc_macros::declare_oxc_lint; | ||
use oxc_span::Span; | ||
|
||
use crate::{context::LintContext, rule::Rule, AstNode}; | ||
|
||
#[derive(Debug, Error, Diagnostic)] | ||
#[error("ES2015 module syntax is preferred over namespaces.")] | ||
#[diagnostic(severity(warning), help("Replace the namespace with an ES2015 module"))] | ||
struct NoNamespaceDiagnostic(#[label] pub Span); | ||
|
||
#[derive(Debug, Default, Clone)] | ||
pub struct NoNamespace { | ||
allow_declarations: bool, | ||
allow_definition_files: bool, | ||
} | ||
|
||
declare_oxc_lint!( | ||
/// ### What it does | ||
/// Disallow TypeScript namespaces. | ||
/// | ||
/// ### 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). | ||
/// | ||
/// ### Example | ||
/// ```typescript | ||
/// module foo {} | ||
/// namespace foo {} | ||
/// declare module foo {} | ||
/// declare namespace foo {} | ||
/// ``` | ||
NoNamespace, | ||
correctness | ||
); | ||
|
||
impl Rule for NoNamespace { | ||
fn from_configuration(value: serde_json::Value) -> Self { | ||
Self { | ||
allow_declarations: value | ||
.get(0) | ||
.and_then(|x| x.get("allowDeclarations")) | ||
.and_then(serde_json::Value::as_bool) | ||
.unwrap_or(false), | ||
allow_definition_files: value | ||
.get(0) | ||
.and_then(|x| x.get("allowDefinitionFiles")) | ||
.and_then(serde_json::Value::as_bool) | ||
.unwrap_or(false), | ||
} | ||
} | ||
|
||
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { | ||
let AstKind::TSModuleDeclaration(declaration) = node.kind() else { return }; | ||
let TSModuleDeclarationName::Identifier(ident) = &declaration.id else { return }; | ||
|
||
if ident.name == "global" { | ||
return; | ||
} | ||
|
||
if let Some(parent) = ctx.nodes().parent_node(node.id()) { | ||
if let AstKind::TSModuleDeclaration(_) = parent.kind() { | ||
return; | ||
} | ||
} | ||
|
||
if self.allow_declarations && is_declaration(node, ctx) { | ||
return; | ||
} | ||
|
||
if self.allow_definition_files && ctx.source_type().is_typescript_definition() { | ||
return; | ||
} | ||
|
||
ctx.diagnostic(NoNamespaceDiagnostic(declaration.span)); | ||
} | ||
} | ||
|
||
fn is_declaration(node: &AstNode, ctx: &LintContext) -> bool { | ||
ctx.nodes().iter_parents(node.id()).any(|node| { | ||
let AstKind::TSModuleDeclaration(declaration) = node.kind() else { return false }; | ||
declaration.modifiers.contains(ModifierKind::Declare) | ||
}) | ||
} | ||
|
||
#[test] | ||
#[allow(clippy::too_many_lines)] | ||
fn test() { | ||
use crate::tester::Tester; | ||
|
||
let pass = vec![ | ||
("declare global {}", None), | ||
("declare module 'foo' {}", None), | ||
("declare module foo {}", Some(serde_json::json!([{ "allowDeclarations": true }]))), | ||
("declare namespace foo {}", Some(serde_json::json!([{ "allowDeclarations": true }]))), | ||
( | ||
" | ||
declare global { | ||
namespace foo {} | ||
} | ||
", | ||
Some(serde_json::json!([{ "allowDeclarations": true }])), | ||
), | ||
( | ||
" | ||
declare module foo { | ||
namespace bar {} | ||
} | ||
", | ||
Some(serde_json::json!([{ "allowDeclarations": true }])), | ||
), | ||
( | ||
" | ||
declare global { | ||
namespace foo { | ||
namespace bar {} | ||
} | ||
} | ||
", | ||
Some(serde_json::json!([{ "allowDeclarations": true }])), | ||
), | ||
( | ||
" | ||
declare namespace foo { | ||
namespace bar { | ||
namespace baz {} | ||
} | ||
} | ||
", | ||
Some(serde_json::json!([{ "allowDeclarations": true }])), | ||
), | ||
( | ||
" | ||
export declare namespace foo { | ||
export namespace bar { | ||
namespace baz {} | ||
} | ||
} | ||
", | ||
Some(serde_json::json!([{ "allowDeclarations": true }])), | ||
), | ||
]; | ||
|
||
let fail = vec![ | ||
("module foo {}", None), | ||
("namespace foo {}", None), | ||
("module foo {}", Some(serde_json::json!([{ "allowDeclarations": false }]))), | ||
("namespace foo {}", Some(serde_json::json!([{ "allowDeclarations": false }]))), | ||
("module foo {}", Some(serde_json::json!([{ "allowDeclarations": true }]))), | ||
("namespace foo {}", Some(serde_json::json!([{ "allowDeclarations": true }]))), | ||
("declare module foo {}", None), | ||
("declare namespace foo {}", None), | ||
("declare module foo {}", Some(serde_json::json!([{ "allowDeclarations": false }]))), | ||
("declare namespace foo {}", Some(serde_json::json!([{ "allowDeclarations": false }]))), | ||
("namespace Foo.Bar {}", Some(serde_json::json!([{ "allowDeclarations": false }]))), | ||
( | ||
" | ||
namespace Foo.Bar { | ||
namespace Baz.Bas { | ||
interface X {} | ||
} | ||
} | ||
", | ||
None, | ||
), | ||
( | ||
" | ||
namespace A { | ||
namespace B { | ||
declare namespace C {} | ||
} | ||
} | ||
", | ||
Some(serde_json::json!([{ "allowDeclarations": true }])), | ||
), | ||
( | ||
" | ||
namespace A { | ||
namespace B { | ||
export declare namespace C {} | ||
} | ||
} | ||
", | ||
Some(serde_json::json!([{ "allowDeclarations": true }])), | ||
), | ||
( | ||
" | ||
namespace A { | ||
declare namespace B { | ||
namespace C {} | ||
} | ||
} | ||
", | ||
Some(serde_json::json!([{ "allowDeclarations": true }])), | ||
), | ||
( | ||
" | ||
namespace A { | ||
export declare namespace B { | ||
namespace C {} | ||
} | ||
} | ||
", | ||
Some(serde_json::json!([{ "allowDeclarations": true }])), | ||
), | ||
( | ||
" | ||
namespace A { | ||
export declare namespace B { | ||
declare namespace C {} | ||
} | ||
} | ||
", | ||
Some(serde_json::json!([{ "allowDeclarations": true }])), | ||
), | ||
( | ||
" | ||
namespace A { | ||
export declare namespace B { | ||
export declare namespace C {} | ||
} | ||
} | ||
", | ||
Some(serde_json::json!([{ "allowDeclarations": true }])), | ||
), | ||
( | ||
" | ||
namespace A { | ||
declare namespace B { | ||
export declare namespace C {} | ||
} | ||
} | ||
", | ||
Some(serde_json::json!([{ "allowDeclarations": true }])), | ||
), | ||
( | ||
" | ||
namespace A { | ||
export namespace B { | ||
export declare namespace C {} | ||
} | ||
} | ||
", | ||
Some(serde_json::json!([{ "allowDeclarations": true }])), | ||
), | ||
( | ||
" | ||
export namespace A { | ||
namespace B { | ||
declare namespace C {} | ||
} | ||
} | ||
", | ||
Some(serde_json::json!([{ "allowDeclarations": true }])), | ||
), | ||
( | ||
" | ||
export namespace A { | ||
namespace B { | ||
export declare namespace C {} | ||
} | ||
} | ||
", | ||
Some(serde_json::json!([{ "allowDeclarations": true }])), | ||
), | ||
( | ||
" | ||
export namespace A { | ||
declare namespace B { | ||
namespace C {} | ||
} | ||
} | ||
", | ||
Some(serde_json::json!([{ "allowDeclarations": true }])), | ||
), | ||
( | ||
" | ||
export namespace A { | ||
export declare namespace B { | ||
namespace C {} | ||
} | ||
} | ||
", | ||
Some(serde_json::json!([{ "allowDeclarations": true }])), | ||
), | ||
( | ||
" | ||
export namespace A { | ||
export declare namespace B { | ||
declare namespace C {} | ||
} | ||
} | ||
", | ||
Some(serde_json::json!([{ "allowDeclarations": true }])), | ||
), | ||
( | ||
" | ||
export namespace A { | ||
export declare namespace B { | ||
export declare namespace C {} | ||
} | ||
} | ||
", | ||
Some(serde_json::json!([{ "allowDeclarations": true }])), | ||
), | ||
( | ||
" | ||
export namespace A { | ||
declare namespace B { | ||
export declare namespace C {} | ||
} | ||
} | ||
", | ||
Some(serde_json::json!([{ "allowDeclarations": true }])), | ||
), | ||
( | ||
" | ||
export namespace A { | ||
export namespace B { | ||
export declare namespace C {} | ||
} | ||
} | ||
", | ||
Some(serde_json::json!([{ "allowDeclarations": true }])), | ||
), | ||
]; | ||
|
||
Tester::new(NoNamespace::NAME, pass, fail).test_and_snapshot(); | ||
} |
Oops, something went wrong.