-
-
Notifications
You must be signed in to change notification settings - Fork 496
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(linter): typescript-eslint/no-wrapper-object-types (#5022)
- Loading branch information
Showing
3 changed files
with
310 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
196 changes: 196 additions & 0 deletions
196
crates/oxc_linter/src/rules/typescript/no_wrapper_object_types.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
112
crates/oxc_linter/src/snapshots/no_wrapper_object_types.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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`. |