From 2292606a1fecc3341c8be8bbda6c4d4caaef5bfa Mon Sep 17 00:00:00 2001 From: camc314 <18101008+camc314@users.noreply.github.com> Date: Tue, 20 Aug 2024 21:45:38 +0000 Subject: [PATCH] feat(linter): typescript-eslint/no-wrapper-object-types (#5022) --- crates/oxc_linter/src/rules.rs | 2 + .../typescript/no_wrapper_object_types.rs | 196 ++++++++++++++++++ .../snapshots/no_wrapper_object_types.snap | 112 ++++++++++ 3 files changed, 310 insertions(+) create mode 100644 crates/oxc_linter/src/rules/typescript/no_wrapper_object_types.rs create mode 100644 crates/oxc_linter/src/snapshots/no_wrapper_object_types.snap diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 5c6670772234e..3db35d40aba20 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -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; @@ -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, diff --git a/crates/oxc_linter/src/rules/typescript/no_wrapper_object_types.rs b/crates/oxc_linter/src/rules/typescript/no_wrapper_object_types.rs new file mode 100644 index 0000000000000..53d21b2705aad --- /dev/null +++ b/crates/oxc_linter/src/rules/typescript/no_wrapper_object_types.rs @@ -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; + type U = UU extends T ? 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(); +} diff --git a/crates/oxc_linter/src/snapshots/no_wrapper_object_types.snap b/crates/oxc_linter/src/snapshots/no_wrapper_object_types.snap new file mode 100644 index 0000000000000..f693080fa3b79 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/no_wrapper_object_types.snap @@ -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`.