Skip to content
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 @@ -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]
1 │ let 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]
1 │ let 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]
1 │ let 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]
1 │ let 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]
1 │ let 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]
1 │ let 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]
1 │ let 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]
1 │ let 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]
1 │ let 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]
1 │ 0 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]
1 │ type 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]
1 │ type 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]
1 │ class MyClass implements Number {}
· ──────
╰────

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

⚠ typescript-eslint(no-wrapper-object-types): Do not use wrapper object types.
╭─[no_wrapper_object_types.tsx:1:15]
1 │ type 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]
1 │ type MyType = Number & String;
· ──────
╰────
help: Replace `String` with `string`.