Skip to content

Commit

Permalink
feat(linter): typescript-eslint/no-wrapper-object-types (#5022)
Browse files Browse the repository at this point in the history
  • Loading branch information
camc314 committed Aug 20, 2024
1 parent c43945c commit 2292606
Show file tree
Hide file tree
Showing 3 changed files with 310 additions and 0 deletions.
2 changes: 2 additions & 0 deletions crates/oxc_linter/src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ mod typescript {
pub mod no_unsafe_declaration_merging;
pub mod no_useless_empty_export;
pub mod no_var_requires;
pub mod no_wrapper_object_types;
pub mod prefer_as_const;
pub mod prefer_enum_initializers;
pub mod prefer_for_of;
Expand Down Expand Up @@ -584,6 +585,7 @@ oxc_macros::declare_all_lint_rules! {
typescript::no_unsafe_declaration_merging,
typescript::no_useless_empty_export,
typescript::no_var_requires,
typescript::no_wrapper_object_types,
typescript::prefer_as_const,
typescript::prefer_for_of,
typescript::prefer_function_type,
Expand Down
196 changes: 196 additions & 0 deletions crates/oxc_linter/src/rules/typescript/no_wrapper_object_types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
use oxc_ast::{
ast::{Expression, TSTypeName},
AstKind,
};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;

use crate::{context::LintContext, rule::Rule, AstNode};

fn no_wrapper_object_types(span0: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("Do not use wrapper object types.").with_label(span0)
}

#[derive(Debug, Default, Clone)]
pub struct NoWrapperObjectTypes;

declare_oxc_lint!(
/// ### What it does
///
/// Disallow the use of wrapper object types.
///
/// ### Why is this bad?
///
/// Wrapper object types are types that are defined in the global scope and are not primitive types. These types are not recommended to be used in TypeScript code.
///
/// ### Examples
///
/// Examples of **incorrect** code for this rule:
/// ```ts
/// let myBigInt: BigInt;
/// let myBoolean: Boolean;
/// let myNumber: Number;
/// let myString: String;
/// let mySymbol: Symbol;
///
/// let myObject: Object = 'allowed by TypeScript';
/// ```
///
/// Examples of **correct** code for this rule:
/// ```ts
/// let myBigint: bigint;
/// let myBoolean: boolean;
/// let myNumber: number;
/// let myString: string;
/// let mySymbol: symbol;
///
/// let myObject: object = "Type 'string' is not assignable to type 'object'.";
/// ```
NoWrapperObjectTypes,
correctness,
fix
);

impl Rule for NoWrapperObjectTypes {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
let (ident_name, ident_span, reference_id) = match node.kind() {
AstKind::TSTypeReference(type_ref) => {
if let TSTypeName::IdentifierReference(type_name) = &type_ref.type_name {
(type_name.name.as_str(), type_name.span, type_name.reference_id())
} else {
return;
}
}
AstKind::TSClassImplements(ts_class_implements) => {
if let TSTypeName::IdentifierReference(type_name) = &ts_class_implements.expression
{
(type_name.name.as_str(), type_name.span, type_name.reference_id())
} else {
return;
}
}
AstKind::TSInterfaceHeritage(ts_interface_heritage) => {
if let Expression::Identifier(extends) = &ts_interface_heritage.expression {
(extends.name.as_str(), extends.span, extends.reference_id())
} else {
return;
}
}
_ => {
return;
}
};

if matches!(ident_name, "BigInt" | "Boolean" | "Number" | "Object" | "String" | "Symbol") {
if reference_id.and_then(|v| ctx.symbols().get_reference(v).symbol_id()).is_some() {
return;
}

let can_fix = matches!(node.kind(), AstKind::TSTypeReference(_));

if can_fix {
ctx.diagnostic_with_fix(no_wrapper_object_types(ident_span), |fixer| {
fixer.replace(ident_span, ident_name.to_lowercase())
});
} else {
ctx.diagnostic(no_wrapper_object_types(ident_span));
}
}
}
}

#[test]
fn test() {
use crate::tester::Tester;

let pass = vec![
"let value: NumberLike;",
"let value: Other;",
"let value: bigint;",
"let value: boolean;",
"let value: never;",
"let value: null;",
"let value: number;",
"let value: symbol;",
"let value: undefined;",
"let value: unknown;",
"let value: void;",
"let value: () => void;",
"let value: () => () => void;",
"let Bigint;",
"let Boolean;",
"let Never;",
"let Null;",
"let Number;",
"let Symbol;",
"let Undefined;",
"let Unknown;",
"let Void;",
"interface Bigint {}",
"interface Boolean {}",
"interface Never {}",
"interface Null {}",
"interface Number {}",
"interface Symbol {}",
"interface Undefined {}",
"interface Unknown {}",
"interface Void {}",
"type Bigint = {};",
"type Boolean = {};",
"type Never = {};",
"type Null = {};",
"type Number = {};",
"type Symbol = {};",
"type Undefined = {};",
"type Unknown = {};",
"type Void = {};",
"class MyClass extends Number {}",
"
type Number = 0 | 1;
let value: Number;
",
"
type Bigint = 0 | 1;
let value: Bigint;
",
"
type T<Symbol> = Symbol;
type U<UU> = UU extends T<infer Function> ? Function : never;
",
];

let fail = vec![
"let value: BigInt;",
"let value: Boolean;",
"let value: Number;",
"let value: Object;",
"let value: String;",
"let value: Symbol;",
"let value: Number | Symbol;",
"let value: { property: Number };",
"0 as Number;",
"type MyType = Number;",
"type MyType = [Number];",
"class MyClass implements Number {}",
"interface MyInterface extends Number {}",
"type MyType = Number & String;",
];

let fix = vec![
("let value: BigInt;", "let value: bigint;", None),
("let value: Boolean;", "let value: boolean;", None),
("let value: Number;", "let value: number;", None),
("let value: Object;", "let value: object;", None),
("let value: String;", "let value: string;", None),
("let value: Symbol;", "let value: symbol;", None),
("let value: Number | Symbol;", "let value: number | symbol;", None),
("let value: { property: Number };", "let value: { property: number };", None),
("0 as Number;", "0 as number;", None),
("type MyType = Number;", "type MyType = number;", None),
("type MyType = [Number];", "type MyType = [number];", None),
("type MyType = Number & String;", "type MyType = number & string;", None),
];

Tester::new(NoWrapperObjectTypes::NAME, pass, fail).expect_fix(fix).test_and_snapshot();
}
112 changes: 112 additions & 0 deletions crates/oxc_linter/src/snapshots/no_wrapper_object_types.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
---
source: crates/oxc_linter/src/tester.rs
---
typescript-eslint(no-wrapper-object-types): Do not use wrapper object types.
╭─[no_wrapper_object_types.tsx:1:12]
1let value: BigInt;
· ──────
╰────
help: Replace `BigInt` with `bigint`.

typescript-eslint(no-wrapper-object-types): Do not use wrapper object types.
╭─[no_wrapper_object_types.tsx:1:12]
1let value: Boolean;
· ───────
╰────
help: Replace `Boolean` with `boolean`.

typescript-eslint(no-wrapper-object-types): Do not use wrapper object types.
╭─[no_wrapper_object_types.tsx:1:12]
1let value: Number;
· ──────
╰────
help: Replace `Number` with `number`.

typescript-eslint(no-wrapper-object-types): Do not use wrapper object types.
╭─[no_wrapper_object_types.tsx:1:12]
1let value: Object;
· ──────
╰────
help: Replace `Object` with `object`.

typescript-eslint(no-wrapper-object-types): Do not use wrapper object types.
╭─[no_wrapper_object_types.tsx:1:12]
1let value: String;
· ──────
╰────
help: Replace `String` with `string`.

typescript-eslint(no-wrapper-object-types): Do not use wrapper object types.
╭─[no_wrapper_object_types.tsx:1:12]
1let value: Symbol;
· ──────
╰────
help: Replace `Symbol` with `symbol`.

typescript-eslint(no-wrapper-object-types): Do not use wrapper object types.
╭─[no_wrapper_object_types.tsx:1:12]
1let value: Number | Symbol;
· ──────
╰────
help: Replace `Number` with `number`.

typescript-eslint(no-wrapper-object-types): Do not use wrapper object types.
╭─[no_wrapper_object_types.tsx:1:21]
1let value: Number | Symbol;
· ──────
╰────
help: Replace `Symbol` with `symbol`.

typescript-eslint(no-wrapper-object-types): Do not use wrapper object types.
╭─[no_wrapper_object_types.tsx:1:24]
1let value: { property: Number };
· ──────
╰────
help: Replace `Number` with `number`.

typescript-eslint(no-wrapper-object-types): Do not use wrapper object types.
╭─[no_wrapper_object_types.tsx:1:6]
10 as Number;
· ──────
╰────
help: Replace `Number` with `number`.

typescript-eslint(no-wrapper-object-types): Do not use wrapper object types.
╭─[no_wrapper_object_types.tsx:1:15]
1type MyType = Number;
· ──────
╰────
help: Replace `Number` with `number`.

typescript-eslint(no-wrapper-object-types): Do not use wrapper object types.
╭─[no_wrapper_object_types.tsx:1:16]
1type MyType = [Number];
· ──────
╰────
help: Replace `Number` with `number`.

typescript-eslint(no-wrapper-object-types): Do not use wrapper object types.
╭─[no_wrapper_object_types.tsx:1:26]
1class MyClass implements Number {}
· ──────
╰────

typescript-eslint(no-wrapper-object-types): Do not use wrapper object types.
╭─[no_wrapper_object_types.tsx:1:31]
1interface MyInterface extends Number {}
· ──────
╰────

typescript-eslint(no-wrapper-object-types): Do not use wrapper object types.
╭─[no_wrapper_object_types.tsx:1:15]
1type MyType = Number & String;
· ──────
╰────
help: Replace `Number` with `number`.

typescript-eslint(no-wrapper-object-types): Do not use wrapper object types.
╭─[no_wrapper_object_types.tsx:1:24]
1type MyType = Number & String;
· ──────
╰────
help: Replace `String` with `string`.

0 comments on commit 2292606

Please sign in to comment.