feat(linter): implement no-misused-new (#525)

This commit is contained in:
阿良仔 2023-07-07 16:00:06 +08:00 committed by GitHub
parent 6614b46f26
commit 1aaeb794a4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 198 additions and 1 deletions

View file

@ -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)]

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

View 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.