diff --git a/docs/maintenance/Issues.mdx b/docs/maintenance/Issues.mdx index 525147f88c54..8b39f5e5506d 100644 --- a/docs/maintenance/Issues.mdx +++ b/docs/maintenance/Issues.mdx @@ -163,7 +163,7 @@ For enhancements meant to limit which kinds of nodes the rule targets, mark the #### 🚀 New Rules We're generally accepting of new rules that meet the above feature request criteria. -The biggest exception is rules that can roughly be implemented with [`@typescript-eslint/ban-types`](/rules/ban-types) and/or [`no-restricted-syntax`](https://eslint.org/docs/latest/rules/no-restricted-syntax). +The biggest exception is rules that can roughly be implemented with [`@typescript-eslint/no-restricted-types`](/rules/no-restricted-types) and/or [`no-restricted-syntax`](https://eslint.org/docs/latest/rules/no-restricted-syntax). ## Pruning Old Issues diff --git a/packages/eslint-plugin/TSLINT_RULE_ALTERNATIVES.md b/packages/eslint-plugin/TSLINT_RULE_ALTERNATIVES.md index b462a35c0511..b0fc34b3fc5f 100644 --- a/packages/eslint-plugin/TSLINT_RULE_ALTERNATIVES.md +++ b/packages/eslint-plugin/TSLINT_RULE_ALTERNATIVES.md @@ -15,32 +15,32 @@ It lists all TSLint rules along side rules from the ESLint ecosystem that are th ### TypeScript-specific -| TSLint rule | | ESLint rule | -| --------------------------------- | :-: | ---------------------------------------------------- | -| [`adjacent-overload-signatures`] | ✅ | [`@typescript-eslint/adjacent-overload-signatures`] | -| [`ban-ts-ignore`] | ✅ | [`@typescript-eslint/ban-ts-comment`] | -| [`ban-types`] | 🌓 | [`@typescript-eslint/ban-types`][1] | -| [`invalid-void`] | ✅ | [`@typescript-eslint/no-invalid-void-type`] | -| [`member-access`] | ✅ | [`@typescript-eslint/explicit-member-accessibility`] | -| [`member-ordering`] | ✅ | [`@typescript-eslint/member-ordering`] | -| [`no-any`] | ✅ | [`@typescript-eslint/no-explicit-any`] | -| [`no-empty-interface`] | ✅ | [`@typescript-eslint/no-empty-object-type`] | -| [`no-import-side-effect`] | 🔌 | [`import/no-unassigned-import`] | -| [`no-inferrable-types`] | ✅ | [`@typescript-eslint/no-inferrable-types`] | -| [`no-internal-module`] | ✅ | [`@typescript-eslint/prefer-namespace-keyword`] | -| [`no-magic-numbers`] | ✅ | [`@typescript-eslint/no-magic-numbers`] | -| [`no-namespace`] | ✅ | [`@typescript-eslint/no-namespace`] | -| [`no-non-null-assertion`] | ✅ | [`@typescript-eslint/no-non-null-assertion`] | -| [`no-parameter-reassignment`] | ✅ | [`no-param-reassign`][no-param-reassign] | -| [`no-reference`] | ✅ | [`@typescript-eslint/triple-slash-reference`] | -| [`no-unnecessary-type-assertion`] | ✅ | [`@typescript-eslint/no-unnecessary-type-assertion`] | -| [`no-var-requires`] | ✅ | [`@typescript-eslint/no-var-requires`] | -| [`only-arrow-functions`] | 🔌 | [`prefer-arrow/prefer-arrow-functions`] | -| [`prefer-for-of`] | ✅ | [`@typescript-eslint/prefer-for-of`] | -| [`promise-function-async`] | ✅ | [`@typescript-eslint/promise-function-async`] | -| [`typedef-whitespace`] | ✅ | [`@typescript-eslint/type-annotation-spacing`] | -| [`typedef`] | ✅ | [`@typescript-eslint/typedef`] | -| [`unified-signatures`] | ✅ | [`@typescript-eslint/unified-signatures`] | +| TSLint rule | | ESLint rule | +| --------------------------------- | :-: | -------------------------------------------------------- | +| [`adjacent-overload-signatures`] | ✅ | [`@typescript-eslint/adjacent-overload-signatures`] | +| [`ban-ts-ignore`] | ✅ | [`@typescript-eslint/ban-ts-comment`] | +| [`ban-types`] | 🌓 | [`@typescript-eslint/no-restricted-types`][1] | +| [`invalid-void`] | ✅ | [`@typescript-eslint/no-invalid-void-type`] | +| [`member-access`] | ✅ | [`@typescript-eslint/explicit-member-accessibility`] | +| [`member-ordering`] | ✅ | [`@typescript-eslint/member-ordering`] | +| [`no-any`] | ✅ | [`@typescript-eslint/no-explicit-any`] | +| [`no-empty-interface`] | ✅ | [`@typescript-eslint/no-empty-object-type`] | +| [`no-import-side-effect`] | 🔌 | [`import/no-unassigned-import`] | +| [`no-inferrable-types`] | ✅ | [`@typescript-eslint/no-inferrable-types`] | +| [`no-internal-module`] | ✅ | [`@typescript-eslint/prefer-namespace-keyword`] | +| [`no-magic-numbers`] | ✅ | [`@typescript-eslint/no-magic-numbers`] | +| [`no-namespace`] | ✅ | [`@typescript-eslint/no-namespace`] | +| [`no-non-null-assertion`] | ✅ | [`@typescript-eslint/no-non-null-assertion`] | +| [`no-parameter-reassignment`] | ✅ | [`no-param-reassign`][no-param-reassign] | +| [`no-reference`] | ✅ | [`@typescript-eslint/triple-slash-reference`] | +| [`no-unnecessary-type-assertion`] | ✅ | [`@typescript-eslint/no-unnecessary-type-assertion`] | +| [`no-var-requires`] | ✅ | [`@typescript-eslint/no-var-requires`] | +| [`only-arrow-functions`] | 🔌 | [`prefer-arrow/prefer-arrow-functions`] | +| [`prefer-for-of`] | ✅ | [`@typescript-eslint/prefer-for-of`] | +| [`promise-function-async`] | ✅ | [`@typescript-eslint/promise-function-async`] | +| [`typedef-whitespace`] | ✅ | [`@typescript-eslint/type-annotation-spacing`] | +| [`typedef`] | ✅ | [`@typescript-eslint/typedef`] | +| [`unified-signatures`] | ✅ | [`@typescript-eslint/unified-signatures`] | [1] The ESLint rule only supports exact string matching, rather than regular expressions
@@ -595,7 +595,7 @@ Relevant plugins: [`chai-expect-keywords`](https://github.com/gavinaiken/eslint- [`@typescript-eslint/adjacent-overload-signatures`]: https://typescript-eslint.io/rules/adjacent-overload-signatures [`@typescript-eslint/await-thenable`]: https://typescript-eslint.io/rules/await-thenable -[`@typescript-eslint/ban-types`]: https://typescript-eslint.io/rules/ban-types +[`@typescript-eslint/no-restricted-types`]: https://typescript-eslint.io/rules/no-restricted-types [`@typescript-eslint/ban-ts-comment`]: https://typescript-eslint.io/rules/ban-ts-comment [`@typescript-eslint/class-methods-use-this`]: https://typescript-eslint.io/rules/class-methods-use-this [`@typescript-eslint/consistent-type-assertions`]: https://typescript-eslint.io/rules/consistent-type-assertions diff --git a/packages/eslint-plugin/docs/rules/ban-types.md b/packages/eslint-plugin/docs/rules/ban-types.md new file mode 100644 index 000000000000..f2aa717a07b2 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/ban-types.md @@ -0,0 +1,22 @@ +:::danger Deprecated + +The old `ban-types` rule encompassed multiple areas of functionality, and so has been split into several rules. + +**[`no-restricted-types`](./no-restricted-types.mdx)** is the new rule for banning a configurable list of type names. +It has no options enabled by default and is akin to rules like [`no-restricted-globals`](https://eslint.org/docs/latest/rules/no-restricted-globals), [`no-restricted-properties`](https://eslint.org/docs/latest/rules/no-restricted-properties), and [`no-restricted-syntax`](https://eslint.org/docs/latest/rules/no-restricted-syntax). + +The default options from `ban-types` are now covered by: + +- **[`no-empty-object-type`](./no-empty-object-type.mdx)**: banning the built-in `{}` type in confusing locations +- **[`no-unsafe-function-type`](./no-unsafe-function-type.mdx)**: banning the built-in `Function` +- **[`no-wrapper-object-types`](./no-wrapper-object-types.mdx)**: banning `Object` and built-in class wrappers such as `Number` + +`ban-types` itself is removed in typescript-eslint v8. +See [Announcing typescript-eslint v8 Beta](/blog/announcing-typescript-eslint-v8-beta) for more details. +::: + + diff --git a/packages/eslint-plugin/docs/rules/ban-types.mdx b/packages/eslint-plugin/docs/rules/ban-types.mdx deleted file mode 100644 index b52ae22df7d3..000000000000 --- a/packages/eslint-plugin/docs/rules/ban-types.mdx +++ /dev/null @@ -1,133 +0,0 @@ ---- -description: 'Disallow certain types.' ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -> 🛑 This file is source code, not the primary documentation location! 🛑 -> -> See **https://typescript-eslint.io/rules/ban-types** for documentation. - -Some built-in types have aliases, while some types are considered dangerous or harmful. -It's often a good idea to ban certain types to help with consistency and safety. - -This rule bans specific types and can suggest alternatives. -Note that it does not ban the corresponding runtime objects from being used. - -## Examples - -Examples of code with the default options: - - - - -```ts -// use lower-case primitives for consistency -const str: String = 'foo'; -const bool: Boolean = true; -const num: Number = 1; -const symb: Symbol = Symbol('foo'); -const bigInt: BigInt = 1n; - -// use a proper function type -const func: Function = () => 1; - -// use safer object types -const lowerObj: Object = {}; -const capitalObj: Object = { a: 'string' }; -``` - - - - -```ts -// use lower-case primitives for consistency -const str: string = 'foo'; -const bool: boolean = true; -const num: number = 1; -const symb: symbol = Symbol('foo'); -const bigInt: bigint = 1n; - -// use a proper function type -const func: () => number = () => 1; - -// use safer object types -const lowerObj: object = {}; -const capitalObj: { a: string } = { a: 'string' }; -``` - - - - -## Options - -The default options provide a set of "best practices", intended to provide safety and standardization in your codebase: - -- Don't use the upper-case primitive types or `Object`, you should use the lower-case types for consistency. -- Avoid the `Function` type, as it provides little safety for the following reasons: - - It provides no type safety when calling the value, which means it's easy to provide the wrong arguments. - - It accepts class declarations, which will fail when called, as they are called without the `new` keyword. - -
-Default Options - -{/* Inject default options */} - -
- -### `types` - -An object whose keys are the types you want to ban, and the values are error messages. - -The type can either be a type name literal (`Foo`), a type name with generic parameter instantiation(s) (`Foo`), the empty object literal (`{}`), or the empty tuple type (`[]`). - -The values can be: - -- A string, which is the error message to be reported; or -- `false` to specifically un-ban this type (useful when you are using `extendDefaults`); or -- An object with the following properties: - - `message: string` - the message to display when the type is matched. - - `fixWith?: string` - a string to replace the banned type with when the fixer is run. If this is omitted, no fix will be done. - - `suggest?: string[]` - a list of suggested replacements for the banned type. - -### `extendDefaults` - -If you're specifying custom `types`, you can set this to `true` to extend the default `types` configuration. This is a convenience option to save you copying across the defaults when adding another type. - -If this is `false`, the rule will _only_ use the types defined in your configuration. - -Example configuration: - -```jsonc -{ - "@typescript-eslint/ban-types": [ - "error", - { - "types": { - // add a custom message to help explain why not to use it - "Foo": "Don't use Foo because it is unsafe", - - // add a custom message, AND tell the plugin how to fix it - "OldAPI": { - "message": "Use NewAPI instead", - "fixWith": "NewAPI", - }, - - // un-ban a type that's banned by default - "{}": false, - }, - "extendDefaults": true, - }, - ], -} -``` - -## When Not To Use It - -If your project is a rare one that intentionally deals with the class equivalents of primitives, it might not be worthwhile to enable the default `ban-types` options. -You might consider using [ESLint disable comments](https://eslint.org/docs/latest/use/configure/rules#using-configuration-comments-1) for those specific situations instead of completely disabling this rule. - -## Related To - -- [`no-empty-object-type`](./no-empty-object-type.mdx) diff --git a/packages/eslint-plugin/docs/rules/no-restricted-types.mdx b/packages/eslint-plugin/docs/rules/no-restricted-types.mdx new file mode 100644 index 000000000000..20732174929e --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-restricted-types.mdx @@ -0,0 +1,71 @@ +--- +description: 'Disallow certain types.' +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +> 🛑 This file is source code, not the primary documentation location! 🛑 +> +> See **https://typescript-eslint.io/rules/no-restricted-types** for documentation. + +It can sometimes be useful to ban specific types from being used in type annotations. +For example, a project might be migrating from using one type to another, and want to ban references to the old type. + +This rule can be configured to ban a list of specific types and can suggest alternatives. +Note that it does not ban the corresponding runtime objects from being used. + +## Options + +### `types` + +An object whose keys are the types you want to ban, and the values are error messages. + +The type can either be a type name literal (`OldType`) or a a type name with generic parameter instantiation(s) (`OldType`). + +The values can be: + +- A string, which is the error message to be reported; or +- `false` to specifically un-ban this type (useful when you are using `extendDefaults`); or +- An object with the following properties: + - `message: string`: the message to display when the type is matched. + - `fixWith?: string`: a string to replace the banned type with when the fixer is run. If this is omitted, no fix will be done. + - `suggest?: string[]`: a list of suggested replacements for the banned type. + +Example configuration: + +```jsonc +{ + "@typescript-eslint/no-restricted-types": [ + "error", + { + "types": { + // add a custom message to help explain why not to use it + "OldType": "Don't use OldType because it is unsafe", + + // add a custom message, and tell the plugin how to fix it + "OldAPI": { + "message": "Use NewAPI instead", + "fixWith": "NewAPI", + }, + + // add a custom message, and tell the plugin how to suggest a fix + "SoonToBeOldAPI": { + "message": "Use NewAPI instead", + "suggest": ["NewAPIOne", "NewAPITwo"], + }, + }, + }, + ], +} +``` + +## When Not To Use It + +If you have no need to ban specific types from being used in type annotations, you don't need this rule. + +## Related To + +- [`no-empty-object-type`](./no-empty-object-type.mdx) +- [`no-unsafe-function-type`](./no-unsafe-function-type.mdx) +- [`no-wrapper-object-types`](./no-wrapper-object-types.mdx) diff --git a/packages/eslint-plugin/docs/rules/no-unsafe-function-type.mdx b/packages/eslint-plugin/docs/rules/no-unsafe-function-type.mdx new file mode 100644 index 000000000000..ea7b60794e4e --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-unsafe-function-type.mdx @@ -0,0 +1,63 @@ +--- +description: 'Disallow using the unsafe built-in Function type.' +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +> 🛑 This file is source code, not the primary documentation location! 🛑 +> +> See **https://typescript-eslint.io/rules/no-unsafe-function-type** for documentation. + +TypeScript's built-in `Function` type allows being called with any number of arguments and returns type `any`. +`Function` also allows classes or plain objects that happen to possess all properties of the `Function` class. +It's generally better to specify function parameters and return types with the function type syntax. + +"Catch-all" function types include: + +- `() => void`: a function that has no parameters and whose return is ignored +- `(...args: never) => unknown`: a "top type" for functions that can be assigned any function type, but can't be called + +Examples of code for this rule: + + + + +```ts +let noParametersOrReturn: Function; +noParametersOrReturn = () => {}; + +let stringToNumber: Function; +stringToNumber = (text: string) => text.length; + +let identity: Function; +identity = value => value; +``` + + + + +```ts +let noParametersOrReturn: () => void; +noParametersOrReturn = () => {}; + +let stringToNumber: (text: string) => number; +stringToNumber = text => text.length; + +let identity: (value: T) => T; +identity = value => value; +``` + + + + +## When Not To Use It + +If your project is still onboarding to TypeScript, it might be difficult to fully replace all unsafe `Function` types with more precise function types. +You might consider using [ESLint disable comments](https://eslint.org/docs/latest/use/configure/rules#using-configuration-comments-1) for those specific situations instead of completely disabling this rule. + +## Related To + +- [`no-empty-object-type`](./no-empty-object-type.mdx) +- [`no-restricted-types`](./no-restricted-types.mdx) +- [`no-wrapper-object-types`](./no-wrapper-object-types.mdx) diff --git a/packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx b/packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx new file mode 100644 index 000000000000..858802470cbb --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx @@ -0,0 +1,67 @@ +--- +description: 'Disallow using confusing built-in primitive class wrappers.' +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +> 🛑 This file is source code, not the primary documentation location! 🛑 +> +> See **https://typescript-eslint.io/rules/no-wrapper-object-types** for documentation. + +The JavaScript language has a set of language types, but some of them correspond to two TypeScript types, which look similar: `boolean`/`Boolean`, `number`/`Number`, `string`/`String`, `bigint`/`BigInt`, `symbol`/`Symbol`, `object`/`Object`. +The difference is that the lowercase variants are compiler intrinsics and specify the actual _runtime types_ (that is, the return value when you use the `typeof` operator), while the uppercase variants are _structural types_ defined in the library that can be satisfied by any user-defined object with the right properties, not just the real primitives. +JavaScript also has a "wrapper" class object for each of those primitives, such as [`Boolean`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean) and [`Number`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number). +These wrapper objects are assignable to the uppercase types, but not to the lowercase types. + +Using the primitives like `0` instead of object wrappers like `new Number(0)` is generally considered a JavaScript best practice. +JavaScript programs typically work with the real number primitives, rather than objects that "look like" numbers. +Primitives are simpler to conceptualize and work with `==` and `===` equality checks -- which their object equivalents do notDeepEqual. +As a result, using the lowercase type names like `number` instead of the uppercase names like `Number` helps make your code behave more reliably. + +Examples of 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'; +``` + + + + +```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'."; +``` + + + + +## When Not To Use It + +If your project is a rare one that intentionally deals with the class equivalents of primitives, it might not be worthwhile to use this rule. +You might consider using [ESLint disable comments](https://eslint.org/docs/latest/use/configure/rules#using-configuration-comments-1) for those specific situations instead of completely disabling this rule. + +## Further Reading + +- [MDN documentation on primitives](https://developer.mozilla.org/en-US/docs/Glossary/Primitive) +- [MDN documentation on `string` primitives and `String` objects](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#string_primitives_and_string_objects) + +## Related To + +- [`no-empty-object-type`](./no-empty-object-type.mdx) +- [`no-restricted-types`](./no-restricted-types.mdx) +- [`no-unsafe-function-type`](./no-unsafe-function-type.mdx) diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts index 5c1e1d7725ac..ab24d6943de0 100644 --- a/packages/eslint-plugin/src/configs/all.ts +++ b/packages/eslint-plugin/src/configs/all.ts @@ -15,7 +15,6 @@ export = { '@typescript-eslint/await-thenable': 'error', '@typescript-eslint/ban-ts-comment': 'error', '@typescript-eslint/ban-tslint-comment': 'error', - '@typescript-eslint/ban-types': 'error', '@typescript-eslint/class-literal-property-style': 'error', 'class-methods-use-this': 'off', '@typescript-eslint/class-methods-use-this': 'error', @@ -85,6 +84,7 @@ export = { '@typescript-eslint/no-require-imports': 'error', 'no-restricted-imports': 'off', '@typescript-eslint/no-restricted-imports': 'error', + '@typescript-eslint/no-restricted-types': 'error', 'no-shadow': 'off', '@typescript-eslint/no-shadow': 'error', '@typescript-eslint/no-this-alias': 'error', @@ -100,6 +100,7 @@ export = { '@typescript-eslint/no-unsafe-call': 'error', '@typescript-eslint/no-unsafe-declaration-merging': 'error', '@typescript-eslint/no-unsafe-enum-comparison': 'error', + '@typescript-eslint/no-unsafe-function-type': 'error', '@typescript-eslint/no-unsafe-member-access': 'error', '@typescript-eslint/no-unsafe-return': 'error', '@typescript-eslint/no-unsafe-unary-minus': 'error', @@ -112,6 +113,7 @@ export = { 'no-useless-constructor': 'off', '@typescript-eslint/no-useless-constructor': 'error', '@typescript-eslint/no-useless-empty-export': 'error', + '@typescript-eslint/no-wrapper-object-types': 'error', '@typescript-eslint/non-nullable-type-assertion-style': 'error', 'no-throw-literal': 'off', '@typescript-eslint/only-throw-error': 'error', diff --git a/packages/eslint-plugin/src/configs/recommended-type-checked.ts b/packages/eslint-plugin/src/configs/recommended-type-checked.ts index d0975fddfe8b..e8e10a7071a5 100644 --- a/packages/eslint-plugin/src/configs/recommended-type-checked.ts +++ b/packages/eslint-plugin/src/configs/recommended-type-checked.ts @@ -12,7 +12,6 @@ export = { rules: { '@typescript-eslint/await-thenable': 'error', '@typescript-eslint/ban-ts-comment': 'error', - '@typescript-eslint/ban-types': 'error', 'no-array-constructor': 'off', '@typescript-eslint/no-array-constructor': 'error', '@typescript-eslint/no-array-delete': 'error', @@ -40,6 +39,7 @@ export = { '@typescript-eslint/no-unsafe-call': 'error', '@typescript-eslint/no-unsafe-declaration-merging': 'error', '@typescript-eslint/no-unsafe-enum-comparison': 'error', + '@typescript-eslint/no-unsafe-function-type': 'error', '@typescript-eslint/no-unsafe-member-access': 'error', '@typescript-eslint/no-unsafe-return': 'error', '@typescript-eslint/no-unsafe-unary-minus': 'error', @@ -47,6 +47,7 @@ export = { '@typescript-eslint/no-unused-expressions': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', + '@typescript-eslint/no-wrapper-object-types': 'error', 'no-throw-literal': 'off', '@typescript-eslint/only-throw-error': 'error', '@typescript-eslint/prefer-as-const': 'error', diff --git a/packages/eslint-plugin/src/configs/recommended.ts b/packages/eslint-plugin/src/configs/recommended.ts index 6657edaaf5b9..487935ee1f23 100644 --- a/packages/eslint-plugin/src/configs/recommended.ts +++ b/packages/eslint-plugin/src/configs/recommended.ts @@ -11,7 +11,6 @@ export = { extends: ['./configs/base', './configs/eslint-recommended'], rules: { '@typescript-eslint/ban-ts-comment': 'error', - '@typescript-eslint/ban-types': 'error', 'no-array-constructor': 'off', '@typescript-eslint/no-array-constructor': 'error', '@typescript-eslint/no-duplicate-enum-values': 'error', @@ -25,10 +24,12 @@ export = { '@typescript-eslint/no-this-alias': 'error', '@typescript-eslint/no-unnecessary-type-constraint': 'error', '@typescript-eslint/no-unsafe-declaration-merging': 'error', + '@typescript-eslint/no-unsafe-function-type': 'error', 'no-unused-expressions': 'off', '@typescript-eslint/no-unused-expressions': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', + '@typescript-eslint/no-wrapper-object-types': 'error', '@typescript-eslint/prefer-as-const': 'error', '@typescript-eslint/prefer-namespace-keyword': 'error', '@typescript-eslint/triple-slash-reference': 'error', diff --git a/packages/eslint-plugin/src/configs/strict-type-checked.ts b/packages/eslint-plugin/src/configs/strict-type-checked.ts index 7987868204db..11d65130de62 100644 --- a/packages/eslint-plugin/src/configs/strict-type-checked.ts +++ b/packages/eslint-plugin/src/configs/strict-type-checked.ts @@ -15,7 +15,6 @@ export = { 'error', { minimumDescriptionLength: 10 }, ], - '@typescript-eslint/ban-types': 'error', 'no-array-constructor': 'off', '@typescript-eslint/no-array-constructor': 'error', '@typescript-eslint/no-array-delete': 'error', @@ -55,6 +54,7 @@ export = { '@typescript-eslint/no-unsafe-call': 'error', '@typescript-eslint/no-unsafe-declaration-merging': 'error', '@typescript-eslint/no-unsafe-enum-comparison': 'error', + '@typescript-eslint/no-unsafe-function-type': 'error', '@typescript-eslint/no-unsafe-member-access': 'error', '@typescript-eslint/no-unsafe-return': 'error', '@typescript-eslint/no-unsafe-unary-minus': 'error', @@ -64,6 +64,7 @@ export = { '@typescript-eslint/no-unused-vars': 'error', 'no-useless-constructor': 'off', '@typescript-eslint/no-useless-constructor': 'error', + '@typescript-eslint/no-wrapper-object-types': 'error', 'no-throw-literal': 'off', '@typescript-eslint/only-throw-error': 'error', '@typescript-eslint/prefer-as-const': 'error', diff --git a/packages/eslint-plugin/src/configs/strict.ts b/packages/eslint-plugin/src/configs/strict.ts index fc7cb1febebc..0e655d1464ca 100644 --- a/packages/eslint-plugin/src/configs/strict.ts +++ b/packages/eslint-plugin/src/configs/strict.ts @@ -14,7 +14,6 @@ export = { 'error', { minimumDescriptionLength: 10 }, ], - '@typescript-eslint/ban-types': 'error', 'no-array-constructor': 'off', '@typescript-eslint/no-array-constructor': 'error', '@typescript-eslint/no-duplicate-enum-values': 'error', @@ -33,12 +32,14 @@ export = { '@typescript-eslint/no-this-alias': 'error', '@typescript-eslint/no-unnecessary-type-constraint': 'error', '@typescript-eslint/no-unsafe-declaration-merging': 'error', + '@typescript-eslint/no-unsafe-function-type': 'error', 'no-unused-expressions': 'off', '@typescript-eslint/no-unused-expressions': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', 'no-useless-constructor': 'off', '@typescript-eslint/no-useless-constructor': 'error', + '@typescript-eslint/no-wrapper-object-types': 'error', '@typescript-eslint/prefer-as-const': 'error', '@typescript-eslint/prefer-literal-enum-member': 'error', '@typescript-eslint/prefer-namespace-keyword': 'error', diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index c167013211c0..d11366a54341 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -5,7 +5,6 @@ import arrayType from './array-type'; import awaitThenable from './await-thenable'; import banTsComment from './ban-ts-comment'; import banTslintComment from './ban-tslint-comment'; -import banTypes from './ban-types'; import classLiteralPropertyStyle from './class-literal-property-style'; import classMethodsUseThis from './class-methods-use-this'; import consistentGenericConstructors from './consistent-generic-constructors'; @@ -62,6 +61,7 @@ import noRedeclare from './no-redeclare'; import noRedundantTypeConstituents from './no-redundant-type-constituents'; import noRequireImports from './no-require-imports'; import noRestrictedImports from './no-restricted-imports'; +import noRestrictedTypes from './no-restricted-types'; import noShadow from './no-shadow'; import noThisAlias from './no-this-alias'; import noTypeAlias from './no-type-alias'; @@ -77,6 +77,7 @@ import noUnsafeAssignment from './no-unsafe-assignment'; import noUnsafeCall from './no-unsafe-call'; import noUnsafeDeclarationMerging from './no-unsafe-declaration-merging'; import noUnsafeEnumComparison from './no-unsafe-enum-comparison'; +import noUnsafeFunctionType from './no-unsafe-function-type'; import noUnsafeMemberAccess from './no-unsafe-member-access'; import noUnsafeReturn from './no-unsafe-return'; import noUnsafeUnaryMinus from './no-unsafe-unary-minus'; @@ -86,6 +87,7 @@ import noUseBeforeDefine from './no-use-before-define'; import noUselessConstructor from './no-useless-constructor'; import noUselessEmptyExport from './no-useless-empty-export'; import noVarRequires from './no-var-requires'; +import noWrapperObjectTypes from './no-wrapper-object-types'; import nonNullableTypeAssertionStyle from './non-nullable-type-assertion-style'; import onlyThrowError from './only-throw-error'; import parameterProperties from './parameter-properties'; @@ -129,7 +131,6 @@ export default { 'await-thenable': awaitThenable, 'ban-ts-comment': banTsComment, 'ban-tslint-comment': banTslintComment, - 'ban-types': banTypes, 'class-literal-property-style': classLiteralPropertyStyle, 'class-methods-use-this': classMethodsUseThis, 'consistent-generic-constructors': consistentGenericConstructors, @@ -186,6 +187,7 @@ export default { 'no-redundant-type-constituents': noRedundantTypeConstituents, 'no-require-imports': noRequireImports, 'no-restricted-imports': noRestrictedImports, + 'no-restricted-types': noRestrictedTypes, 'no-shadow': noShadow, 'no-this-alias': noThisAlias, 'no-type-alias': noTypeAlias, @@ -201,6 +203,7 @@ export default { 'no-unsafe-call': noUnsafeCall, 'no-unsafe-declaration-merging': noUnsafeDeclarationMerging, 'no-unsafe-enum-comparison': noUnsafeEnumComparison, + 'no-unsafe-function-type': noUnsafeFunctionType, 'no-unsafe-member-access': noUnsafeMemberAccess, 'no-unsafe-return': noUnsafeReturn, 'no-unsafe-unary-minus': noUnsafeUnaryMinus, @@ -210,6 +213,7 @@ export default { 'no-useless-constructor': noUselessConstructor, 'no-useless-empty-export': noUselessEmptyExport, 'no-var-requires': noVarRequires, + 'no-wrapper-object-types': noWrapperObjectTypes, 'non-nullable-type-assertion-style': nonNullableTypeAssertionStyle, 'only-throw-error': onlyThrowError, 'parameter-properties': parameterProperties, diff --git a/packages/eslint-plugin/src/rules/no-implied-eval.ts b/packages/eslint-plugin/src/rules/no-implied-eval.ts index dee23d62d275..b2bfac55203a 100644 --- a/packages/eslint-plugin/src/rules/no-implied-eval.ts +++ b/packages/eslint-plugin/src/rules/no-implied-eval.ts @@ -3,7 +3,11 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import * as tsutils from 'ts-api-utils'; import * as ts from 'typescript'; -import { createRule, getParserServices } from '../util'; +import { + createRule, + getParserServices, + isReferenceToGlobalFunction, +} from '../util'; const FUNCTION_CONSTRUCTOR = 'Function'; const GLOBAL_CANDIDATES = new Set(['global', 'window', 'globalThis']); @@ -119,18 +123,6 @@ export default createRule({ } } - function isReferenceToGlobalFunction( - calleeName: string, - node: TSESTree.Node, - ): boolean { - const ref = context.sourceCode - .getScope(node) - .references.find(ref => ref.identifier.name === calleeName); - - // ensure it's the "global" version - return !ref?.resolved || ref.resolved.defs.length === 0; - } - function checkImpliedEval( node: TSESTree.CallExpression | TSESTree.NewExpression, ): void { @@ -165,7 +157,7 @@ export default createRule({ if ( EVAL_LIKE_METHODS.has(calleeName) && !isFunction(handler) && - isReferenceToGlobalFunction(calleeName, node) + isReferenceToGlobalFunction(calleeName, node, context.sourceCode) ) { context.report({ node: handler, messageId: 'noImpliedEvalError' }); } diff --git a/packages/eslint-plugin/src/rules/ban-types.ts b/packages/eslint-plugin/src/rules/no-restricted-types.ts similarity index 74% rename from packages/eslint-plugin/src/rules/ban-types.ts rename to packages/eslint-plugin/src/rules/no-restricted-types.ts index 06c9daa62d4a..f3d2e3b3f197 100644 --- a/packages/eslint-plugin/src/rules/ban-types.ts +++ b/packages/eslint-plugin/src/rules/no-restricted-types.ts @@ -21,6 +21,7 @@ export type Options = [ extendDefaults?: boolean; }, ]; + export type MessageIds = 'bannedTypeMessage' | 'bannedTypeReplacement'; function removeSpaces(str: string): string { @@ -37,7 +38,7 @@ function stringifyNode( function getCustomMessage( bannedType: string | true | { message?: string; fixWith?: string } | null, ): string { - if (bannedType == null || bannedType === true) { + if (!bannedType || bannedType === true) { return ''; } @@ -52,42 +53,7 @@ function getCustomMessage( return ''; } -const defaultTypes: Types = { - String: { - message: 'Use string instead', - fixWith: 'string', - }, - Boolean: { - message: 'Use boolean instead', - fixWith: 'boolean', - }, - Number: { - message: 'Use number instead', - fixWith: 'number', - }, - Symbol: { - message: 'Use symbol instead', - fixWith: 'symbol', - }, - BigInt: { - message: 'Use bigint instead', - fixWith: 'bigint', - }, - Object: { - message: 'Use object instead', - fixWith: 'object', - }, - Function: { - message: [ - 'The `Function` type accepts any function-like value.', - 'It provides no type safety when calling the function, which can be a common source of bugs.', - 'It also accepts things like class declarations, which will throw at runtime as they will not be called with `new`.', - 'If you are expecting the function to accept certain arguments, you should explicitly define the function shape.', - ].join('\n'), - }, -}; - -export const TYPE_KEYWORDS = { +const TYPE_KEYWORDS = { bigint: AST_NODE_TYPES.TSBigIntKeyword, boolean: AST_NODE_TYPES.TSBooleanKeyword, never: AST_NODE_TYPES.TSNeverKeyword, @@ -102,12 +68,11 @@ export const TYPE_KEYWORDS = { }; export default createRule({ - name: 'ban-types', + name: 'no-restricted-types', meta: { type: 'suggestion', docs: { description: 'Disallow certain types', - recommended: 'recommended', }, fixable: 'code', hasSuggestions: true, @@ -120,16 +85,6 @@ export default createRule({ $defs: { banConfig: { oneOf: [ - { - type: 'null', - description: 'Bans the type with the default message', - }, - { - type: 'boolean', - enum: [false], - description: - 'Un-bans the type (useful when paired with `extendDefaults`)', - }, { type: 'boolean', enum: [true], @@ -156,7 +111,6 @@ export default createRule({ type: 'array', items: { type: 'string' }, description: 'Types to suggest replacing with.', - additionalItems: false, }, }, additionalProperties: false, @@ -172,23 +126,13 @@ export default createRule({ $ref: '#/items/0/$defs/banConfig', }, }, - extendDefaults: { - type: 'boolean', - }, }, additionalProperties: false, }, ], }, defaultOptions: [{}], - create(context, [options]) { - const extendDefaults = options.extendDefaults ?? true; - const customTypes = options.types ?? {}; - const types = Object.assign( - {}, - extendDefaults ? defaultTypes : {}, - customTypes, - ); + create(context, [{ types = {} }]) { const bannedTypes = new Map( Object.entries(types).map(([type, data]) => [removeSpaces(type), data]), ); @@ -249,15 +193,19 @@ export default createRule({ return { ...keywordSelectors, - TSTypeLiteral(node): void { - if (node.members.length) { - return; - } - + TSClassImplements(node): void { + checkBannedTypes(node); + }, + TSInterfaceHeritage(node): void { checkBannedTypes(node); }, TSTupleType(node): void { - if (node.elementTypes.length === 0) { + if (!node.elementTypes.length) { + checkBannedTypes(node); + } + }, + TSTypeLiteral(node): void { + if (!node.members.length) { checkBannedTypes(node); } }, @@ -268,12 +216,6 @@ export default createRule({ checkBannedTypes(node); } }, - TSInterfaceHeritage(node): void { - checkBannedTypes(node); - }, - TSClassImplements(node): void { - checkBannedTypes(node); - }, }; }, }); diff --git a/packages/eslint-plugin/src/rules/no-unsafe-function-type.ts b/packages/eslint-plugin/src/rules/no-unsafe-function-type.ts new file mode 100644 index 000000000000..624c038f8ff7 --- /dev/null +++ b/packages/eslint-plugin/src/rules/no-unsafe-function-type.ts @@ -0,0 +1,50 @@ +import type { TSESTree } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; + +import { createRule, isReferenceToGlobalFunction } from '../util'; + +export default createRule({ + name: 'no-unsafe-function-type', + meta: { + type: 'problem', + docs: { + description: 'Disallow using the unsafe built-in Function type', + recommended: 'recommended', + }, + fixable: 'code', + messages: { + bannedFunctionType: [ + 'The `Function` type accepts any function-like value.', + 'Prefer explicitly defining any function parameters and return type.', + ].join('\n'), + }, + schema: [], + }, + defaultOptions: [], + create(context) { + function checkBannedTypes(node: TSESTree.Node): void { + if ( + node.type === AST_NODE_TYPES.Identifier && + node.name === 'Function' && + isReferenceToGlobalFunction('Function', node, context.sourceCode) + ) { + context.report({ + node, + messageId: 'bannedFunctionType', + }); + } + } + + return { + TSClassImplements(node): void { + checkBannedTypes(node.expression); + }, + TSInterfaceHeritage(node): void { + checkBannedTypes(node.expression); + }, + TSTypeReference(node): void { + checkBannedTypes(node.typeName); + }, + }; + }, +}); diff --git a/packages/eslint-plugin/src/rules/no-wrapper-object-types.ts b/packages/eslint-plugin/src/rules/no-wrapper-object-types.ts new file mode 100644 index 000000000000..f51b6c8564b5 --- /dev/null +++ b/packages/eslint-plugin/src/rules/no-wrapper-object-types.ts @@ -0,0 +1,71 @@ +import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; + +import { createRule, isReferenceToGlobalFunction } from '../util'; + +const classNames = new Set([ + 'BigInt', + // eslint-disable-next-line @typescript-eslint/internal/prefer-ast-types-enum + 'Boolean', + 'Number', + 'Object', + // eslint-disable-next-line @typescript-eslint/internal/prefer-ast-types-enum + 'String', + 'Symbol', +]); + +export default createRule({ + name: 'no-wrapper-object-types', + meta: { + type: 'problem', + docs: { + description: 'Disallow using confusing built-in primitive class wrappers', + recommended: 'recommended', + }, + fixable: 'code', + messages: { + bannedClassType: + 'Prefer using the primitive `{{preferred}}` as a type name, rather than the upper-cased `{{typeName}}`.', + }, + schema: [], + }, + defaultOptions: [], + create(context) { + function checkBannedTypes( + node: TSESTree.EntityName | TSESTree.Expression, + includeFix: boolean, + ): void { + const typeName = node.type === AST_NODE_TYPES.Identifier && node.name; + if ( + !typeName || + !classNames.has(typeName) || + !isReferenceToGlobalFunction(typeName, node, context.sourceCode) + ) { + return; + } + + const preferred = typeName.toLowerCase(); + + context.report({ + data: { typeName, preferred }, + fix: includeFix + ? (fixer): TSESLint.RuleFix => fixer.replaceText(node, preferred) + : undefined, + messageId: 'bannedClassType', + node, + }); + } + + return { + TSClassImplements(node): void { + checkBannedTypes(node.expression, false); + }, + TSInterfaceHeritage(node): void { + checkBannedTypes(node.expression, false); + }, + TSTypeReference(node): void { + checkBannedTypes(node.typeName, true); + }, + }; + }, +}); diff --git a/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts b/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts index 54c44f4edda9..53beee5d2826 100644 --- a/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts +++ b/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts @@ -13,7 +13,7 @@ import { } from 'ts-api-utils'; import * as ts from 'typescript'; -import { isTypeFlagSet } from '../../util'; +import { isReferenceToGlobalFunction, isTypeFlagSet } from '../../util'; import type { PreferOptionalChainOptions } from './PreferOptionalChainOptions'; const enum ComparisonValueType { @@ -153,16 +153,13 @@ export function gatherLogicalOperands( comparedExpression.operator === 'typeof' ) { const argument = comparedExpression.argument; - if (argument.type === AST_NODE_TYPES.Identifier) { - const reference = sourceCode - .getScope(argument) - .references.find(ref => ref.identifier.name === argument.name); - - if (!reference?.resolved?.defs.length) { - // typeof window === 'undefined' - result.push({ type: OperandValidity.Invalid }); - continue; - } + if ( + argument.type === AST_NODE_TYPES.Identifier && + // typeof window === 'undefined' + isReferenceToGlobalFunction(argument.name, argument, sourceCode) + ) { + result.push({ type: OperandValidity.Invalid }); + continue; } // typeof x.y === 'undefined' diff --git a/packages/eslint-plugin/src/util/index.ts b/packages/eslint-plugin/src/util/index.ts index 1f8d657cd542..083353a69233 100644 --- a/packages/eslint-plugin/src/util/index.ts +++ b/packages/eslint-plugin/src/util/index.ts @@ -14,6 +14,7 @@ export * from './isNullLiteral'; export * from './isUndefinedIdentifier'; export * from './misc'; export * from './objectIterators'; +export * from './scopeUtils'; export * from './types'; export * from './isAssignee'; diff --git a/packages/eslint-plugin/src/util/scopeUtils.ts b/packages/eslint-plugin/src/util/scopeUtils.ts new file mode 100644 index 000000000000..b350a7412106 --- /dev/null +++ b/packages/eslint-plugin/src/util/scopeUtils.ts @@ -0,0 +1,15 @@ +import type { TSESTree } from '@typescript-eslint/utils'; +import type { SourceCode } from '@typescript-eslint/utils/ts-eslint'; + +export function isReferenceToGlobalFunction( + calleeName: string, + node: TSESTree.Node, + sourceCode: SourceCode, +): boolean { + const ref = sourceCode + .getScope(node) + .references.find(ref => ref.identifier.name === calleeName); + + // ensure it's the "global" version + return !ref?.resolved?.defs.length; +} diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/ban-types.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/ban-types.shot deleted file mode 100644 index 6644fd985d9b..000000000000 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/ban-types.shot +++ /dev/null @@ -1,50 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Validating rule docs ban-types.mdx code examples ESLint output 1`] = ` -"Incorrect - -// use lower-case primitives for consistency -const str: String = 'foo'; - ~~~~~~ Don't use \`String\` as a type. Use string instead -const bool: Boolean = true; - ~~~~~~~ Don't use \`Boolean\` as a type. Use boolean instead -const num: Number = 1; - ~~~~~~ Don't use \`Number\` as a type. Use number instead -const symb: Symbol = Symbol('foo'); - ~~~~~~ Don't use \`Symbol\` as a type. Use symbol instead -const bigInt: BigInt = 1n; - ~~~~~~ Don't use \`BigInt\` as a type. Use bigint instead - -// use a proper function type -const func: Function = () => 1; - ~~~~~~~~ Don't use \`Function\` as a type. The \`Function\` type accepts any function-like value. - It provides no type safety when calling the function, which can be a common source of bugs. - It also accepts things like class declarations, which will throw at runtime as they will not be called with \`new\`. - If you are expecting the function to accept certain arguments, you should explicitly define the function shape. - -// use safer object types -const lowerObj: Object = {}; - ~~~~~~ Don't use \`Object\` as a type. Use object instead -const capitalObj: Object = { a: 'string' }; - ~~~~~~ Don't use \`Object\` as a type. Use object instead -" -`; - -exports[`Validating rule docs ban-types.mdx code examples ESLint output 2`] = ` -"Correct - -// use lower-case primitives for consistency -const str: string = 'foo'; -const bool: boolean = true; -const num: number = 1; -const symb: symbol = Symbol('foo'); -const bigInt: bigint = 1n; - -// use a proper function type -const func: () => number = () => 1; - -// use safer object types -const lowerObj: object = {}; -const capitalObj: { a: string } = { a: 'string' }; -" -`; diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unsafe-function-type.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unsafe-function-type.shot new file mode 100644 index 000000000000..c77f15d6feb3 --- /dev/null +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unsafe-function-type.shot @@ -0,0 +1,35 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Validating rule docs no-unsafe-function-type.mdx code examples ESLint output 1`] = ` +"Incorrect + +let noParametersOrReturn: Function; + ~~~~~~~~ The \`Function\` type accepts any function-like value. + Prefer explicitly defining any function parameters and return type. +noParametersOrReturn = () => {}; + +let stringToNumber: Function; + ~~~~~~~~ The \`Function\` type accepts any function-like value. + Prefer explicitly defining any function parameters and return type. +stringToNumber = (text: string) => text.length; + +let identity: Function; + ~~~~~~~~ The \`Function\` type accepts any function-like value. + Prefer explicitly defining any function parameters and return type. +identity = value => value; +" +`; + +exports[`Validating rule docs no-unsafe-function-type.mdx code examples ESLint output 2`] = ` +"Correct + +let noParametersOrReturn: () => void; +noParametersOrReturn = () => {}; + +let stringToNumber: (text: string) => number; +stringToNumber = text => text.length; + +let identity: (value: T) => T; +identity = value => value; +" +`; diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-wrapper-object-types.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-wrapper-object-types.shot new file mode 100644 index 000000000000..4303b79a77a7 --- /dev/null +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-wrapper-object-types.shot @@ -0,0 +1,33 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Validating rule docs no-wrapper-object-types.mdx code examples ESLint output 1`] = ` +"Incorrect + +let myBigInt: BigInt; + ~~~~~~ Prefer using the primitive \`bigint\` as a type name, rather than the upper-cased \`BigInt\`. +let myBoolean: Boolean; + ~~~~~~~ Prefer using the primitive \`boolean\` as a type name, rather than the upper-cased \`Boolean\`. +let myNumber: Number; + ~~~~~~ Prefer using the primitive \`number\` as a type name, rather than the upper-cased \`Number\`. +let myString: String; + ~~~~~~ Prefer using the primitive \`string\` as a type name, rather than the upper-cased \`String\`. +let mySymbol: Symbol; + ~~~~~~ Prefer using the primitive \`symbol\` as a type name, rather than the upper-cased \`Symbol\`. + +let myObject: Object = 'allowed by TypeScript'; + ~~~~~~ Prefer using the primitive \`object\` as a type name, rather than the upper-cased \`Object\`. +" +`; + +exports[`Validating rule docs no-wrapper-object-types.mdx code examples ESLint output 2`] = ` +"Correct + +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'."; +" +`; diff --git a/packages/eslint-plugin/tests/docs.test.ts b/packages/eslint-plugin/tests/docs.test.ts index 96401ecb2fa5..a54f57a7bee4 100644 --- a/packages/eslint-plugin/tests/docs.test.ts +++ b/packages/eslint-plugin/tests/docs.test.ts @@ -155,6 +155,7 @@ describe('Validating rule docs', () => { 'TEMPLATE.md', // These rule docs were left behind on purpose for legacy reasons. See the // comments in the files for more information. + 'ban-types.md', 'no-duplicate-imports.mdx', 'no-parameter-properties.mdx', 'no-useless-template-literals.mdx', @@ -162,7 +163,11 @@ describe('Validating rule docs', () => { ...oldStylisticRules, ]); - const rulesWithComplexOptions = new Set(['array-type', 'member-ordering']); + const rulesWithComplexOptions = new Set([ + 'array-type', + 'member-ordering', + 'no-restricted-types', + ]); // TODO: whittle this list down to as few as possible const rulesWithComplexOptionHeadings = new Set([ diff --git a/packages/eslint-plugin/tests/rules/ban-types.test.ts b/packages/eslint-plugin/tests/rules/ban-types.test.ts deleted file mode 100644 index 046ac290a635..000000000000 --- a/packages/eslint-plugin/tests/rules/ban-types.test.ts +++ /dev/null @@ -1,741 +0,0 @@ -/* eslint-disable @typescript-eslint/internal/prefer-ast-types-enum */ -import { noFormat, RuleTester } from '@typescript-eslint/rule-tester'; -import type { TSESLint } from '@typescript-eslint/utils'; - -import type { MessageIds, Options } from '../../src/rules/ban-types'; -import rule, { TYPE_KEYWORDS } from '../../src/rules/ban-types'; -import { objectReduceKey } from '../../src/util'; - -const ruleTester = new RuleTester({ - parser: '@typescript-eslint/parser', -}); - -const options: Options = [ - { - types: { - String: { - message: 'Use string instead.', - fixWith: 'string', - }, - Object: "Use '{}' instead.", - Array: null, - F: null, - 'NS.Bad': { - message: 'Use NS.Good instead.', - fixWith: 'NS.Good', - }, - }, - extendDefaults: false, - }, -]; - -ruleTester.run('ban-types', rule, { - valid: [ - 'let f = Object();', // Should not fail if there is no options set - 'let f: { x: number; y: number } = { x: 1, y: 1 };', - { - code: 'let f = Object();', - options, - }, - { - code: 'let g = Object.create(null);', - options, - }, - { - code: 'let h = String(false);', - options, - }, - { - code: 'let e: foo.String;', - options, - }, - { - code: 'let a: _.NS.Bad;', - options, - }, - { - code: 'let a: NS.Bad._;', - options, - }, - // Replace default options instead of merging with extendDefaults: false - { - code: 'let a: String;', - options: [ - { - types: { - Number: { - message: 'Use number instead.', - fixWith: 'number', - }, - }, - extendDefaults: false, - }, - ], - }, - { - code: 'let a: undefined;', - options: [ - { - types: { - null: { - message: 'Use undefined instead.', - fixWith: 'undefined', - }, - }, - }, - ], - }, - { - code: 'let a: null;', - options: [ - { - types: { - undefined: null, - }, - extendDefaults: false, - }, - ], - }, - { - code: 'type Props = {};', - options: [ - { - types: { - '{}': false, - }, - extendDefaults: true, - }, - ], - }, - 'let a: [];', - ], - invalid: [ - { - code: 'let a: String;', - output: 'let a: string;', - errors: [ - { - messageId: 'bannedTypeMessage', - line: 1, - column: 8, - }, - ], - }, - { - code: 'let a: Object;', - output: null, - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: 'Object', - customMessage: " Use '{}' instead.", - }, - line: 1, - column: 8, - }, - ], - options, - }, - { - code: 'let aa: Foo;', - output: null, - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: 'Foo', - customMessage: '', - }, - }, - ], - options: [ - { - types: { - Foo: { message: '' }, - }, - }, - ], - }, - { - code: 'let b: { c: String };', - output: 'let b: { c: string };', - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: 'String', - customMessage: ' Use string instead.', - }, - line: 1, - column: 13, - }, - ], - options, - }, - { - code: 'function foo(a: String) {}', - output: 'function foo(a: string) {}', - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: 'String', - customMessage: ' Use string instead.', - }, - line: 1, - column: 17, - }, - ], - options, - }, - { - code: "'a' as String;", - output: "'a' as string;", - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: 'String', - customMessage: ' Use string instead.', - }, - line: 1, - column: 8, - }, - ], - options, - }, - { - code: 'let c: F;', - output: null, - errors: [ - { - messageId: 'bannedTypeMessage', - data: { name: 'F', customMessage: '' }, - line: 1, - column: 8, - }, - ], - options, - }, - { - code: ` -class Foo extends Bar implements Baz { - constructor(foo: String | Object) {} - - exit(): Array { - const foo: String = 1 as String; - } -} - `, - output: ` -class Foo extends Bar implements Baz { - constructor(foo: string | Object) {} - - exit(): Array { - const foo: string = 1 as string; - } -} - `, - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: 'String', - customMessage: ' Use string instead.', - }, - line: 2, - column: 15, - }, - { - messageId: 'bannedTypeMessage', - data: { - name: 'String', - customMessage: ' Use string instead.', - }, - line: 2, - column: 35, - }, - { - messageId: 'bannedTypeMessage', - data: { - name: 'Object', - customMessage: " Use '{}' instead.", - }, - line: 2, - column: 58, - }, - { - messageId: 'bannedTypeMessage', - data: { - name: 'String', - customMessage: ' Use string instead.', - }, - line: 3, - column: 20, - }, - { - messageId: 'bannedTypeMessage', - data: { - name: 'Object', - customMessage: " Use '{}' instead.", - }, - line: 3, - column: 29, - }, - { - messageId: 'bannedTypeMessage', - data: { name: 'Array', customMessage: '' }, - line: 5, - column: 11, - }, - { - messageId: 'bannedTypeMessage', - data: { - name: 'String', - customMessage: ' Use string instead.', - }, - line: 5, - column: 17, - }, - { - messageId: 'bannedTypeMessage', - data: { - name: 'String', - customMessage: ' Use string instead.', - }, - line: 6, - column: 16, - }, - { - messageId: 'bannedTypeMessage', - data: { - name: 'String', - customMessage: ' Use string instead.', - }, - line: 6, - column: 30, - }, - ], - options, - }, - { - code: 'let a: NS.Bad;', - output: 'let a: NS.Good;', - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: 'NS.Bad', - customMessage: ' Use NS.Good instead.', - }, - line: 1, - column: 8, - }, - ], - options, - }, - { - code: ` -let a: NS.Bad; -let b: Foo; - `, - output: ` -let a: NS.Good; -let b: Foo; - `, - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: 'NS.Bad', - customMessage: ' Use NS.Good instead.', - }, - line: 2, - column: 8, - }, - { - messageId: 'bannedTypeMessage', - data: { - name: 'NS.Bad', - customMessage: ' Use NS.Good instead.', - }, - line: 3, - column: 12, - }, - ], - options, - }, - { - code: 'let foo: {} = {};', - output: 'let foo: object = {};', - options: [ - { - types: { - '{}': { - message: 'Use object instead.', - fixWith: 'object', - }, - }, - }, - ], - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: '{}', - customMessage: ' Use object instead.', - }, - line: 1, - column: 10, - }, - ], - }, - { - code: noFormat` -let foo: {} = {}; -let bar: { } = {}; -let baz: { -} = {}; - `, - output: ` -let foo: object = {}; -let bar: object = {}; -let baz: object = {}; - `, - options: [ - { - types: { - '{ }': { - message: 'Use object instead.', - fixWith: 'object', - }, - }, - }, - ], - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: '{}', - customMessage: ' Use object instead.', - }, - line: 2, - column: 10, - }, - { - messageId: 'bannedTypeMessage', - data: { - name: '{}', - customMessage: ' Use object instead.', - }, - line: 3, - column: 10, - }, - { - messageId: 'bannedTypeMessage', - data: { - name: '{}', - customMessage: ' Use object instead.', - }, - line: 4, - column: 10, - }, - ], - }, - { - code: 'let a: NS.Bad;', - output: 'let a: NS.Good;', - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: 'NS.Bad', - customMessage: ' Use NS.Good instead.', - }, - line: 1, - column: 8, - }, - ], - options: [ - { - types: { - ' NS.Bad ': { - message: 'Use NS.Good instead.', - fixWith: 'NS.Good', - }, - }, - }, - ], - }, - { - code: noFormat`let a: Foo< F >;`, - output: `let a: Foo< T >;`, - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: 'F', - customMessage: ' Use T instead.', - }, - line: 1, - column: 15, - }, - ], - options: [ - { - types: { - ' F ': { - message: 'Use T instead.', - fixWith: 'T', - }, - }, - }, - ], - }, - { - code: 'type Foo = Bar;', - output: null, - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: 'Bar', - customMessage: " Don't use `any` as a type parameter to `Bar`", - }, - line: 1, - column: 12, - }, - ], - options: [ - { - types: { - 'Bar': "Don't use `any` as a type parameter to `Bar`", - }, - }, - ], - }, - { - code: noFormat`type Foo = Bar;`, - output: null, - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: 'Bar', - customMessage: " Don't pass `A, B` as parameters to `Bar`", - }, - line: 1, - column: 12, - }, - ], - options: [ - { - types: { - 'Bar': "Don't pass `A, B` as parameters to `Bar`", - }, - }, - ], - }, - { - code: 'let a: [];', - output: null, - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: '[]', - customMessage: ' `[]` does only allow empty arrays.', - }, - line: 1, - column: 8, - }, - ], - options: [ - { - types: { - '[]': '`[]` does only allow empty arrays.', - }, - }, - ], - }, - { - code: noFormat`let a: [ ] ;`, - output: null, - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: '[]', - customMessage: ' `[]` does only allow empty arrays.', - }, - line: 1, - column: 9, - }, - ], - options: [ - { - types: { - '[]': '`[]` does only allow empty arrays.', - }, - }, - ], - }, - { - code: 'let a: [];', - output: 'let a: any[];', - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: '[]', - customMessage: ' `[]` does only allow empty arrays.', - }, - line: 1, - column: 8, - }, - ], - options: [ - { - types: { - '[]': { - message: '`[]` does only allow empty arrays.', - fixWith: 'any[]', - }, - }, - }, - ], - }, - { - code: 'let a: [[]];', - output: null, - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: '[]', - customMessage: ' `[]` does only allow empty arrays.', - }, - line: 1, - column: 9, - }, - ], - options: [ - { - types: { - '[]': '`[]` does only allow empty arrays.', - }, - }, - ], - }, - { - code: 'type Baz = 1 & Foo;', - output: null, - errors: [ - { - messageId: 'bannedTypeMessage', - }, - ], - options: [ - { - types: { - Foo: { message: '' }, - }, - }, - ], - }, - { - code: 'interface Foo extends Bar {}', - output: null, - errors: [ - { - messageId: 'bannedTypeMessage', - }, - ], - options: [ - { - types: { - Bar: { message: '' }, - }, - }, - ], - }, - { - code: 'interface Foo extends Bar, Baz {}', - output: null, - errors: [ - { - messageId: 'bannedTypeMessage', - }, - ], - options: [ - { - types: { - Bar: { message: '' }, - }, - }, - ], - }, - { - code: 'class Foo implements Bar {}', - output: null, - errors: [ - { - messageId: 'bannedTypeMessage', - }, - ], - options: [ - { - types: { - Bar: { message: '' }, - }, - }, - ], - }, - { - code: 'class Foo implements Bar, Baz {}', - output: null, - errors: [ - { - messageId: 'bannedTypeMessage', - }, - ], - options: [ - { - types: { - Bar: { message: 'Bla' }, - }, - }, - ], - }, - ...objectReduceKey( - TYPE_KEYWORDS, - (acc: TSESLint.InvalidTestCase[], key) => { - acc.push({ - code: `function foo(x: ${key}) {}`, - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: key, - customMessage: '', - }, - line: 1, - column: 17, - }, - ], - options: [ - { - extendDefaults: false, - types: { - [key]: null, - }, - }, - ], - }); - return acc; - }, - [], - ), - ], -}); diff --git a/packages/eslint-plugin/tests/rules/no-restricted-types.test.ts b/packages/eslint-plugin/tests/rules/no-restricted-types.test.ts new file mode 100644 index 000000000000..28bf5435828a --- /dev/null +++ b/packages/eslint-plugin/tests/rules/no-restricted-types.test.ts @@ -0,0 +1,628 @@ +import { noFormat, RuleTester } from '@typescript-eslint/rule-tester'; + +import rule from '../../src/rules/no-restricted-types'; + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('no-restricted-types', rule, { + valid: [ + 'let f = Object();', + 'let f: { x: number; y: number } = { x: 1, y: 1 };', + { + code: 'let f = Object();', + options: [{ types: { Object: true } }], + }, + { + code: 'let f = Object(false);', + options: [{ types: { Object: true } }], + }, + { + code: 'let g = Object.create(null);', + options: [{ types: { Object: true } }], + }, + { + code: 'let e: namespace.Object;', + options: [{ types: { Object: true } }], + }, + { + code: 'let value: _.NS.Banned;', + options: [{ types: { 'NS.Banned': true } }], + }, + { + code: 'let value: NS.Banned._;', + options: [{ types: { 'NS.Banned': true } }], + }, + ], + invalid: [ + { + code: 'let value: bigint;', + output: null, + errors: [ + { + messageId: 'bannedTypeMessage', + data: { + customMessage: ' Use Ok instead.', + name: 'bigint', + }, + line: 1, + column: 12, + }, + ], + options: [{ types: { bigint: 'Use Ok instead.' } }], + }, + { + code: 'let value: boolean;', + output: null, + errors: [ + { + messageId: 'bannedTypeMessage', + data: { + customMessage: ' Use Ok instead.', + name: 'boolean', + }, + line: 1, + column: 12, + }, + ], + options: [{ types: { boolean: 'Use Ok instead.' } }], + }, + { + code: 'let value: never;', + output: null, + errors: [ + { + messageId: 'bannedTypeMessage', + data: { + customMessage: ' Use Ok instead.', + name: 'never', + }, + line: 1, + column: 12, + }, + ], + options: [{ types: { never: 'Use Ok instead.' } }], + }, + { + code: 'let value: null;', + output: null, + errors: [ + { + messageId: 'bannedTypeMessage', + data: { + customMessage: ' Use Ok instead.', + name: 'null', + }, + line: 1, + column: 12, + }, + ], + options: [{ types: { null: 'Use Ok instead.' } }], + }, + { + code: 'let value: number;', + output: null, + errors: [ + { + messageId: 'bannedTypeMessage', + data: { + customMessage: ' Use Ok instead.', + name: 'number', + }, + line: 1, + column: 12, + }, + ], + options: [{ types: { number: 'Use Ok instead.' } }], + }, + { + code: 'let value: object;', + output: null, + errors: [ + { + messageId: 'bannedTypeMessage', + data: { + customMessage: ' Use Ok instead.', + name: 'object', + }, + line: 1, + column: 12, + }, + ], + options: [{ types: { object: 'Use Ok instead.' } }], + }, + { + code: 'let value: string;', + output: null, + errors: [ + { + messageId: 'bannedTypeMessage', + data: { + customMessage: ' Use Ok instead.', + name: 'string', + }, + line: 1, + column: 12, + }, + ], + options: [{ types: { string: 'Use Ok instead.' } }], + }, + { + code: 'let value: symbol;', + output: null, + errors: [ + { + messageId: 'bannedTypeMessage', + data: { + customMessage: ' Use Ok instead.', + name: 'symbol', + }, + line: 1, + column: 12, + }, + ], + options: [{ types: { symbol: 'Use Ok instead.' } }], + }, + { + code: 'let value: undefined;', + output: null, + errors: [ + { + messageId: 'bannedTypeMessage', + data: { + customMessage: ' Use Ok instead.', + name: 'undefined', + }, + line: 1, + column: 12, + }, + ], + options: [{ types: { undefined: 'Use Ok instead.' } }], + }, + { + code: 'let value: unknown;', + output: null, + errors: [ + { + messageId: 'bannedTypeMessage', + data: { + customMessage: ' Use Ok instead.', + name: 'unknown', + }, + line: 1, + column: 12, + }, + ], + options: [{ types: { unknown: 'Use Ok instead.' } }], + }, + { + code: 'let value: void;', + output: null, + errors: [ + { + messageId: 'bannedTypeMessage', + data: { + customMessage: ' Use Ok instead.', + name: 'void', + }, + line: 1, + column: 12, + }, + ], + options: [{ types: { void: 'Use Ok instead.' } }], + }, + { + code: 'let value: [];', + output: null, + errors: [ + { + messageId: 'bannedTypeMessage', + data: { + customMessage: ' Use unknown[] instead.', + name: '[]', + }, + line: 1, + column: 12, + }, + ], + options: [{ types: { '[]': 'Use unknown[] instead.' } }], + }, + { + code: noFormat`let value: [ ];`, + output: null, + errors: [ + { + messageId: 'bannedTypeMessage', + data: { + customMessage: ' Use unknown[] instead.', + name: '[]', + }, + line: 1, + column: 12, + }, + ], + options: [{ types: { '[]': 'Use unknown[] instead.' } }], + }, + { + code: 'let value: [[]];', + output: null, + errors: [ + { + messageId: 'bannedTypeMessage', + data: { + customMessage: ' Use unknown[] instead.', + name: '[]', + }, + line: 1, + column: 13, + }, + ], + options: [{ types: { '[]': 'Use unknown[] instead.' } }], + }, + { + code: 'let value: Banned;', + output: null, + errors: [ + { + messageId: 'bannedTypeMessage', + data: { + customMessage: '', + name: 'Banned', + }, + line: 1, + column: 12, + }, + ], + options: [{ types: { Banned: true } }], + }, + { + code: 'let value: Banned;', + output: null, + errors: [ + { + messageId: 'bannedTypeMessage', + data: { + customMessage: " Use '{}' instead.", + name: 'Banned', + }, + line: 1, + column: 12, + }, + ], + options: [{ types: { Banned: "Use '{}' instead." } }], + }, + { + code: 'let value: Banned[];', + output: null, + errors: [ + { + messageId: 'bannedTypeMessage', + data: { + customMessage: " Use '{}' instead.", + name: 'Banned', + }, + line: 1, + column: 12, + }, + ], + options: [{ types: { Banned: "Use '{}' instead." } }], + }, + { + code: 'let value: [Banned];', + output: null, + errors: [ + { + messageId: 'bannedTypeMessage', + data: { + customMessage: " Use '{}' instead.", + name: 'Banned', + }, + line: 1, + column: 13, + }, + ], + options: [{ types: { Banned: "Use '{}' instead." } }], + }, + { + code: 'let value: Banned;', + output: null, + errors: [ + { + messageId: 'bannedTypeMessage', + data: { + customMessage: '', + name: 'Banned', + }, + line: 1, + column: 12, + }, + ], + options: [{ types: { Banned: '' } }], + }, + { + code: 'let b: { c: Banned };', + output: 'let b: { c: Ok };', + errors: [ + { + messageId: 'bannedTypeMessage', + data: { + name: 'Banned', + customMessage: ' Use Ok instead.', + }, + line: 1, + column: 13, + }, + ], + options: [ + { types: { Banned: { message: 'Use Ok instead.', fixWith: 'Ok' } } }, + ], + }, + { + code: '1 as Banned;', + output: '1 as Ok;', + errors: [ + { + messageId: 'bannedTypeMessage', + data: { + name: 'Banned', + customMessage: ' Use Ok instead.', + }, + line: 1, + column: 6, + }, + ], + options: [ + { types: { Banned: { message: 'Use Ok instead.', fixWith: 'Ok' } } }, + ], + }, + { + code: 'class Derived implements Banned {}', + output: 'class Derived implements Ok {}', + errors: [ + { + messageId: 'bannedTypeMessage', + data: { + name: 'Banned', + customMessage: ' Use Ok instead.', + }, + line: 1, + column: 26, + }, + ], + options: [ + { types: { Banned: { message: 'Use Ok instead.', fixWith: 'Ok' } } }, + ], + }, + { + code: 'class Derived implements Banned1, Banned2 {}', + output: 'class Derived implements Ok1, Ok2 {}', + errors: [ + { + messageId: 'bannedTypeMessage', + data: { + name: 'Banned1', + customMessage: ' Use Ok1 instead.', + }, + line: 1, + column: 26, + }, + { + messageId: 'bannedTypeMessage', + data: { + name: 'Banned2', + customMessage: ' Use Ok2 instead.', + }, + line: 1, + column: 35, + }, + ], + options: [ + { + types: { + Banned1: { message: 'Use Ok1 instead.', fixWith: 'Ok1' }, + Banned2: { message: 'Use Ok2 instead.', fixWith: 'Ok2' }, + }, + }, + ], + }, + { + code: 'interface Derived extends Banned {}', + output: 'interface Derived extends Ok {}', + errors: [ + { + messageId: 'bannedTypeMessage', + data: { + name: 'Banned', + customMessage: ' Use Ok instead.', + }, + line: 1, + column: 27, + }, + ], + options: [ + { types: { Banned: { message: 'Use Ok instead.', fixWith: 'Ok' } } }, + ], + }, + { + code: 'type Intersection = Banned & {};', + output: 'type Intersection = Ok & {};', + errors: [ + { + messageId: 'bannedTypeMessage', + data: { + name: 'Banned', + customMessage: ' Use Ok instead.', + }, + line: 1, + column: 21, + }, + ], + options: [ + { types: { Banned: { message: 'Use Ok instead.', fixWith: 'Ok' } } }, + ], + }, + { + code: 'type Union = Banned | {};', + output: 'type Union = Ok | {};', + errors: [ + { + messageId: 'bannedTypeMessage', + data: { + name: 'Banned', + customMessage: ' Use Ok instead.', + }, + line: 1, + column: 14, + }, + ], + options: [ + { types: { Banned: { message: 'Use Ok instead.', fixWith: 'Ok' } } }, + ], + }, + { + code: 'let value: NS.Banned;', + output: 'let value: NS.Ok;', + errors: [ + { + messageId: 'bannedTypeMessage', + data: { + name: 'NS.Banned', + customMessage: ' Use NS.Ok instead.', + }, + line: 1, + column: 12, + }, + ], + options: [ + { + types: { + 'NS.Banned': { + message: 'Use NS.Ok instead.', + fixWith: 'NS.Ok', + }, + }, + }, + ], + }, + { + code: 'let value: {} = {};', + output: 'let value: object = {};', + options: [ + { + types: { + '{}': { + message: 'Use object instead.', + fixWith: 'object', + }, + }, + }, + ], + errors: [ + { + messageId: 'bannedTypeMessage', + data: { + name: '{}', + customMessage: ' Use object instead.', + }, + line: 1, + column: 12, + }, + ], + }, + { + code: 'let value: NS.Banned;', + output: 'let value: NS.Ok;', + errors: [ + { + messageId: 'bannedTypeMessage', + data: { + name: 'NS.Banned', + customMessage: ' Use NS.Ok instead.', + }, + line: 1, + column: 12, + }, + ], + options: [ + { + types: { + ' NS.Banned ': { + message: 'Use NS.Ok instead.', + fixWith: 'NS.Ok', + }, + }, + }, + ], + }, + { + code: noFormat`let value: Type< Banned >;`, + output: `let value: Type< Ok >;`, + errors: [ + { + messageId: 'bannedTypeMessage', + data: { + name: 'Banned', + customMessage: ' Use Ok instead.', + }, + line: 1, + column: 20, + }, + ], + options: [ + { + types: { + ' Banned ': { + message: 'Use Ok instead.', + fixWith: 'Ok', + }, + }, + }, + ], + }, + { + code: 'type Intersection = Banned;', + output: null, + errors: [ + { + messageId: 'bannedTypeMessage', + data: { + name: 'Banned', + customMessage: " Don't use `any` as a type parameter to `Banned`", + }, + line: 1, + column: 21, + }, + ], + options: [ + { + types: { + 'Banned': "Don't use `any` as a type parameter to `Banned`", + }, + }, + ], + }, + { + code: noFormat`type Intersection = Banned;`, + output: null, + errors: [ + { + messageId: 'bannedTypeMessage', + data: { + name: 'Banned', + customMessage: " Don't pass `A, B` as parameters to `Banned`", + }, + line: 1, + column: 21, + }, + ], + options: [ + { + types: { + 'Banned': "Don't pass `A, B` as parameters to `Banned`", + }, + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/tests/rules/no-unsafe-function-type.test.ts b/packages/eslint-plugin/tests/rules/no-unsafe-function-type.test.ts new file mode 100644 index 000000000000..88e2d932a07b --- /dev/null +++ b/packages/eslint-plugin/tests/rules/no-unsafe-function-type.test.ts @@ -0,0 +1,83 @@ +import { RuleTester } from '@typescript-eslint/rule-tester'; + +import rule from '../../src/rules/no-unsafe-function-type'; + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('no-unsafe-function-type', rule, { + valid: [ + 'let value: () => void;', + 'let value: (t: T) => T;', + ` + type Function = () => void; + let value: Function; + `, + ], + invalid: [ + { + code: 'let value: Function;', + output: null, + errors: [ + { + messageId: 'bannedFunctionType', + line: 1, + column: 12, + }, + ], + }, + { + code: 'let value: Function[];', + output: null, + errors: [ + { + messageId: 'bannedFunctionType', + line: 1, + column: 12, + }, + ], + }, + { + code: 'let value: Function | number;', + output: null, + errors: [ + { + messageId: 'bannedFunctionType', + line: 1, + column: 12, + }, + ], + }, + { + code: ` + class Weird implements Function { + // ... + } + `, + output: null, + errors: [ + { + messageId: 'bannedFunctionType', + line: 2, + column: 32, + }, + ], + }, + { + code: ` + interface Weird extends Function { + // ... + } + `, + output: null, + errors: [ + { + messageId: 'bannedFunctionType', + line: 2, + column: 33, + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/tests/rules/no-wrapper-object-types.test.ts b/packages/eslint-plugin/tests/rules/no-wrapper-object-types.test.ts new file mode 100644 index 000000000000..73613259cc22 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/no-wrapper-object-types.test.ts @@ -0,0 +1,242 @@ +/* eslint-disable @typescript-eslint/internal/prefer-ast-types-enum */ +import { RuleTester } from '@typescript-eslint/rule-tester'; + +import rule from '../../src/rules/no-wrapper-object-types'; + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('no-wrapper-object-types', rule, { + valid: [ + '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; + `, + ], + invalid: [ + { + code: 'let value: BigInt;', + output: 'let value: bigint;', + errors: [ + { + data: { typeName: 'BigInt', preferred: 'bigint' }, + messageId: 'bannedClassType', + line: 1, + column: 12, + }, + ], + }, + { + code: 'let value: Boolean;', + output: 'let value: boolean;', + errors: [ + { + data: { typeName: 'Boolean', preferred: 'boolean' }, + messageId: 'bannedClassType', + line: 1, + column: 12, + }, + ], + }, + { + code: 'let value: Number;', + output: 'let value: number;', + errors: [ + { + data: { typeName: 'Number', preferred: 'number' }, + messageId: 'bannedClassType', + line: 1, + column: 12, + }, + ], + }, + { + code: 'let value: Object;', + output: 'let value: object;', + errors: [ + { + data: { typeName: 'Object', preferred: 'object' }, + messageId: 'bannedClassType', + line: 1, + column: 12, + }, + ], + }, + { + code: 'let value: String;', + output: 'let value: string;', + errors: [ + { + data: { typeName: 'String', preferred: 'string' }, + messageId: 'bannedClassType', + line: 1, + column: 12, + }, + ], + }, + { + code: 'let value: Symbol;', + output: 'let value: symbol;', + errors: [ + { + data: { typeName: 'Symbol', preferred: 'symbol' }, + messageId: 'bannedClassType', + line: 1, + column: 12, + }, + ], + }, + { + code: 'let value: Number | Symbol;', + output: 'let value: number | symbol;', + errors: [ + { + data: { typeName: 'Number', preferred: 'number' }, + messageId: 'bannedClassType', + line: 1, + column: 12, + }, + { + data: { typeName: 'Symbol', preferred: 'symbol' }, + messageId: 'bannedClassType', + line: 1, + column: 21, + }, + ], + }, + { + code: 'let value: { property: Number };', + output: 'let value: { property: number };', + errors: [ + { + messageId: 'bannedClassType', + line: 1, + column: 24, + }, + ], + }, + { + code: '0 as Number;', + output: '0 as number;', + errors: [ + { + messageId: 'bannedClassType', + line: 1, + column: 6, + }, + ], + }, + { + code: 'type MyType = Number;', + output: 'type MyType = number;', + errors: [ + { + messageId: 'bannedClassType', + line: 1, + column: 15, + }, + ], + }, + { + code: 'type MyType = [Number];', + output: 'type MyType = [number];', + errors: [ + { + messageId: 'bannedClassType', + line: 1, + column: 16, + }, + ], + }, + { + code: 'class MyClass implements Number {}', + output: null, + errors: [ + { + messageId: 'bannedClassType', + line: 1, + column: 26, + }, + ], + }, + { + code: 'interface MyInterface extends Number {}', + output: null, + errors: [ + { + messageId: 'bannedClassType', + line: 1, + column: 31, + }, + ], + }, + { + code: 'type MyType = Number & String;', + output: 'type MyType = number & string;', + errors: [ + { + data: { preferred: 'number', typeName: 'Number' }, + messageId: 'bannedClassType', + line: 1, + column: 15, + }, + { + data: { preferred: 'string', typeName: 'String' }, + messageId: 'bannedClassType', + line: 1, + column: 24, + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/tests/schema-snapshots/ban-types.shot b/packages/eslint-plugin/tests/schema-snapshots/no-restricted-types.shot similarity index 76% rename from packages/eslint-plugin/tests/schema-snapshots/ban-types.shot rename to packages/eslint-plugin/tests/schema-snapshots/no-restricted-types.shot index a7fe2d555cda..845262dabe82 100644 --- a/packages/eslint-plugin/tests/schema-snapshots/ban-types.shot +++ b/packages/eslint-plugin/tests/schema-snapshots/no-restricted-types.shot @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Rule schemas should be convertible to TS types for documentation purposes ban-types 1`] = ` +exports[`Rule schemas should be convertible to TS types for documentation purposes no-restricted-types 1`] = ` " # SCHEMA: @@ -9,15 +9,6 @@ exports[`Rule schemas should be convertible to TS types for documentation purpos "$defs": { "banConfig": { "oneOf": [ - { - "description": "Bans the type with the default message", - "type": "null" - }, - { - "description": "Un-bans the type (useful when paired with \`extendDefaults\`)", - "enum": [false], - "type": "boolean" - }, { "description": "Bans the type with the default message", "enum": [true], @@ -40,7 +31,6 @@ exports[`Rule schemas should be convertible to TS types for documentation purpos "type": "string" }, "suggest": { - "additionalItems": false, "description": "Types to suggest replacing with.", "items": { "type": "string" @@ -55,9 +45,6 @@ exports[`Rule schemas should be convertible to TS types for documentation purpos }, "additionalProperties": false, "properties": { - "extendDefaults": { - "type": "boolean" - }, "types": { "additionalProperties": { "$ref": "#/items/0/$defs/banConfig" @@ -85,15 +72,10 @@ type BanConfig = /** Bans the type with a custom message */ | string /** Bans the type with the default message */ - | null - /** Bans the type with the default message */ - | true - /** Un-bans the type (useful when paired with \`extendDefaults\`) */ - | false; + | true; type Options = [ { - extendDefaults?: boolean; types?: { [k: string]: BanConfig; }; diff --git a/packages/eslint-plugin/tests/schema-snapshots/no-unsafe-function-type.shot b/packages/eslint-plugin/tests/schema-snapshots/no-unsafe-function-type.shot new file mode 100644 index 000000000000..0317a17c3ad5 --- /dev/null +++ b/packages/eslint-plugin/tests/schema-snapshots/no-unsafe-function-type.shot @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Rule schemas should be convertible to TS types for documentation purposes no-unsafe-function-type 1`] = ` +" +# SCHEMA: + +[] + + +# TYPES: + +/** No options declared */ +type Options = [];" +`; diff --git a/packages/eslint-plugin/tests/schema-snapshots/no-wrapper-object-types.shot b/packages/eslint-plugin/tests/schema-snapshots/no-wrapper-object-types.shot new file mode 100644 index 000000000000..247f1dd2e183 --- /dev/null +++ b/packages/eslint-plugin/tests/schema-snapshots/no-wrapper-object-types.shot @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Rule schemas should be convertible to TS types for documentation purposes no-wrapper-object-types 1`] = ` +" +# SCHEMA: + +[] + + +# TYPES: + +/** No options declared */ +type Options = [];" +`; diff --git a/packages/scope-manager/tests/test-utils/serializers/baseSerializer.ts b/packages/scope-manager/tests/test-utils/serializers/baseSerializer.ts index cb619d683a5b..509608a30fb4 100644 --- a/packages/scope-manager/tests/test-utils/serializers/baseSerializer.ts +++ b/packages/scope-manager/tests/test-utils/serializers/baseSerializer.ts @@ -33,7 +33,7 @@ function createSerializer( ): string { const id = thing.$id != null ? `$${thing.$id}` : ''; // If `type` is a base class, we should print out the name of the subclass - // eslint-disable-next-line @typescript-eslint/ban-types + // eslint-disable-next-line @typescript-eslint/no-wrapper-object-types const constructorName = (Object.getPrototypeOf(thing) as Object) .constructor.name; diff --git a/packages/typescript-eslint/src/configs/all.ts b/packages/typescript-eslint/src/configs/all.ts index f01ac17c8ddb..dc899379b164 100644 --- a/packages/typescript-eslint/src/configs/all.ts +++ b/packages/typescript-eslint/src/configs/all.ts @@ -24,7 +24,6 @@ export default ( '@typescript-eslint/await-thenable': 'error', '@typescript-eslint/ban-ts-comment': 'error', '@typescript-eslint/ban-tslint-comment': 'error', - '@typescript-eslint/ban-types': 'error', '@typescript-eslint/class-literal-property-style': 'error', 'class-methods-use-this': 'off', '@typescript-eslint/class-methods-use-this': 'error', @@ -94,6 +93,7 @@ export default ( '@typescript-eslint/no-require-imports': 'error', 'no-restricted-imports': 'off', '@typescript-eslint/no-restricted-imports': 'error', + '@typescript-eslint/no-restricted-types': 'error', 'no-shadow': 'off', '@typescript-eslint/no-shadow': 'error', '@typescript-eslint/no-this-alias': 'error', @@ -109,6 +109,7 @@ export default ( '@typescript-eslint/no-unsafe-call': 'error', '@typescript-eslint/no-unsafe-declaration-merging': 'error', '@typescript-eslint/no-unsafe-enum-comparison': 'error', + '@typescript-eslint/no-unsafe-function-type': 'error', '@typescript-eslint/no-unsafe-member-access': 'error', '@typescript-eslint/no-unsafe-return': 'error', '@typescript-eslint/no-unsafe-unary-minus': 'error', @@ -121,6 +122,7 @@ export default ( 'no-useless-constructor': 'off', '@typescript-eslint/no-useless-constructor': 'error', '@typescript-eslint/no-useless-empty-export': 'error', + '@typescript-eslint/no-wrapper-object-types': 'error', '@typescript-eslint/non-nullable-type-assertion-style': 'error', 'no-throw-literal': 'off', '@typescript-eslint/only-throw-error': 'error', diff --git a/packages/typescript-eslint/src/configs/recommended-type-checked.ts b/packages/typescript-eslint/src/configs/recommended-type-checked.ts index 47b8c9b08ef6..993f7baa90ed 100644 --- a/packages/typescript-eslint/src/configs/recommended-type-checked.ts +++ b/packages/typescript-eslint/src/configs/recommended-type-checked.ts @@ -21,7 +21,6 @@ export default ( rules: { '@typescript-eslint/await-thenable': 'error', '@typescript-eslint/ban-ts-comment': 'error', - '@typescript-eslint/ban-types': 'error', 'no-array-constructor': 'off', '@typescript-eslint/no-array-constructor': 'error', '@typescript-eslint/no-array-delete': 'error', @@ -49,6 +48,7 @@ export default ( '@typescript-eslint/no-unsafe-call': 'error', '@typescript-eslint/no-unsafe-declaration-merging': 'error', '@typescript-eslint/no-unsafe-enum-comparison': 'error', + '@typescript-eslint/no-unsafe-function-type': 'error', '@typescript-eslint/no-unsafe-member-access': 'error', '@typescript-eslint/no-unsafe-return': 'error', '@typescript-eslint/no-unsafe-unary-minus': 'error', @@ -56,6 +56,7 @@ export default ( '@typescript-eslint/no-unused-expressions': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', + '@typescript-eslint/no-wrapper-object-types': 'error', 'no-throw-literal': 'off', '@typescript-eslint/only-throw-error': 'error', '@typescript-eslint/prefer-as-const': 'error', diff --git a/packages/typescript-eslint/src/configs/recommended.ts b/packages/typescript-eslint/src/configs/recommended.ts index 0c9d5bb3c91e..1e6be3251a08 100644 --- a/packages/typescript-eslint/src/configs/recommended.ts +++ b/packages/typescript-eslint/src/configs/recommended.ts @@ -20,7 +20,6 @@ export default ( name: 'typescript-eslint/recommended', rules: { '@typescript-eslint/ban-ts-comment': 'error', - '@typescript-eslint/ban-types': 'error', 'no-array-constructor': 'off', '@typescript-eslint/no-array-constructor': 'error', '@typescript-eslint/no-duplicate-enum-values': 'error', @@ -34,10 +33,12 @@ export default ( '@typescript-eslint/no-this-alias': 'error', '@typescript-eslint/no-unnecessary-type-constraint': 'error', '@typescript-eslint/no-unsafe-declaration-merging': 'error', + '@typescript-eslint/no-unsafe-function-type': 'error', 'no-unused-expressions': 'off', '@typescript-eslint/no-unused-expressions': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', + '@typescript-eslint/no-wrapper-object-types': 'error', '@typescript-eslint/prefer-as-const': 'error', '@typescript-eslint/prefer-namespace-keyword': 'error', '@typescript-eslint/triple-slash-reference': 'error', diff --git a/packages/typescript-eslint/src/configs/strict-type-checked.ts b/packages/typescript-eslint/src/configs/strict-type-checked.ts index 61d0a4d579a2..fb53665756e3 100644 --- a/packages/typescript-eslint/src/configs/strict-type-checked.ts +++ b/packages/typescript-eslint/src/configs/strict-type-checked.ts @@ -24,7 +24,6 @@ export default ( 'error', { minimumDescriptionLength: 10 }, ], - '@typescript-eslint/ban-types': 'error', 'no-array-constructor': 'off', '@typescript-eslint/no-array-constructor': 'error', '@typescript-eslint/no-array-delete': 'error', @@ -64,6 +63,7 @@ export default ( '@typescript-eslint/no-unsafe-call': 'error', '@typescript-eslint/no-unsafe-declaration-merging': 'error', '@typescript-eslint/no-unsafe-enum-comparison': 'error', + '@typescript-eslint/no-unsafe-function-type': 'error', '@typescript-eslint/no-unsafe-member-access': 'error', '@typescript-eslint/no-unsafe-return': 'error', '@typescript-eslint/no-unsafe-unary-minus': 'error', @@ -73,6 +73,7 @@ export default ( '@typescript-eslint/no-unused-vars': 'error', 'no-useless-constructor': 'off', '@typescript-eslint/no-useless-constructor': 'error', + '@typescript-eslint/no-wrapper-object-types': 'error', 'no-throw-literal': 'off', '@typescript-eslint/only-throw-error': 'error', '@typescript-eslint/prefer-as-const': 'error', diff --git a/packages/typescript-eslint/src/configs/strict.ts b/packages/typescript-eslint/src/configs/strict.ts index 4a581ff95335..d6c5a37e9c54 100644 --- a/packages/typescript-eslint/src/configs/strict.ts +++ b/packages/typescript-eslint/src/configs/strict.ts @@ -23,7 +23,6 @@ export default ( 'error', { minimumDescriptionLength: 10 }, ], - '@typescript-eslint/ban-types': 'error', 'no-array-constructor': 'off', '@typescript-eslint/no-array-constructor': 'error', '@typescript-eslint/no-duplicate-enum-values': 'error', @@ -42,12 +41,14 @@ export default ( '@typescript-eslint/no-this-alias': 'error', '@typescript-eslint/no-unnecessary-type-constraint': 'error', '@typescript-eslint/no-unsafe-declaration-merging': 'error', + '@typescript-eslint/no-unsafe-function-type': 'error', 'no-unused-expressions': 'off', '@typescript-eslint/no-unused-expressions': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', 'no-useless-constructor': 'off', '@typescript-eslint/no-useless-constructor': 'error', + '@typescript-eslint/no-wrapper-object-types': 'error', '@typescript-eslint/prefer-as-const': 'error', '@typescript-eslint/prefer-literal-enum-member': 'error', '@typescript-eslint/prefer-namespace-keyword': 'error', diff --git a/packages/utils/src/ts-eslint/Rule.ts b/packages/utils/src/ts-eslint/Rule.ts index 899b8a98346b..fe3fa0412d07 100644 --- a/packages/utils/src/ts-eslint/Rule.ts +++ b/packages/utils/src/ts-eslint/Rule.ts @@ -690,7 +690,7 @@ never only allow unidirectional) export type LooseRuleCreateFunction = (context: any) => Record< string, /* - eslint-disable-next-line @typescript-eslint/ban-types -- + eslint-disable-next-line @typescript-eslint/no-unsafe-function-type -- intentionally use Function here to give us the basic "is a function" validation without enforcing specific argument types so that different AST types can still be passed to configs diff --git a/packages/website/blog/2024-05-27-announcing-typescript-eslint-v8-beta.mdx b/packages/website/blog/2024-05-27-announcing-typescript-eslint-v8-beta.mdx index 6bda8fcbf66e..b30789289077 100644 --- a/packages/website/blog/2024-05-27-announcing-typescript-eslint-v8-beta.mdx +++ b/packages/website/blog/2024-05-27-announcing-typescript-eslint-v8-beta.mdx @@ -187,11 +187,45 @@ Several rules are changed in significant enough ways to be considered breaking c - If you want to have the rule check conditional tests, set its [`ignoreConditionalTests` option](/rules/prefer-nullish-coalescing/#ignoreconditionaltests) to `false` in your ESLint config - [feat(eslint-plugin): [no-unused-vars] align catch behavior to ESLint 9](https://github.com/typescript-eslint/typescript-eslint/pull/8971) - If you want [`@typescript-eslint/no-unused-vars`](/rules/no-unused-vars) to ignore caught errors, enable its `caughtErrors` option to `'none'` in your ESLint config + +#### Replacement of `ban-types` + +[`@typescript-eslint/ban-types`](https://typescript-eslint.io/rules/ban-types) has long been one of the more controversial rules in typescript-eslint. +It served two purposes: + +- Allowing users to ban a configurable list of types from being used in type annotations +- Banning confusing or dangerous built-in types such as `Function` and `Number` + +Notably, `ban-types` banned the built-in `{}` ("empty object") type in TypeScript. +The `{}` type is a common source of confusion for TypeScript developers because it matches _any non-nullable_ value, including primitives like `""`. + +Banning `{}` in `ban-types` was helpful to prevent developers from accidentally using it instead of a more safe type such as `object`. +On the other hand, there are legitimate uses for `{}`, and banning it by default was harmful in those cases. + +typescript-eslint v8 deletes the `ban-types` rule and replaces it with several more targeted rules: + +- [`@typescript-eslint/no-restricted-types`](/rules/no-restricted-types) is the new rule for banning a configurable list of type names. + It has no options enabled by default. +- [`@typescript-eslint/no-empty-object-type`](/rules/no-empty-object-type) bans the built-in `{}` type in confusing locations. +- [`@typescript-eslint/no-unsafe-function-type`](/rules/no-unsafe-function-type) bans the built-in `Function` type +- [`@typescript-eslint/no-wrapper-object-types`](/rules/no-wrapper-object-types) bans `Object` and built-in class wrappers such as `Number`. + +To migrate to the new rules: + +- If you were disabling the ban on `{}`, consider enabling [`@typescript-eslint/no-empty-object-type`](https://v8--typescript-eslint.netlify.app/rules/no-empty-object-type), as it allows some cases of `{}` that were previously banned. +- If you were banning any configurable types lists, provide a similar configuration to [`no-restricted-types`](/rules/no-restricted-types). +- If you have [`@typescript-eslint/ban-types`](/rules/ban-types) manually enabled, it will no longer ban: + - `{}` or `object`: use a [recommended config](/users/configs) or manually enable [`@typescript-eslint/no-empty-object-type`](https://v8--typescript-eslint.netlify.app/rules/no-empty-object-type) + - `Function`: use a [recommended config](/users/configs) or manually enable [`@typescript-eslint/no-unsafe-function-type`](https://v8--typescript-eslint.netlify.app/rules/no-unsafe-function-type) + - `Number` or other built-in uppercase types: use a [recommended config](/users/configs) or manually enable [`@typescript-eslint/no-wrapper-object-types`](https://v8--typescript-eslint.netlify.app/rules/no-wrapper-object-types) +- If you have [`@typescript-eslint/no-empty-interface`](/rules/no-empty-interface) manually enabled, remove that, and instead either use a [recommended config](/users/configs) or manually enable [`@typescript-eslint/no-empty-object-type`](https://v8--typescript-eslint.netlify.app/rules/no-empty-object-type) + +For more details, see the issues and pull requests that split apart the `ban-types` rule: + +- [Enhancement: [ban-types] Split the {} ban into a separate, better-phrased rule](https://github.com/typescript-eslint/typescript-eslint/issues/8700) - [feat(eslint-plugin): split no-empty-object-type out from ban-types and no-empty-interface](https://github.com/typescript-eslint/typescript-eslint/pull/8977) - - If you have [`@typescript-eslint/ban-types`](/rules/ban-types) manually enabled, it will no longer ban the `{}` or `object` types; use a [recommended config](/users/configs) or manually enable [`@typescript-eslint/no-empty-object-type`](https://v8--typescript-eslint.netlify.app/rules/no-empty-object-type) - - If you have [`@typescript-eslint/no-empty-interface`](/rules/no-empty-interface) manually enabled, remove that, and instead either use a [recommended config](/users/configs) or manually enable [`@typescript-eslint/no-empty-object-type`](https://v8--typescript-eslint.netlify.app/rules/no-empty-object-type) -- ⏳ [Enhancement: [ban-types] Split into default-less no-restricted-types and more targeted type ban rule(s)](https://github.com/typescript-eslint/typescript-eslint/issues/8978) - - [#9102](https://github.com/typescript-eslint/typescript-eslint/pull/9102) is still in review; we'll update this post when the migration path is settled +- [Enhancement: [ban-types] Split into default-less no-restricted-types and more targeted type ban rule(s)](https://github.com/typescript-eslint/typescript-eslint/issues/8978) +- [feat(eslint-plugin): replace ban-types with no-restricted-types, no-unsafe-function-type, no-wrapper-object-types](https://github.com/typescript-eslint/typescript-eslint/pull/9102) ### Tooling Breaking Changes diff --git a/packages/website/plugins/generated-rule-docs/index.ts b/packages/website/plugins/generated-rule-docs/index.ts index fd4d8d50efc8..c0a7265d28b4 100644 --- a/packages/website/plugins/generated-rule-docs/index.ts +++ b/packages/website/plugins/generated-rule-docs/index.ts @@ -10,7 +10,6 @@ import { insertFormattingNotice } from './insertions/insertFormattingNotice'; import { insertNewRuleReferences } from './insertions/insertNewRuleReferences'; import { insertResources } from './insertions/insertResources'; import { insertRuleDescription } from './insertions/insertRuleDescription'; -import { insertSpecialCaseOptions } from './insertions/insertSpecialCaseOptions'; import { insertWhenNotToUseIt } from './insertions/insertWhenNotToUseIt'; import { removeSourceCodeNotice } from './removeSourceCodeNotice'; @@ -35,7 +34,6 @@ export const generatedRuleDocs: Plugin = () => { ? insertBaseRuleReferences(page) : await insertNewRuleReferences(page); - insertSpecialCaseOptions(page); insertWhenNotToUseIt(page); insertResources(page); addESLintHashToCodeBlocksMeta(page, eslintrc); diff --git a/packages/website/plugins/generated-rule-docs/insertions/insertNewRuleReferences.ts b/packages/website/plugins/generated-rule-docs/insertions/insertNewRuleReferences.ts index 9d6ccdfa7be2..fcd6196feda8 100644 --- a/packages/website/plugins/generated-rule-docs/insertions/insertNewRuleReferences.ts +++ b/packages/website/plugins/generated-rule-docs/insertions/insertNewRuleReferences.ts @@ -20,14 +20,6 @@ const COMPLICATED_RULE_OPTIONS = new Set([ 'naming-convention', ]); -/** - * Rules that do funky things with their defaults and require special code - * rather than just JSON.stringify-ing their defaults blob - */ -const SPECIAL_CASE_DEFAULTS = new Map([ - ['ban-types', '[{ /* See below for default options */ }]'], -]); - const PRETTIER_CONFIG_PATH = path.resolve( __dirname, '..', @@ -190,10 +182,7 @@ function linkToConfigs(configs: string[]): mdast.Node[] { } function getRuleDefaultOptions(page: RuleDocsPage): string { - const defaults = - SPECIAL_CASE_DEFAULTS.get(page.file.stem) ?? - JSON.stringify(page.rule.defaultOptions); - + const defaults = JSON.stringify(page.rule.defaultOptions); const recommended = page.rule.meta.docs.recommended; return typeof recommended === 'object' diff --git a/packages/website/plugins/generated-rule-docs/insertions/insertSpecialCaseOptions.ts b/packages/website/plugins/generated-rule-docs/insertions/insertSpecialCaseOptions.ts deleted file mode 100644 index cacce340d634..000000000000 --- a/packages/website/plugins/generated-rule-docs/insertions/insertSpecialCaseOptions.ts +++ /dev/null @@ -1,41 +0,0 @@ -import * as fs from 'fs'; -import type * as mdast from 'mdast'; -import * as path from 'path'; - -import { eslintPluginDirectory } from '../../utils/rules'; -import type { RuleDocsPage } from '../RuleDocsPage'; - -export function insertSpecialCaseOptions(page: RuleDocsPage): void { - if (page.file.stem !== 'ban-types') { - return; - } - - const detailsElement = page.children.find( - (node): node is mdast.Parent => - (node as mdast.Node & { name: string }).name === 'details' && - (node as mdast.Parent).children.length > 0 && - ((node as mdast.Parent).children[0] as { name: string }).name === - 'summary', - ); - - if (!detailsElement) { - throw new Error('Could not find default injection site in ban-types'); - } - - const defaultOptions = /^const defaultTypes.+?^\};$/msu.exec( - fs.readFileSync( - path.join(eslintPluginDirectory, 'src/rules/ban-types.ts'), - 'utf8', - ), - )?.[0]; - - if (!defaultOptions) { - throw new Error('Could not find default options for ban-types'); - } - - detailsElement.children.push({ - lang: 'ts', - type: 'code', - value: defaultOptions, - } as mdast.Code); -} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy