mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
feat(linter): implement no-misused-new (#525)
This commit is contained in:
parent
6614b46f26
commit
1aaeb794a4
3 changed files with 198 additions and 1 deletions
|
|
@ -58,7 +58,8 @@ oxc_macros::declare_all_lint_rules! {
|
|||
typescript::no_empty_interface,
|
||||
typescript::no_extra_non_null_assertion,
|
||||
typescript::no_non_null_asserted_optional_chain,
|
||||
typescript::no_unnecessary_type_constraint
|
||||
typescript::no_unnecessary_type_constraint,
|
||||
typescript::no_misused_new
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
142
crates/oxc_linter/src/rules/typescript/no_misused_new.rs
Normal file
142
crates/oxc_linter/src/rules/typescript/no_misused_new.rs
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
use oxc_ast::{ast::{TSSignature, TSType, TSTypeName, PropertyKey, ClassElement}, AstKind};
|
||||
use oxc_diagnostics::{
|
||||
miette::{self, Diagnostic},
|
||||
thiserror::Error,
|
||||
};
|
||||
use oxc_macros::declare_oxc_lint;
|
||||
use oxc_span::{Span, GetSpan};
|
||||
|
||||
use crate::{context::LintContext, rule::Rule, AstNode};
|
||||
|
||||
#[derive(Debug, Error, Diagnostic)]
|
||||
#[error("typescript-eslint(no-misused-new): Interfaces cannot be constructed, only classes.")]
|
||||
#[diagnostic(
|
||||
severity(warning),
|
||||
help("Consider removing this method from your interface.")
|
||||
)]
|
||||
struct NoMisusedNewInterfaceDiagnostic(#[label] pub Span);
|
||||
|
||||
#[derive(Debug, Error, Diagnostic)]
|
||||
#[error("typescript-eslint(no-misused-new): Class cannot have method named `new`.")]
|
||||
#[diagnostic(
|
||||
severity(warning),
|
||||
help("This method name is confusing, consider renaming the method to `constructor`")
|
||||
)]
|
||||
struct NoMisusedNewClassDiagnostic(#[label] pub Span);
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct NoMisusedNew;
|
||||
|
||||
declare_oxc_lint!(
|
||||
/// ### What it does
|
||||
///
|
||||
/// Enforce valid definition of `new` and `constructor`
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
///
|
||||
/// JavaScript classes may define a constructor method that runs
|
||||
/// when a class instance is newly created.
|
||||
/// TypeScript allows interfaces that describe a static class object to define
|
||||
/// a new() method (though this is rarely used in real world code).
|
||||
/// Developers new to JavaScript classes and/or TypeScript interfaces may
|
||||
/// sometimes confuse when to use constructor or new.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```typescript
|
||||
// declare class C {
|
||||
// new(): C;
|
||||
// }
|
||||
|
||||
// interface I {
|
||||
// new (): I;
|
||||
// constructor(): void;
|
||||
// }
|
||||
/// ```
|
||||
NoMisusedNew,
|
||||
correctness
|
||||
);
|
||||
|
||||
|
||||
impl Rule for NoMisusedNew {
|
||||
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
|
||||
match node.kind() {
|
||||
AstKind::TSInterfaceDeclaration(interface_decl) => {
|
||||
let decl_name = &interface_decl.id.name;
|
||||
|
||||
for signature in &interface_decl.body.body {
|
||||
if let TSSignature::TSConstructSignatureDeclaration(sig) = signature &&
|
||||
let Some(return_type) = &sig.return_type &&
|
||||
let TSType::TSTypeReference(type_ref) = &return_type.type_annotation &&
|
||||
let TSTypeName::IdentifierName(id) = &type_ref.type_name &&
|
||||
id.name == decl_name {
|
||||
|
||||
ctx.diagnostic(NoMisusedNewInterfaceDiagnostic(
|
||||
Span::new(sig.span.start, sig.span.start + 3)
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
AstKind::TSMethodSignature(method_sig) => {
|
||||
if let PropertyKey::Identifier(id) = &method_sig.key {
|
||||
if id.name == "constructor" {
|
||||
ctx.diagnostic(NoMisusedNewInterfaceDiagnostic(method_sig.key.span()));
|
||||
}
|
||||
}
|
||||
}
|
||||
AstKind::Class(cls) => {
|
||||
if let Some(cls_id) = &cls.id {
|
||||
let cls_name = &cls_id.name;
|
||||
|
||||
for element in &cls.body.body {
|
||||
if let ClassElement::MethodDefinition(method) = element &&
|
||||
let PropertyKey::Identifier(id) = &method.key &&
|
||||
id.name == "new" &&
|
||||
method.value.body.is_none() &&
|
||||
let Some(return_type) = &method.value.return_type &&
|
||||
let TSType::TSTypeReference(type_ref) = &return_type.type_annotation &&
|
||||
let TSTypeName::IdentifierName(current_id) = &type_ref.type_name &&
|
||||
current_id.name == cls_name {
|
||||
|
||||
ctx.diagnostic(NoMisusedNewClassDiagnostic(method.key.span()));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
use crate::tester::Tester;
|
||||
|
||||
let pass = vec![
|
||||
"declare abstract class C { foo() {} get new();bar();}",
|
||||
"class C { constructor();}",
|
||||
"const foo = class { constructor();};",
|
||||
"const foo = class { new(): X;};",
|
||||
"class C { new() {} }",
|
||||
"class C { constructor() {} }",
|
||||
"const foo = class { new() {} };",
|
||||
"const foo = class { constructor() {} };",
|
||||
"interface I { new (): {}; }",
|
||||
"type T = { new (): T };",
|
||||
"export default class { constructor(); }",
|
||||
"interface foo { new <T>(): bar<T>; }",
|
||||
"interface foo { new <T>(): 'x'; }"
|
||||
];
|
||||
|
||||
let fail = vec![
|
||||
"interface I { new (): I; constructor(): void;}",
|
||||
"interface G { new <T>(): G<T>;}",
|
||||
"type T = { constructor(): void;};",
|
||||
"class C { new(): C;}",
|
||||
"declare abstract class C { new(): C;}",
|
||||
"interface I { constructor(): '';}"
|
||||
];
|
||||
|
||||
Tester::new_without_config(NoMisusedNew::NAME, pass, fail).test_and_snapshot();
|
||||
}
|
||||
54
crates/oxc_linter/src/snapshots/no_misused_new.snap
Normal file
54
crates/oxc_linter/src/snapshots/no_misused_new.snap
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
---
|
||||
source: crates/oxc_linter/src/tester.rs
|
||||
expression: no_misused_new
|
||||
---
|
||||
⚠ typescript-eslint(no-misused-new): Interfaces cannot be constructed, only classes.
|
||||
╭─[no_misused_new.tsx:1:1]
|
||||
1 │ interface I { new (): I; constructor(): void;}
|
||||
· ───
|
||||
╰────
|
||||
help: Consider removing this method from your interface.
|
||||
|
||||
⚠ typescript-eslint(no-misused-new): Interfaces cannot be constructed, only classes.
|
||||
╭─[no_misused_new.tsx:1:1]
|
||||
1 │ interface I { new (): I; constructor(): void;}
|
||||
· ───────────
|
||||
╰────
|
||||
help: Consider removing this method from your interface.
|
||||
|
||||
⚠ typescript-eslint(no-misused-new): Interfaces cannot be constructed, only classes.
|
||||
╭─[no_misused_new.tsx:1:1]
|
||||
1 │ interface G { new <T>(): G<T>;}
|
||||
· ───
|
||||
╰────
|
||||
help: Consider removing this method from your interface.
|
||||
|
||||
⚠ typescript-eslint(no-misused-new): Interfaces cannot be constructed, only classes.
|
||||
╭─[no_misused_new.tsx:1:1]
|
||||
1 │ type T = { constructor(): void;};
|
||||
· ───────────
|
||||
╰────
|
||||
help: Consider removing this method from your interface.
|
||||
|
||||
⚠ typescript-eslint(no-misused-new): Class cannot have method named `new`.
|
||||
╭─[no_misused_new.tsx:1:1]
|
||||
1 │ class C { new(): C;}
|
||||
· ───
|
||||
╰────
|
||||
help: This method name is confusing, consider renaming the method to `constructor`
|
||||
|
||||
⚠ typescript-eslint(no-misused-new): Class cannot have method named `new`.
|
||||
╭─[no_misused_new.tsx:1:1]
|
||||
1 │ declare abstract class C { new(): C;}
|
||||
· ───
|
||||
╰────
|
||||
help: This method name is confusing, consider renaming the method to `constructor`
|
||||
|
||||
⚠ typescript-eslint(no-misused-new): Interfaces cannot be constructed, only classes.
|
||||
╭─[no_misused_new.tsx:1:1]
|
||||
1 │ interface I { constructor(): '';}
|
||||
· ───────────
|
||||
╰────
|
||||
help: Consider removing this method from your interface.
|
||||
|
||||
|
||||
Loading…
Reference in a new issue