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

@typescript-eslint/no-namespace #703

Merged
merged 5 commits into from
Aug 9, 2023
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 @@ -80,6 +80,7 @@ mod typescript {
pub mod no_empty_interface;
pub mod no_extra_non_null_assertion;
pub mod no_misused_new;
pub mod no_namespace;
pub mod no_non_null_asserted_optional_chain;
pub mod no_this_alias;
pub mod no_unnecessary_type_constraint;
Expand Down Expand Up @@ -164,6 +165,7 @@ oxc_macros::declare_all_lint_rules! {
typescript::no_unnecessary_type_constraint,
typescript::no_misused_new,
typescript::no_this_alias,
typescript::no_namespace,
typescript::no_var_requires,
jest::no_disabled_tests,
jest::no_test_prefixes,
Expand Down
337 changes: 337 additions & 0 deletions crates/oxc_linter/src/rules/typescript/no_namespace.rs
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();
}
Loading