From 452fe6d861ecec7f481290f07e17e97f26ff5fff Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Wed, 15 May 2024 14:07:20 -0700 Subject: [PATCH 01/32] feat(eslint-plugin): add no-restricted-types, no-uppercase-alias-types rules --- .../eslint-plugin/docs/rules/ban-types.mdx | 12 + .../docs/rules/no-restricted-types.mdx | 71 ++ .../docs/rules/no-uppercase-wrapper-types.mdx | 70 ++ packages/eslint-plugin/src/configs/all.ts | 3 +- .../src/configs/recommended-type-checked.ts | 2 +- .../eslint-plugin/src/configs/recommended.ts | 2 +- .../src/configs/strict-type-checked.ts | 2 +- packages/eslint-plugin/src/configs/strict.ts | 2 +- packages/eslint-plugin/src/rules/ban-types.ts | 7 +- packages/eslint-plugin/src/rules/index.ts | 4 + .../src/rules/no-restricted-types.ts | 221 ++++++ .../src/rules/no-uppercase-wrapper-types.ts | 80 +++ .../no-uppercase-wrapper-types.shot | 41 ++ .../tests/rules/no-restricted-types.test.ts | 628 ++++++++++++++++++ .../rules/no-uppercase-wrapper-types.test.ts | 222 +++++++ .../schema-snapshots/no-restricted-types.shot | 85 +++ .../no-uppercase-wrapper-types.shot | 14 + packages/typescript-eslint/src/configs/all.ts | 3 +- .../src/configs/recommended-type-checked.ts | 2 +- .../src/configs/recommended.ts | 2 +- .../src/configs/strict-type-checked.ts | 2 +- .../typescript-eslint/src/configs/strict.ts | 2 +- 22 files changed, 1466 insertions(+), 11 deletions(-) create mode 100644 packages/eslint-plugin/docs/rules/no-restricted-types.mdx create mode 100644 packages/eslint-plugin/docs/rules/no-uppercase-wrapper-types.mdx create mode 100644 packages/eslint-plugin/src/rules/no-restricted-types.ts create mode 100644 packages/eslint-plugin/src/rules/no-uppercase-wrapper-types.ts create mode 100644 packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-uppercase-wrapper-types.shot create mode 100644 packages/eslint-plugin/tests/rules/no-restricted-types.test.ts create mode 100644 packages/eslint-plugin/tests/rules/no-uppercase-wrapper-types.test.ts create mode 100644 packages/eslint-plugin/tests/schema-snapshots/no-restricted-types.shot create mode 100644 packages/eslint-plugin/tests/schema-snapshots/no-uppercase-wrapper-types.shot diff --git a/packages/eslint-plugin/docs/rules/ban-types.mdx b/packages/eslint-plugin/docs/rules/ban-types.mdx index b52ae22df7d3..2bd3d7e637be 100644 --- a/packages/eslint-plugin/docs/rules/ban-types.mdx +++ b/packages/eslint-plugin/docs/rules/ban-types.mdx @@ -15,6 +15,16 @@ 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. +:::danger Deprecated +This rule is deprecated and now split across several rules: + +- [`no-empty-object-type`](./no-empty-object-type): banning the built-in `{}` type in confusing locations +- [`no-restricted-types`](./no-restricted-types.mdx): allowing banning a configurable list of type names +- [`no-uppercase-wrapper-types`](./no-uppercase-wrapper-types.mdx): banning built-in class wrappers such as `Number` + +This deprecated rule will be removed in a future major version of typescript-eslint. +::: + ## Examples Examples of code with the default options: @@ -131,3 +141,5 @@ You might consider using [ESLint disable comments](https://eslint.org/docs/lates ## Related To - [`no-empty-object-type`](./no-empty-object-type.mdx) +- [`no-restricted-types`](./no-restricted-types.mdx) +- [`no-uppercase-wrapper-types`](./no-uppercase-wrapper-types.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..56814a4720de --- /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 + +- [`ban-types`](./ban-types.mdx) +- [`no-empty-object-type`](./no-empty-object-type.mdx) +- [`no-uppercase-wrapper-types`](./no-uppercase-wrapper-types.mdx) diff --git a/packages/eslint-plugin/docs/rules/no-uppercase-wrapper-types.mdx b/packages/eslint-plugin/docs/rules/no-uppercase-wrapper-types.mdx new file mode 100644 index 000000000000..e376deb83c4a --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-uppercase-wrapper-types.mdx @@ -0,0 +1,70 @@ +--- +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-uppercase-wrapper-types** for documentation. + +The JavaScript language includes a set of primitives such as `boolean` and `number`s. +JavaScript also contains 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). +TypeScript represents the primitive in lower-case types and the wrapper classes with upper-case + +Using the primitives like `0` instead of class constructors like `Number(0)` is generally considered a JavaScript best practice. +As a result, using the lower-case primitive type names like `number` instead of class names like `Number` is generally considered a TypeScript best practice. + +Similarly, TypeScript's `Function` type allows any function-like value. +It's generally better to specify function parameter and return types with function type syntax. + +Examples of code for this rule: + + + + +```ts +let myBigInt: BigInt; +let myBoolean: Boolean; +let myNumber: Number; +let myObject: Object; +let myString: String; +let mySymbol: Symbol; + +let myFunction: Function; +myFunction = () => {}; +``` + + + + +```ts +let myBigint: bigint; +let myBoolean: boolean; +let myNumber: number; +let myObject: object; +let myString: string; +let mySymbol: symbol; + +let myFunction: () => void; +myFunction = () => {}; +``` + + + + +## 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 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 `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 + +- [`ban-types`](./ban-types.mdx) +- [`no-empty-object-type`](./no-empty-object-type.mdx) +- [`no-restricted-types`](./no-restricted-types.mdx) diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts index 0ac9c3653d97..a4f3c388f13e 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', @@ -106,6 +106,7 @@ export = { '@typescript-eslint/no-unused-expressions': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', + '@typescript-eslint/no-uppercase-wrapper-types': 'error', 'no-use-before-define': 'off', '@typescript-eslint/no-use-before-define': 'error', 'no-useless-constructor': 'off', diff --git a/packages/eslint-plugin/src/configs/recommended-type-checked.ts b/packages/eslint-plugin/src/configs/recommended-type-checked.ts index 858eb6555090..0c36299c0e0c 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-base-to-string': 'error', @@ -42,6 +41,7 @@ export = { '@typescript-eslint/no-unsafe-return': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', + '@typescript-eslint/no-uppercase-wrapper-types': 'error', '@typescript-eslint/no-var-requires': 'error', '@typescript-eslint/prefer-as-const': 'error', 'require-await': 'off', diff --git a/packages/eslint-plugin/src/configs/recommended.ts b/packages/eslint-plugin/src/configs/recommended.ts index c93e38eabb2f..7e0b1e8a5746 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', @@ -26,6 +25,7 @@ export = { '@typescript-eslint/no-unsafe-declaration-merging': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', + '@typescript-eslint/no-uppercase-wrapper-types': 'error', '@typescript-eslint/no-var-requires': 'error', '@typescript-eslint/prefer-as-const': '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 91fd8b1589df..4be72964982a 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', @@ -57,6 +56,7 @@ export = { '@typescript-eslint/no-unsafe-return': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', + '@typescript-eslint/no-uppercase-wrapper-types': 'error', 'no-useless-constructor': 'off', '@typescript-eslint/no-useless-constructor': 'error', '@typescript-eslint/no-useless-template-literals': 'error', diff --git a/packages/eslint-plugin/src/configs/strict.ts b/packages/eslint-plugin/src/configs/strict.ts index ae000f72d3f5..65f36804765a 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', @@ -34,6 +33,7 @@ export = { '@typescript-eslint/no-unsafe-declaration-merging': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', + '@typescript-eslint/no-uppercase-wrapper-types': 'error', 'no-useless-constructor': 'off', '@typescript-eslint/no-useless-constructor': 'error', '@typescript-eslint/no-var-requires': 'error', diff --git a/packages/eslint-plugin/src/rules/ban-types.ts b/packages/eslint-plugin/src/rules/ban-types.ts index 06c9daa62d4a..20e550c13ee3 100644 --- a/packages/eslint-plugin/src/rules/ban-types.ts +++ b/packages/eslint-plugin/src/rules/ban-types.ts @@ -107,9 +107,14 @@ export default createRule({ type: 'suggestion', docs: { description: 'Disallow certain types', - recommended: 'recommended', }, fixable: 'code', + deprecated: true, + replacedBy: [ + '@typescript-eslint/no-empty-object-type', + '@typescript-eslint/no-restricted-types', + '@typescript-eslint/no-uppercase-wrapper-types', + ], hasSuggestions: true, messages: { bannedTypeMessage: "Don't use `{{name}}` as a type.{{customMessage}}", diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index c580027d059c..33cc083580a0 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -62,6 +62,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 noThrowLiteral from './no-throw-literal'; @@ -82,6 +83,7 @@ import noUnsafeReturn from './no-unsafe-return'; import noUnsafeUnaryMinus from './no-unsafe-unary-minus'; import noUnusedExpressions from './no-unused-expressions'; import noUnusedVars from './no-unused-vars'; +import noUppercaseWrapperTypes from './no-uppercase-wrapper-types'; import noUseBeforeDefine from './no-use-before-define'; import noUselessConstructor from './no-useless-constructor'; import noUselessEmptyExport from './no-useless-empty-export'; @@ -187,6 +189,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-throw-literal': noThrowLiteral, @@ -207,6 +210,7 @@ export default { 'no-unsafe-unary-minus': noUnsafeUnaryMinus, 'no-unused-expressions': noUnusedExpressions, 'no-unused-vars': noUnusedVars, + 'no-uppercase-wrapper-types': noUppercaseWrapperTypes, 'no-use-before-define': noUseBeforeDefine, 'no-useless-constructor': noUselessConstructor, 'no-useless-empty-export': noUselessEmptyExport, diff --git a/packages/eslint-plugin/src/rules/no-restricted-types.ts b/packages/eslint-plugin/src/rules/no-restricted-types.ts new file mode 100644 index 000000000000..f3d2e3b3f197 --- /dev/null +++ b/packages/eslint-plugin/src/rules/no-restricted-types.ts @@ -0,0 +1,221 @@ +import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; + +import { createRule, objectReduceKey } from '../util'; + +type Types = Record< + string, + | boolean + | string + | { + message: string; + fixWith?: string; + suggest?: readonly string[]; + } + | null +>; + +export type Options = [ + { + types?: Types; + extendDefaults?: boolean; + }, +]; + +export type MessageIds = 'bannedTypeMessage' | 'bannedTypeReplacement'; + +function removeSpaces(str: string): string { + return str.replace(/\s/g, ''); +} + +function stringifyNode( + node: TSESTree.Node, + sourceCode: TSESLint.SourceCode, +): string { + return removeSpaces(sourceCode.getText(node)); +} + +function getCustomMessage( + bannedType: string | true | { message?: string; fixWith?: string } | null, +): string { + if (!bannedType || bannedType === true) { + return ''; + } + + if (typeof bannedType === 'string') { + return ` ${bannedType}`; + } + + if (bannedType.message) { + return ` ${bannedType.message}`; + } + + return ''; +} + +const TYPE_KEYWORDS = { + bigint: AST_NODE_TYPES.TSBigIntKeyword, + boolean: AST_NODE_TYPES.TSBooleanKeyword, + never: AST_NODE_TYPES.TSNeverKeyword, + null: AST_NODE_TYPES.TSNullKeyword, + number: AST_NODE_TYPES.TSNumberKeyword, + object: AST_NODE_TYPES.TSObjectKeyword, + string: AST_NODE_TYPES.TSStringKeyword, + symbol: AST_NODE_TYPES.TSSymbolKeyword, + undefined: AST_NODE_TYPES.TSUndefinedKeyword, + unknown: AST_NODE_TYPES.TSUnknownKeyword, + void: AST_NODE_TYPES.TSVoidKeyword, +}; + +export default createRule({ + name: 'no-restricted-types', + meta: { + type: 'suggestion', + docs: { + description: 'Disallow certain types', + }, + fixable: 'code', + hasSuggestions: true, + messages: { + bannedTypeMessage: "Don't use `{{name}}` as a type.{{customMessage}}", + bannedTypeReplacement: 'Replace `{{name}}` with `{{replacement}}`.', + }, + schema: [ + { + $defs: { + banConfig: { + oneOf: [ + { + type: 'boolean', + enum: [true], + description: 'Bans the type with the default message', + }, + { + type: 'string', + description: 'Bans the type with a custom message', + }, + { + type: 'object', + description: 'Bans a type', + properties: { + message: { + type: 'string', + description: 'Custom error message', + }, + fixWith: { + type: 'string', + description: + 'Type to autofix replace with. Note that autofixers can be applied automatically - so you need to be careful with this option.', + }, + suggest: { + type: 'array', + items: { type: 'string' }, + description: 'Types to suggest replacing with.', + }, + }, + additionalProperties: false, + }, + ], + }, + }, + type: 'object', + properties: { + types: { + type: 'object', + additionalProperties: { + $ref: '#/items/0/$defs/banConfig', + }, + }, + }, + additionalProperties: false, + }, + ], + }, + defaultOptions: [{}], + create(context, [{ types = {} }]) { + const bannedTypes = new Map( + Object.entries(types).map(([type, data]) => [removeSpaces(type), data]), + ); + + function checkBannedTypes( + typeNode: TSESTree.Node, + name = stringifyNode(typeNode, context.sourceCode), + ): void { + const bannedType = bannedTypes.get(name); + + if (bannedType === undefined || bannedType === false) { + return; + } + + const customMessage = getCustomMessage(bannedType); + const fixWith = + bannedType && typeof bannedType === 'object' && bannedType.fixWith; + const suggest = + bannedType && typeof bannedType === 'object' + ? bannedType.suggest + : undefined; + + context.report({ + node: typeNode, + messageId: 'bannedTypeMessage', + data: { + name, + customMessage, + }, + fix: fixWith + ? (fixer): TSESLint.RuleFix => fixer.replaceText(typeNode, fixWith) + : null, + suggest: suggest?.map(replacement => ({ + messageId: 'bannedTypeReplacement', + data: { + name, + replacement, + }, + fix: (fixer): TSESLint.RuleFix => + fixer.replaceText(typeNode, replacement), + })), + }); + } + + const keywordSelectors = objectReduceKey( + TYPE_KEYWORDS, + (acc: TSESLint.RuleListener, keyword) => { + if (bannedTypes.has(keyword)) { + acc[TYPE_KEYWORDS[keyword]] = (node: TSESTree.Node): void => + checkBannedTypes(node, keyword); + } + + return acc; + }, + {}, + ); + + return { + ...keywordSelectors, + + TSClassImplements(node): void { + checkBannedTypes(node); + }, + TSInterfaceHeritage(node): void { + checkBannedTypes(node); + }, + TSTupleType(node): void { + if (!node.elementTypes.length) { + checkBannedTypes(node); + } + }, + TSTypeLiteral(node): void { + if (!node.members.length) { + checkBannedTypes(node); + } + }, + TSTypeReference(node): void { + checkBannedTypes(node.typeName); + + if (node.typeArguments) { + checkBannedTypes(node); + } + }, + }; + }, +}); diff --git a/packages/eslint-plugin/src/rules/no-uppercase-wrapper-types.ts b/packages/eslint-plugin/src/rules/no-uppercase-wrapper-types.ts new file mode 100644 index 000000000000..3cb6edb81afb --- /dev/null +++ b/packages/eslint-plugin/src/rules/no-uppercase-wrapper-types.ts @@ -0,0 +1,80 @@ +import type { TSESTree } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; + +import { createRule } from '../util'; + +const classNames = new Set([ + 'BigInt', + 'Boolean', + 'Function', + 'Number', + 'Object', + 'String', + 'Symbol', +]); + +export default createRule({ + name: 'no-uppercase-wrapper-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 class wrapper `{{typeName}}`.', + bannedFunctionType: [ + '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'), + }, + 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)) { + return; + } + + if (typeName === 'Function') { + context.report({ + node, + messageId: 'bannedFunctionType', + }); + return; + } + + const preferred = typeName.toLowerCase(); + + context.report({ + data: { typeName, preferred }, + fix: includeFix + ? fixer => 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/tests/docs-eslint-output-snapshots/no-uppercase-wrapper-types.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-uppercase-wrapper-types.shot new file mode 100644 index 000000000000..c6f19ffab9b3 --- /dev/null +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-uppercase-wrapper-types.shot @@ -0,0 +1,41 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Validating rule docs no-uppercase-wrapper-types.mdx code examples ESLint output 1`] = ` +"Incorrect + +let myBigInt: BigInt; + ~~~~~~ Prefer using the primitive \`bigint\` as a type name, rather than the class wrapper \`BigInt\`. +let myBoolean: Boolean; + ~~~~~~~ Prefer using the primitive \`boolean\` as a type name, rather than the class wrapper \`Boolean\`. +let myNumber: Number; + ~~~~~~ Prefer using the primitive \`number\` as a type name, rather than the class wrapper \`Number\`. +let myObject: Object; + ~~~~~~ Prefer using the primitive \`object\` as a type name, rather than the class wrapper \`Object\`. +let myString: String; + ~~~~~~ Prefer using the primitive \`string\` as a type name, rather than the class wrapper \`String\`. +let mySymbol: Symbol; + ~~~~~~ Prefer using the primitive \`symbol\` as a type name, rather than the class wrapper \`Symbol\`. + +let myFunction: Function; + ~~~~~~~~ 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. +myFunction = () => {}; +" +`; + +exports[`Validating rule docs no-uppercase-wrapper-types.mdx code examples ESLint output 2`] = ` +"Correct + +let myBigint: bigint; +let myBoolean: boolean; +let myNumber: number; +let myObject: object; +let myString: string; +let mySymbol: symbol; + +let myFunction: () => void; +myFunction = () => {}; +" +`; 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-uppercase-wrapper-types.test.ts b/packages/eslint-plugin/tests/rules/no-uppercase-wrapper-types.test.ts new file mode 100644 index 000000000000..08fae5b152c4 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/no-uppercase-wrapper-types.test.ts @@ -0,0 +1,222 @@ +import { noFormat, RuleTester } from '@typescript-eslint/rule-tester'; + +import rule from '../../src/rules/no-uppercase-wrapper-types'; + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('no-uppercase-wrapper-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 {}', + ], + 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: Function;', + output: null, + errors: [ + { + messageId: 'bannedFunctionType', + line: 1, + column: 12, + }, + ], + }, + { + 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/no-restricted-types.shot b/packages/eslint-plugin/tests/schema-snapshots/no-restricted-types.shot new file mode 100644 index 000000000000..845262dabe82 --- /dev/null +++ b/packages/eslint-plugin/tests/schema-snapshots/no-restricted-types.shot @@ -0,0 +1,85 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Rule schemas should be convertible to TS types for documentation purposes no-restricted-types 1`] = ` +" +# SCHEMA: + +[ + { + "$defs": { + "banConfig": { + "oneOf": [ + { + "description": "Bans the type with the default message", + "enum": [true], + "type": "boolean" + }, + { + "description": "Bans the type with a custom message", + "type": "string" + }, + { + "additionalProperties": false, + "description": "Bans a type", + "properties": { + "fixWith": { + "description": "Type to autofix replace with. Note that autofixers can be applied automatically - so you need to be careful with this option.", + "type": "string" + }, + "message": { + "description": "Custom error message", + "type": "string" + }, + "suggest": { + "description": "Types to suggest replacing with.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + } + ] + } + }, + "additionalProperties": false, + "properties": { + "types": { + "additionalProperties": { + "$ref": "#/items/0/$defs/banConfig" + }, + "type": "object" + } + }, + "type": "object" + } +] + + +# TYPES: + +type BanConfig = + /** Bans a type */ + | { + /** Type to autofix replace with. Note that autofixers can be applied automatically - so you need to be careful with this option. */ + fixWith?: string; + /** Custom error message */ + message?: string; + /** Types to suggest replacing with. */ + suggest?: string[]; + } + /** Bans the type with a custom message */ + | string + /** Bans the type with the default message */ + | true; + +type Options = [ + { + types?: { + [k: string]: BanConfig; + }; + }, +]; +" +`; diff --git a/packages/eslint-plugin/tests/schema-snapshots/no-uppercase-wrapper-types.shot b/packages/eslint-plugin/tests/schema-snapshots/no-uppercase-wrapper-types.shot new file mode 100644 index 000000000000..d20373c40bb8 --- /dev/null +++ b/packages/eslint-plugin/tests/schema-snapshots/no-uppercase-wrapper-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-uppercase-wrapper-types 1`] = ` +" +# SCHEMA: + +[] + + +# TYPES: + +/** No options declared */ +type Options = [];" +`; diff --git a/packages/typescript-eslint/src/configs/all.ts b/packages/typescript-eslint/src/configs/all.ts index 01d4a56f9ee1..824c3ee7ca8d 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', @@ -115,6 +115,7 @@ export default ( '@typescript-eslint/no-unused-expressions': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', + '@typescript-eslint/no-uppercase-wrapper-types': 'error', 'no-use-before-define': 'off', '@typescript-eslint/no-use-before-define': 'error', 'no-useless-constructor': 'off', diff --git a/packages/typescript-eslint/src/configs/recommended-type-checked.ts b/packages/typescript-eslint/src/configs/recommended-type-checked.ts index 2d954c705819..4905f2080088 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-base-to-string': 'error', @@ -51,6 +50,7 @@ export default ( '@typescript-eslint/no-unsafe-return': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', + '@typescript-eslint/no-uppercase-wrapper-types': 'error', '@typescript-eslint/no-var-requires': 'error', '@typescript-eslint/prefer-as-const': 'error', 'require-await': 'off', diff --git a/packages/typescript-eslint/src/configs/recommended.ts b/packages/typescript-eslint/src/configs/recommended.ts index 7df78599ea91..eca7f84dd918 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', @@ -35,6 +34,7 @@ export default ( '@typescript-eslint/no-unsafe-declaration-merging': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', + '@typescript-eslint/no-uppercase-wrapper-types': 'error', '@typescript-eslint/no-var-requires': 'error', '@typescript-eslint/prefer-as-const': '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 1542bf528504..84b6a3c74ee4 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', @@ -66,6 +65,7 @@ export default ( '@typescript-eslint/no-unsafe-return': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', + '@typescript-eslint/no-uppercase-wrapper-types': 'error', 'no-useless-constructor': 'off', '@typescript-eslint/no-useless-constructor': 'error', '@typescript-eslint/no-useless-template-literals': 'error', diff --git a/packages/typescript-eslint/src/configs/strict.ts b/packages/typescript-eslint/src/configs/strict.ts index 680813d7d64f..77427a9b7ddf 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', @@ -43,6 +42,7 @@ export default ( '@typescript-eslint/no-unsafe-declaration-merging': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', + '@typescript-eslint/no-uppercase-wrapper-types': 'error', 'no-useless-constructor': 'off', '@typescript-eslint/no-useless-constructor': 'error', '@typescript-eslint/no-var-requires': 'error', From 1791e4f9bb21ebedc3bbc73c6a0912f2a48923f5 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Wed, 15 May 2024 16:51:33 -0700 Subject: [PATCH 02/32] Fix lint issues --- .../eslint-plugin/src/rules/no-uppercase-wrapper-types.ts | 6 ++++-- .../tests/rules/no-uppercase-wrapper-types.test.ts | 3 ++- .../tests/test-utils/serializers/baseSerializer.ts | 2 +- packages/utils/src/ts-eslint/Rule.ts | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-uppercase-wrapper-types.ts b/packages/eslint-plugin/src/rules/no-uppercase-wrapper-types.ts index 3cb6edb81afb..a8154f49a936 100644 --- a/packages/eslint-plugin/src/rules/no-uppercase-wrapper-types.ts +++ b/packages/eslint-plugin/src/rules/no-uppercase-wrapper-types.ts @@ -1,14 +1,16 @@ -import type { TSESTree } from '@typescript-eslint/utils'; +import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule } from '../util'; const classNames = new Set([ 'BigInt', + // eslint-disable-next-line @typescript-eslint/internal/prefer-ast-types-enum 'Boolean', 'Function', 'Number', 'Object', + // eslint-disable-next-line @typescript-eslint/internal/prefer-ast-types-enum 'String', 'Symbol', ]); @@ -58,7 +60,7 @@ export default createRule({ context.report({ data: { typeName, preferred }, fix: includeFix - ? fixer => fixer.replaceText(node, preferred) + ? (fixer): TSESLint.RuleFix => fixer.replaceText(node, preferred) : undefined, messageId: 'bannedClassType', node, diff --git a/packages/eslint-plugin/tests/rules/no-uppercase-wrapper-types.test.ts b/packages/eslint-plugin/tests/rules/no-uppercase-wrapper-types.test.ts index 08fae5b152c4..0060bdb0cac4 100644 --- a/packages/eslint-plugin/tests/rules/no-uppercase-wrapper-types.test.ts +++ b/packages/eslint-plugin/tests/rules/no-uppercase-wrapper-types.test.ts @@ -1,4 +1,5 @@ -import { noFormat, RuleTester } from '@typescript-eslint/rule-tester'; +/* eslint-disable @typescript-eslint/internal/prefer-ast-types-enum */ +import { RuleTester } from '@typescript-eslint/rule-tester'; import rule from '../../src/rules/no-uppercase-wrapper-types'; diff --git a/packages/scope-manager/tests/test-utils/serializers/baseSerializer.ts b/packages/scope-manager/tests/test-utils/serializers/baseSerializer.ts index cb619d683a5b..8413e7179d5a 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-uppercase-wrapper-types const constructorName = (Object.getPrototypeOf(thing) as Object) .constructor.name; diff --git a/packages/utils/src/ts-eslint/Rule.ts b/packages/utils/src/ts-eslint/Rule.ts index 899b8a98346b..b2907da94165 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-uppercase-wrapper-types -- 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 From 7c606fcafadde4ff9657715cc86af5e88d59c674 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Fri, 24 May 2024 05:27:23 +0300 Subject: [PATCH 03/32] Apply suggestions from code review Co-authored-by: Kirk Waiblinger --- .../eslint-plugin/docs/rules/no-uppercase-wrapper-types.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/no-uppercase-wrapper-types.mdx b/packages/eslint-plugin/docs/rules/no-uppercase-wrapper-types.mdx index e376deb83c4a..e9a440d36eda 100644 --- a/packages/eslint-plugin/docs/rules/no-uppercase-wrapper-types.mdx +++ b/packages/eslint-plugin/docs/rules/no-uppercase-wrapper-types.mdx @@ -11,7 +11,7 @@ import TabItem from '@theme/TabItem'; The JavaScript language includes a set of primitives such as `boolean` and `number`s. JavaScript also contains 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). -TypeScript represents the primitive in lower-case types and the wrapper classes with upper-case +TypeScript represents the primitive in lower-case types and instances of the wrapper classes with upper-case. Using the primitives like `0` instead of class constructors like `Number(0)` is generally considered a JavaScript best practice. As a result, using the lower-case primitive type names like `number` instead of class names like `Number` is generally considered a TypeScript best practice. @@ -56,7 +56,7 @@ myFunction = () => {}; ## 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 this rule. +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 From 7734da7e0471affe89742c80fe7864caa1edcdbb Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Fri, 24 May 2024 05:43:11 +0300 Subject: [PATCH 04/32] Kirk suggestions --- .../docs/rules/no-uppercase-wrapper-types.mdx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/no-uppercase-wrapper-types.mdx b/packages/eslint-plugin/docs/rules/no-uppercase-wrapper-types.mdx index e9a440d36eda..2f5fe9668e6e 100644 --- a/packages/eslint-plugin/docs/rules/no-uppercase-wrapper-types.mdx +++ b/packages/eslint-plugin/docs/rules/no-uppercase-wrapper-types.mdx @@ -16,7 +16,8 @@ TypeScript represents the primitive in lower-case types and instances of the wra Using the primitives like `0` instead of class constructors like `Number(0)` is generally considered a JavaScript best practice. As a result, using the lower-case primitive type names like `number` instead of class names like `Number` is generally considered a TypeScript best practice. -Similarly, TypeScript's `Function` type allows any function-like value. +Similarly, TypeScript's `Function` type refers to objects created with the `Function` constructor, i.e. `new Function("eval'd code with implicit args")`. +`Function` allows being called with any number of arguments and returns type `any`. It's generally better to specify function parameter and return types with function type syntax. Examples of code for this rule: @@ -28,10 +29,11 @@ Examples of code for this rule: let myBigInt: BigInt; let myBoolean: Boolean; let myNumber: Number; -let myObject: Object; let myString: String; let mySymbol: Symbol; +let myObject: Object = 'allowed by TypeScript'; + let myFunction: Function; myFunction = () => {}; ``` @@ -47,6 +49,8 @@ let myObject: object; let myString: string; let mySymbol: symbol; +let myObject: object = "Type 'string' is not assignable to type 'object'."; + let myFunction: () => void; myFunction = () => {}; ``` @@ -61,6 +65,7 @@ You might consider using [ESLint disable comments](https://eslint.org/docs/lates ## 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 From d1127f5474bfcccfbe3504f473b09f9bca458f21 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Fri, 24 May 2024 06:20:27 +0300 Subject: [PATCH 05/32] yarn test -u --- .../no-uppercase-wrapper-types.shot | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-uppercase-wrapper-types.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-uppercase-wrapper-types.shot index c6f19ffab9b3..6ba6158ebd10 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-uppercase-wrapper-types.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-uppercase-wrapper-types.shot @@ -9,13 +9,14 @@ let myBoolean: Boolean; ~~~~~~~ Prefer using the primitive \`boolean\` as a type name, rather than the class wrapper \`Boolean\`. let myNumber: Number; ~~~~~~ Prefer using the primitive \`number\` as a type name, rather than the class wrapper \`Number\`. -let myObject: Object; - ~~~~~~ Prefer using the primitive \`object\` as a type name, rather than the class wrapper \`Object\`. let myString: String; ~~~~~~ Prefer using the primitive \`string\` as a type name, rather than the class wrapper \`String\`. let mySymbol: Symbol; ~~~~~~ Prefer using the primitive \`symbol\` as a type name, rather than the class wrapper \`Symbol\`. +let myObject: Object = 'allowed by TypeScript'; + ~~~~~~ Prefer using the primitive \`object\` as a type name, rather than the class wrapper \`Object\`. + let myFunction: Function; ~~~~~~~~ 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. @@ -35,6 +36,8 @@ let myObject: object; let myString: string; let mySymbol: symbol; +let myObject: object = "Type 'string' is not assignable to type 'object'."; + let myFunction: () => void; myFunction = () => {}; " From aae4be7c68c599036751ef2f2b8d90d6047266dd Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sun, 26 May 2024 14:56:40 +0200 Subject: [PATCH 06/32] test: fix tests for useProgramFromProjectService --- .../src/create-program/validateDefaultProjectForFilesGlob.ts | 2 +- .../tests/lib/useProgramFromProjectService.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/typescript-estree/src/create-program/validateDefaultProjectForFilesGlob.ts b/packages/typescript-estree/src/create-program/validateDefaultProjectForFilesGlob.ts index 863b612d9c93..9a2a4d5d6661 100644 --- a/packages/typescript-estree/src/create-program/validateDefaultProjectForFilesGlob.ts +++ b/packages/typescript-estree/src/create-program/validateDefaultProjectForFilesGlob.ts @@ -4,7 +4,7 @@ export const DEFAULT_PROJECT_FILES_ERROR_EXPLANATION = ` Having many files run with the default project is known to cause performance issues and slow down linting. -See https://typescript-eslint.io/troubleshooting/#allowDefaultProject-glob-too-wide +See https://typescript-eslint.io/troubleshooting/#allowdefaultproject-glob-too-wide `; export function validateDefaultProjectForFilesGlob( diff --git a/packages/typescript-estree/tests/lib/useProgramFromProjectService.test.ts b/packages/typescript-estree/tests/lib/useProgramFromProjectService.test.ts index 277722365422..6fa98ada5420 100644 --- a/packages/typescript-estree/tests/lib/useProgramFromProjectService.test.ts +++ b/packages/typescript-estree/tests/lib/useProgramFromProjectService.test.ts @@ -142,7 +142,7 @@ describe('useProgramFromProjectService', () => { Having many files run with the default project is known to cause performance issues and slow down linting. -See https://typescript-eslint.io/troubleshooting/#allowDefaultProject-glob-too-wide +See https://typescript-eslint.io/troubleshooting/#allowdefaultproject-glob-too-wide Matching files: - a From c411cdb7302513d4d0dba3480ccc5e0aaabc2cab Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sun, 26 May 2024 17:12:55 +0200 Subject: [PATCH 07/32] Rename to no-wrapper-object-types --- packages/eslint-plugin/docs/rules/ban-types.mdx | 4 ++-- packages/eslint-plugin/docs/rules/no-restricted-types.mdx | 2 +- ...ppercase-wrapper-types.mdx => no-wrapper-object-types.mdx} | 2 +- packages/eslint-plugin/src/configs/all.ts | 2 +- .../eslint-plugin/src/configs/recommended-type-checked.ts | 2 +- packages/eslint-plugin/src/configs/recommended.ts | 2 +- packages/eslint-plugin/src/configs/strict-type-checked.ts | 2 +- packages/eslint-plugin/src/configs/strict.ts | 2 +- packages/eslint-plugin/src/rules/ban-types.ts | 2 +- packages/eslint-plugin/src/rules/index.ts | 4 ++-- ...-uppercase-wrapper-types.ts => no-wrapper-object-types.ts} | 2 +- ...ercase-wrapper-types.shot => no-wrapper-object-types.shot} | 4 ++-- ...-wrapper-types.test.ts => no-wrapper-object-types.test.ts} | 4 ++-- ...ercase-wrapper-types.shot => no-wrapper-object-types.shot} | 2 +- .../tests/test-utils/serializers/baseSerializer.ts | 2 +- packages/typescript-eslint/src/configs/all.ts | 2 +- .../typescript-eslint/src/configs/recommended-type-checked.ts | 2 +- packages/typescript-eslint/src/configs/recommended.ts | 2 +- packages/typescript-eslint/src/configs/strict-type-checked.ts | 2 +- packages/typescript-eslint/src/configs/strict.ts | 2 +- packages/utils/src/ts-eslint/Rule.ts | 2 +- 21 files changed, 25 insertions(+), 25 deletions(-) rename packages/eslint-plugin/docs/rules/{no-uppercase-wrapper-types.mdx => no-wrapper-object-types.mdx} (96%) rename packages/eslint-plugin/src/rules/{no-uppercase-wrapper-types.ts => no-wrapper-object-types.ts} (98%) rename packages/eslint-plugin/tests/docs-eslint-output-snapshots/{no-uppercase-wrapper-types.shot => no-wrapper-object-types.shot} (89%) rename packages/eslint-plugin/tests/rules/{no-uppercase-wrapper-types.test.ts => no-wrapper-object-types.test.ts} (97%) rename packages/eslint-plugin/tests/schema-snapshots/{no-uppercase-wrapper-types.shot => no-wrapper-object-types.shot} (77%) diff --git a/packages/eslint-plugin/docs/rules/ban-types.mdx b/packages/eslint-plugin/docs/rules/ban-types.mdx index 2bd3d7e637be..490b622cc6d1 100644 --- a/packages/eslint-plugin/docs/rules/ban-types.mdx +++ b/packages/eslint-plugin/docs/rules/ban-types.mdx @@ -20,7 +20,7 @@ This rule is deprecated and now split across several rules: - [`no-empty-object-type`](./no-empty-object-type): banning the built-in `{}` type in confusing locations - [`no-restricted-types`](./no-restricted-types.mdx): allowing banning a configurable list of type names -- [`no-uppercase-wrapper-types`](./no-uppercase-wrapper-types.mdx): banning built-in class wrappers such as `Number` +- [`no-wrapper-object-types`](./no-wrapper-object-types.mdx): banning built-in class wrappers such as `Number` This deprecated rule will be removed in a future major version of typescript-eslint. ::: @@ -142,4 +142,4 @@ You might consider using [ESLint disable comments](https://eslint.org/docs/lates - [`no-empty-object-type`](./no-empty-object-type.mdx) - [`no-restricted-types`](./no-restricted-types.mdx) -- [`no-uppercase-wrapper-types`](./no-uppercase-wrapper-types.mdx) +- [`no-wrapper-object-types`](./no-wrapper-object-types.mdx) diff --git a/packages/eslint-plugin/docs/rules/no-restricted-types.mdx b/packages/eslint-plugin/docs/rules/no-restricted-types.mdx index 56814a4720de..6ad478be3bb5 100644 --- a/packages/eslint-plugin/docs/rules/no-restricted-types.mdx +++ b/packages/eslint-plugin/docs/rules/no-restricted-types.mdx @@ -68,4 +68,4 @@ If you have no need to ban specific types from being used in type annotations, y - [`ban-types`](./ban-types.mdx) - [`no-empty-object-type`](./no-empty-object-type.mdx) -- [`no-uppercase-wrapper-types`](./no-uppercase-wrapper-types.mdx) +- [`no-wrapper-object-types`](./no-wrapper-object-types.mdx) diff --git a/packages/eslint-plugin/docs/rules/no-uppercase-wrapper-types.mdx b/packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx similarity index 96% rename from packages/eslint-plugin/docs/rules/no-uppercase-wrapper-types.mdx rename to packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx index 2f5fe9668e6e..0962ab9b3aea 100644 --- a/packages/eslint-plugin/docs/rules/no-uppercase-wrapper-types.mdx +++ b/packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx @@ -7,7 +7,7 @@ import TabItem from '@theme/TabItem'; > 🛑 This file is source code, not the primary documentation location! 🛑 > -> See **https://typescript-eslint.io/rules/no-uppercase-wrapper-types** for documentation. +> See **https://typescript-eslint.io/rules/no-wrapper-object-types** for documentation. The JavaScript language includes a set of primitives such as `boolean` and `number`s. JavaScript also contains 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). diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts index a4f3c388f13e..931647fa5887 100644 --- a/packages/eslint-plugin/src/configs/all.ts +++ b/packages/eslint-plugin/src/configs/all.ts @@ -106,7 +106,7 @@ export = { '@typescript-eslint/no-unused-expressions': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', - '@typescript-eslint/no-uppercase-wrapper-types': 'error', + '@typescript-eslint/no-wrapper-object-types': 'error', 'no-use-before-define': 'off', '@typescript-eslint/no-use-before-define': 'error', 'no-useless-constructor': 'off', diff --git a/packages/eslint-plugin/src/configs/recommended-type-checked.ts b/packages/eslint-plugin/src/configs/recommended-type-checked.ts index 0c36299c0e0c..3f1ff1b615a8 100644 --- a/packages/eslint-plugin/src/configs/recommended-type-checked.ts +++ b/packages/eslint-plugin/src/configs/recommended-type-checked.ts @@ -41,7 +41,7 @@ export = { '@typescript-eslint/no-unsafe-return': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', - '@typescript-eslint/no-uppercase-wrapper-types': 'error', + '@typescript-eslint/no-wrapper-object-types': 'error', '@typescript-eslint/no-var-requires': 'error', '@typescript-eslint/prefer-as-const': 'error', 'require-await': 'off', diff --git a/packages/eslint-plugin/src/configs/recommended.ts b/packages/eslint-plugin/src/configs/recommended.ts index 7e0b1e8a5746..76c794bc8fc6 100644 --- a/packages/eslint-plugin/src/configs/recommended.ts +++ b/packages/eslint-plugin/src/configs/recommended.ts @@ -25,7 +25,7 @@ export = { '@typescript-eslint/no-unsafe-declaration-merging': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', - '@typescript-eslint/no-uppercase-wrapper-types': 'error', + '@typescript-eslint/no-wrapper-object-types': 'error', '@typescript-eslint/no-var-requires': 'error', '@typescript-eslint/prefer-as-const': '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 4be72964982a..4f7e7c9ad6a2 100644 --- a/packages/eslint-plugin/src/configs/strict-type-checked.ts +++ b/packages/eslint-plugin/src/configs/strict-type-checked.ts @@ -56,7 +56,7 @@ export = { '@typescript-eslint/no-unsafe-return': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', - '@typescript-eslint/no-uppercase-wrapper-types': 'error', + '@typescript-eslint/no-wrapper-object-types': 'error', 'no-useless-constructor': 'off', '@typescript-eslint/no-useless-constructor': 'error', '@typescript-eslint/no-useless-template-literals': 'error', diff --git a/packages/eslint-plugin/src/configs/strict.ts b/packages/eslint-plugin/src/configs/strict.ts index 65f36804765a..a1e8c1f3fc29 100644 --- a/packages/eslint-plugin/src/configs/strict.ts +++ b/packages/eslint-plugin/src/configs/strict.ts @@ -33,7 +33,7 @@ export = { '@typescript-eslint/no-unsafe-declaration-merging': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', - '@typescript-eslint/no-uppercase-wrapper-types': 'error', + '@typescript-eslint/no-wrapper-object-types': 'error', 'no-useless-constructor': 'off', '@typescript-eslint/no-useless-constructor': 'error', '@typescript-eslint/no-var-requires': 'error', diff --git a/packages/eslint-plugin/src/rules/ban-types.ts b/packages/eslint-plugin/src/rules/ban-types.ts index 20e550c13ee3..a0b2c9fbe097 100644 --- a/packages/eslint-plugin/src/rules/ban-types.ts +++ b/packages/eslint-plugin/src/rules/ban-types.ts @@ -113,7 +113,7 @@ export default createRule({ replacedBy: [ '@typescript-eslint/no-empty-object-type', '@typescript-eslint/no-restricted-types', - '@typescript-eslint/no-uppercase-wrapper-types', + '@typescript-eslint/no-wrapper-object-types', ], hasSuggestions: true, messages: { diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index 33cc083580a0..69fe5d6677ad 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -83,7 +83,7 @@ import noUnsafeReturn from './no-unsafe-return'; import noUnsafeUnaryMinus from './no-unsafe-unary-minus'; import noUnusedExpressions from './no-unused-expressions'; import noUnusedVars from './no-unused-vars'; -import noUppercaseWrapperTypes from './no-uppercase-wrapper-types'; +import noUppercaseWrapperTypes from './no-wrapper-object-types'; import noUseBeforeDefine from './no-use-before-define'; import noUselessConstructor from './no-useless-constructor'; import noUselessEmptyExport from './no-useless-empty-export'; @@ -210,7 +210,7 @@ export default { 'no-unsafe-unary-minus': noUnsafeUnaryMinus, 'no-unused-expressions': noUnusedExpressions, 'no-unused-vars': noUnusedVars, - 'no-uppercase-wrapper-types': noUppercaseWrapperTypes, + 'no-wrapper-object-types': noUppercaseWrapperTypes, 'no-use-before-define': noUseBeforeDefine, 'no-useless-constructor': noUselessConstructor, 'no-useless-empty-export': noUselessEmptyExport, diff --git a/packages/eslint-plugin/src/rules/no-uppercase-wrapper-types.ts b/packages/eslint-plugin/src/rules/no-wrapper-object-types.ts similarity index 98% rename from packages/eslint-plugin/src/rules/no-uppercase-wrapper-types.ts rename to packages/eslint-plugin/src/rules/no-wrapper-object-types.ts index a8154f49a936..c31503f3021e 100644 --- a/packages/eslint-plugin/src/rules/no-uppercase-wrapper-types.ts +++ b/packages/eslint-plugin/src/rules/no-wrapper-object-types.ts @@ -16,7 +16,7 @@ const classNames = new Set([ ]); export default createRule({ - name: 'no-uppercase-wrapper-types', + name: 'no-wrapper-object-types', meta: { type: 'problem', docs: { diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-uppercase-wrapper-types.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-wrapper-object-types.shot similarity index 89% rename from packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-uppercase-wrapper-types.shot rename to packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-wrapper-object-types.shot index 6ba6158ebd10..f71e067a55a7 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-uppercase-wrapper-types.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-wrapper-object-types.shot @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Validating rule docs no-uppercase-wrapper-types.mdx code examples ESLint output 1`] = ` +exports[`Validating rule docs no-wrapper-object-types.mdx code examples ESLint output 1`] = ` "Incorrect let myBigInt: BigInt; @@ -26,7 +26,7 @@ myFunction = () => {}; " `; -exports[`Validating rule docs no-uppercase-wrapper-types.mdx code examples ESLint output 2`] = ` +exports[`Validating rule docs no-wrapper-object-types.mdx code examples ESLint output 2`] = ` "Correct let myBigint: bigint; diff --git a/packages/eslint-plugin/tests/rules/no-uppercase-wrapper-types.test.ts b/packages/eslint-plugin/tests/rules/no-wrapper-object-types.test.ts similarity index 97% rename from packages/eslint-plugin/tests/rules/no-uppercase-wrapper-types.test.ts rename to packages/eslint-plugin/tests/rules/no-wrapper-object-types.test.ts index 0060bdb0cac4..e15117ea327d 100644 --- a/packages/eslint-plugin/tests/rules/no-uppercase-wrapper-types.test.ts +++ b/packages/eslint-plugin/tests/rules/no-wrapper-object-types.test.ts @@ -1,13 +1,13 @@ /* eslint-disable @typescript-eslint/internal/prefer-ast-types-enum */ import { RuleTester } from '@typescript-eslint/rule-tester'; -import rule from '../../src/rules/no-uppercase-wrapper-types'; +import rule from '../../src/rules/no-wrapper-object-types'; const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', }); -ruleTester.run('no-uppercase-wrapper-types', rule, { +ruleTester.run('no-wrapper-object-types', rule, { valid: [ 'let value: NumberLike;', 'let value: Other;', diff --git a/packages/eslint-plugin/tests/schema-snapshots/no-uppercase-wrapper-types.shot b/packages/eslint-plugin/tests/schema-snapshots/no-wrapper-object-types.shot similarity index 77% rename from packages/eslint-plugin/tests/schema-snapshots/no-uppercase-wrapper-types.shot rename to packages/eslint-plugin/tests/schema-snapshots/no-wrapper-object-types.shot index d20373c40bb8..247f1dd2e183 100644 --- a/packages/eslint-plugin/tests/schema-snapshots/no-uppercase-wrapper-types.shot +++ b/packages/eslint-plugin/tests/schema-snapshots/no-wrapper-object-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 no-uppercase-wrapper-types 1`] = ` +exports[`Rule schemas should be convertible to TS types for documentation purposes no-wrapper-object-types 1`] = ` " # SCHEMA: diff --git a/packages/scope-manager/tests/test-utils/serializers/baseSerializer.ts b/packages/scope-manager/tests/test-utils/serializers/baseSerializer.ts index 8413e7179d5a..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/no-uppercase-wrapper-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 824c3ee7ca8d..9ca43fa0cf80 100644 --- a/packages/typescript-eslint/src/configs/all.ts +++ b/packages/typescript-eslint/src/configs/all.ts @@ -115,7 +115,7 @@ export default ( '@typescript-eslint/no-unused-expressions': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', - '@typescript-eslint/no-uppercase-wrapper-types': 'error', + '@typescript-eslint/no-wrapper-object-types': 'error', 'no-use-before-define': 'off', '@typescript-eslint/no-use-before-define': 'error', 'no-useless-constructor': 'off', diff --git a/packages/typescript-eslint/src/configs/recommended-type-checked.ts b/packages/typescript-eslint/src/configs/recommended-type-checked.ts index 4905f2080088..c050add93904 100644 --- a/packages/typescript-eslint/src/configs/recommended-type-checked.ts +++ b/packages/typescript-eslint/src/configs/recommended-type-checked.ts @@ -50,7 +50,7 @@ export default ( '@typescript-eslint/no-unsafe-return': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', - '@typescript-eslint/no-uppercase-wrapper-types': 'error', + '@typescript-eslint/no-wrapper-object-types': 'error', '@typescript-eslint/no-var-requires': 'error', '@typescript-eslint/prefer-as-const': 'error', 'require-await': 'off', diff --git a/packages/typescript-eslint/src/configs/recommended.ts b/packages/typescript-eslint/src/configs/recommended.ts index eca7f84dd918..8fc624159d21 100644 --- a/packages/typescript-eslint/src/configs/recommended.ts +++ b/packages/typescript-eslint/src/configs/recommended.ts @@ -34,7 +34,7 @@ export default ( '@typescript-eslint/no-unsafe-declaration-merging': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', - '@typescript-eslint/no-uppercase-wrapper-types': 'error', + '@typescript-eslint/no-wrapper-object-types': 'error', '@typescript-eslint/no-var-requires': 'error', '@typescript-eslint/prefer-as-const': '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 84b6a3c74ee4..a78c9e286fe5 100644 --- a/packages/typescript-eslint/src/configs/strict-type-checked.ts +++ b/packages/typescript-eslint/src/configs/strict-type-checked.ts @@ -65,7 +65,7 @@ export default ( '@typescript-eslint/no-unsafe-return': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', - '@typescript-eslint/no-uppercase-wrapper-types': 'error', + '@typescript-eslint/no-wrapper-object-types': 'error', 'no-useless-constructor': 'off', '@typescript-eslint/no-useless-constructor': 'error', '@typescript-eslint/no-useless-template-literals': 'error', diff --git a/packages/typescript-eslint/src/configs/strict.ts b/packages/typescript-eslint/src/configs/strict.ts index 77427a9b7ddf..b8ba651ad316 100644 --- a/packages/typescript-eslint/src/configs/strict.ts +++ b/packages/typescript-eslint/src/configs/strict.ts @@ -42,7 +42,7 @@ export default ( '@typescript-eslint/no-unsafe-declaration-merging': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', - '@typescript-eslint/no-uppercase-wrapper-types': 'error', + '@typescript-eslint/no-wrapper-object-types': 'error', 'no-useless-constructor': 'off', '@typescript-eslint/no-useless-constructor': 'error', '@typescript-eslint/no-var-requires': 'error', diff --git a/packages/utils/src/ts-eslint/Rule.ts b/packages/utils/src/ts-eslint/Rule.ts index b2907da94165..802edb93b19b 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/no-uppercase-wrapper-types -- + eslint-disable-next-line @typescript-eslint/no-wrapper-object-types -- 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 From 057b34ae9bea35280d5f5f657fed14d630f4ad0e Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sun, 26 May 2024 17:13:13 +0200 Subject: [PATCH 08/32] excess myObject in docs --- packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx b/packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx index 0962ab9b3aea..19b3c9062022 100644 --- a/packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx +++ b/packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx @@ -45,7 +45,6 @@ myFunction = () => {}; let myBigint: bigint; let myBoolean: boolean; let myNumber: number; -let myObject: object; let myString: string; let mySymbol: symbol; From d4baaefa2019999e3fffaf90c9dbe6137ebddc33 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sun, 26 May 2024 17:48:53 +0200 Subject: [PATCH 09/32] Fix rules/index.ts --- packages/eslint-plugin/src/rules/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index 69fe5d6677ad..6ecde2da70ad 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -83,7 +83,7 @@ import noUnsafeReturn from './no-unsafe-return'; import noUnsafeUnaryMinus from './no-unsafe-unary-minus'; import noUnusedExpressions from './no-unused-expressions'; import noUnusedVars from './no-unused-vars'; -import noUppercaseWrapperTypes from './no-wrapper-object-types'; +import noWrapperObjectTypes from './no-wrapper-object-types'; import noUseBeforeDefine from './no-use-before-define'; import noUselessConstructor from './no-useless-constructor'; import noUselessEmptyExport from './no-useless-empty-export'; @@ -210,7 +210,7 @@ export default { 'no-unsafe-unary-minus': noUnsafeUnaryMinus, 'no-unused-expressions': noUnusedExpressions, 'no-unused-vars': noUnusedVars, - 'no-wrapper-object-types': noUppercaseWrapperTypes, + 'no-wrapper-object-types': noWrapperObjectTypes, 'no-use-before-define': noUseBeforeDefine, 'no-useless-constructor': noUselessConstructor, 'no-useless-empty-export': noUselessEmptyExport, From 88bd8357811ec3e7b6e8ff973dbed28dae3c31e6 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 27 May 2024 10:57:36 -0400 Subject: [PATCH 10/32] Clarify ban-types deprecation message --- packages/eslint-plugin/docs/rules/ban-types.mdx | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/ban-types.mdx b/packages/eslint-plugin/docs/rules/ban-types.mdx index 490b622cc6d1..d5ba81aa217e 100644 --- a/packages/eslint-plugin/docs/rules/ban-types.mdx +++ b/packages/eslint-plugin/docs/rules/ban-types.mdx @@ -16,13 +16,18 @@ This rule bans specific types and can suggest alternatives. Note that it does not ban the corresponding runtime objects from being used. :::danger Deprecated -This rule is deprecated and now split across several rules: +The old `ban-types` rule encompassed multiple areas of functionality, and so has been split into several rules. -- [`no-empty-object-type`](./no-empty-object-type): banning the built-in `{}` type in confusing locations -- [`no-restricted-types`](./no-restricted-types.mdx): allowing banning a configurable list of type names -- [`no-wrapper-object-types`](./no-wrapper-object-types.mdx): banning built-in class wrappers such as `Number` +**[`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. -This deprecated rule will be removed in a future major version of typescript-eslint. +The default options from `ban-types` are now covered by: + +- **[`no-empty-object-type`](./no-empty-object-type)**: banning the built-in `{}` type in confusing locations +- **[`no-wrapper-object-types`](./no-wrapper-object-types.mdx)**: banning built-in class wrappers such as `Number` + +`ban-types` itself is removed in typescript-eslint v8. +See [Announcing typescript-eslint v8 Beta](/announcing-typescript-eslint-v8-beta) for more details. ::: ## Examples From 874e97aa42110baec2dc2bfc635b686e6a9e87ea Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 27 May 2024 11:16:32 -0400 Subject: [PATCH 11/32] Remove ban-types altogether --- docs/maintenance/Issues.mdx | 2 +- .../eslint-plugin/TSLINT_RULE_ALTERNATIVES.md | 54 +- .../eslint-plugin/docs/rules/ban-types.md | 21 + .../eslint-plugin/docs/rules/ban-types.mdx | 150 ---- .../docs/rules/no-restricted-types.mdx | 2 +- .../docs/rules/no-wrapper-object-types.mdx | 2 +- packages/eslint-plugin/src/configs/all.ts | 2 +- .../src/configs/recommended-type-checked.ts | 2 +- .../eslint-plugin/src/configs/recommended.ts | 2 +- .../src/configs/strict-type-checked.ts | 2 +- packages/eslint-plugin/src/configs/strict.ts | 2 +- packages/eslint-plugin/src/rules/ban-types.ts | 284 ------- packages/eslint-plugin/src/rules/index.ts | 6 +- .../ban-types.shot | 50 -- .../tests/rules/ban-types.test.ts | 741 ------------------ .../tests/schema-snapshots/ban-types.shot | 103 --- packages/typescript-eslint/src/configs/all.ts | 2 +- .../src/configs/recommended-type-checked.ts | 2 +- .../src/configs/recommended.ts | 2 +- .../src/configs/strict-type-checked.ts | 2 +- .../typescript-eslint/src/configs/strict.ts | 2 +- ...7-announcing-typescript-eslint-v8-beta.mdx | 33 +- .../plugins/generated-rule-docs/index.ts | 2 - .../insertions/insertNewRuleReferences.ts | 13 +- .../insertions/insertSpecialCaseOptions.ts | 41 - 25 files changed, 95 insertions(+), 1429 deletions(-) create mode 100644 packages/eslint-plugin/docs/rules/ban-types.md delete mode 100644 packages/eslint-plugin/docs/rules/ban-types.mdx delete mode 100644 packages/eslint-plugin/src/rules/ban-types.ts delete mode 100644 packages/eslint-plugin/tests/docs-eslint-output-snapshots/ban-types.shot delete mode 100644 packages/eslint-plugin/tests/rules/ban-types.test.ts delete mode 100644 packages/eslint-plugin/tests/schema-snapshots/ban-types.shot delete mode 100644 packages/website/plugins/generated-rule-docs/insertions/insertSpecialCaseOptions.ts 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 d6dd55f8b9e3..be9259fd9ad2 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..0d2b16627159 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/ban-types.md @@ -0,0 +1,21 @@ +:::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. + +The default options from `ban-types` are now covered by: + +- **[`no-empty-object-type`](./no-empty-object-type)**: banning the built-in `{}` type in confusing locations +- **[`no-wrapper-object-types`](./no-wrapper-object-types.mdx)**: banning built-in class wrappers such as `Number` + +`ban-types` itself is removed in typescript-eslint v8. +See [Announcing typescript-eslint v8 Beta](/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 d5ba81aa217e..000000000000 --- a/packages/eslint-plugin/docs/rules/ban-types.mdx +++ /dev/null @@ -1,150 +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. - -:::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. - -The default options from `ban-types` are now covered by: - -- **[`no-empty-object-type`](./no-empty-object-type)**: banning the built-in `{}` type in confusing locations -- **[`no-wrapper-object-types`](./no-wrapper-object-types.mdx)**: banning built-in class wrappers such as `Number` - -`ban-types` itself is removed in typescript-eslint v8. -See [Announcing typescript-eslint v8 Beta](/announcing-typescript-eslint-v8-beta) for more details. -::: - -## 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) -- [`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-restricted-types.mdx b/packages/eslint-plugin/docs/rules/no-restricted-types.mdx index 6ad478be3bb5..a0a73fd600fc 100644 --- a/packages/eslint-plugin/docs/rules/no-restricted-types.mdx +++ b/packages/eslint-plugin/docs/rules/no-restricted-types.mdx @@ -66,6 +66,6 @@ If you have no need to ban specific types from being used in type annotations, y ## Related To -- [`ban-types`](./ban-types.mdx) +- [`ban-types`](./ban-types.md) - [`no-empty-object-type`](./no-empty-object-type.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 index 19b3c9062022..b2a0e7556d41 100644 --- a/packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx +++ b/packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx @@ -69,6 +69,6 @@ You might consider using [ESLint disable comments](https://eslint.org/docs/lates ## Related To -- [`ban-types`](./ban-types.mdx) +- [`ban-types`](./ban-types.md) - [`no-empty-object-type`](./no-empty-object-type.mdx) - [`no-restricted-types`](./no-restricted-types.mdx) diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts index 931647fa5887..c7fde8c4dbc9 100644 --- a/packages/eslint-plugin/src/configs/all.ts +++ b/packages/eslint-plugin/src/configs/all.ts @@ -106,7 +106,6 @@ 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-use-before-define': 'off', '@typescript-eslint/no-use-before-define': 'error', 'no-useless-constructor': 'off', @@ -114,6 +113,7 @@ export = { '@typescript-eslint/no-useless-empty-export': 'error', '@typescript-eslint/no-useless-template-literals': 'error', '@typescript-eslint/no-var-requires': '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 3f1ff1b615a8..3fc43879e6d5 100644 --- a/packages/eslint-plugin/src/configs/recommended-type-checked.ts +++ b/packages/eslint-plugin/src/configs/recommended-type-checked.ts @@ -41,8 +41,8 @@ export = { '@typescript-eslint/no-unsafe-return': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', - '@typescript-eslint/no-wrapper-object-types': 'error', '@typescript-eslint/no-var-requires': 'error', + '@typescript-eslint/no-wrapper-object-types': 'error', '@typescript-eslint/prefer-as-const': 'error', 'require-await': 'off', '@typescript-eslint/require-await': 'error', diff --git a/packages/eslint-plugin/src/configs/recommended.ts b/packages/eslint-plugin/src/configs/recommended.ts index 76c794bc8fc6..d01b29447953 100644 --- a/packages/eslint-plugin/src/configs/recommended.ts +++ b/packages/eslint-plugin/src/configs/recommended.ts @@ -25,8 +25,8 @@ export = { '@typescript-eslint/no-unsafe-declaration-merging': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', - '@typescript-eslint/no-wrapper-object-types': 'error', '@typescript-eslint/no-var-requires': 'error', + '@typescript-eslint/no-wrapper-object-types': 'error', '@typescript-eslint/prefer-as-const': '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 4f7e7c9ad6a2..0cbc69249532 100644 --- a/packages/eslint-plugin/src/configs/strict-type-checked.ts +++ b/packages/eslint-plugin/src/configs/strict-type-checked.ts @@ -56,11 +56,11 @@ export = { '@typescript-eslint/no-unsafe-return': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', - '@typescript-eslint/no-wrapper-object-types': 'error', 'no-useless-constructor': 'off', '@typescript-eslint/no-useless-constructor': 'error', '@typescript-eslint/no-useless-template-literals': 'error', '@typescript-eslint/no-var-requires': '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 a1e8c1f3fc29..030c348aea40 100644 --- a/packages/eslint-plugin/src/configs/strict.ts +++ b/packages/eslint-plugin/src/configs/strict.ts @@ -33,10 +33,10 @@ export = { '@typescript-eslint/no-unsafe-declaration-merging': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', - '@typescript-eslint/no-wrapper-object-types': 'error', 'no-useless-constructor': 'off', '@typescript-eslint/no-useless-constructor': 'error', '@typescript-eslint/no-var-requires': 'error', + '@typescript-eslint/no-wrapper-object-types': 'error', '@typescript-eslint/prefer-as-const': 'error', '@typescript-eslint/prefer-literal-enum-member': 'error', '@typescript-eslint/prefer-ts-expect-error': 'error', diff --git a/packages/eslint-plugin/src/rules/ban-types.ts b/packages/eslint-plugin/src/rules/ban-types.ts deleted file mode 100644 index a0b2c9fbe097..000000000000 --- a/packages/eslint-plugin/src/rules/ban-types.ts +++ /dev/null @@ -1,284 +0,0 @@ -import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; - -import { createRule, objectReduceKey } from '../util'; - -type Types = Record< - string, - | boolean - | string - | { - message: string; - fixWith?: string; - suggest?: readonly string[]; - } - | null ->; - -export type Options = [ - { - types?: Types; - extendDefaults?: boolean; - }, -]; -export type MessageIds = 'bannedTypeMessage' | 'bannedTypeReplacement'; - -function removeSpaces(str: string): string { - return str.replace(/\s/g, ''); -} - -function stringifyNode( - node: TSESTree.Node, - sourceCode: TSESLint.SourceCode, -): string { - return removeSpaces(sourceCode.getText(node)); -} - -function getCustomMessage( - bannedType: string | true | { message?: string; fixWith?: string } | null, -): string { - if (bannedType == null || bannedType === true) { - return ''; - } - - if (typeof bannedType === 'string') { - return ` ${bannedType}`; - } - - if (bannedType.message) { - return ` ${bannedType.message}`; - } - - 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 = { - bigint: AST_NODE_TYPES.TSBigIntKeyword, - boolean: AST_NODE_TYPES.TSBooleanKeyword, - never: AST_NODE_TYPES.TSNeverKeyword, - null: AST_NODE_TYPES.TSNullKeyword, - number: AST_NODE_TYPES.TSNumberKeyword, - object: AST_NODE_TYPES.TSObjectKeyword, - string: AST_NODE_TYPES.TSStringKeyword, - symbol: AST_NODE_TYPES.TSSymbolKeyword, - undefined: AST_NODE_TYPES.TSUndefinedKeyword, - unknown: AST_NODE_TYPES.TSUnknownKeyword, - void: AST_NODE_TYPES.TSVoidKeyword, -}; - -export default createRule({ - name: 'ban-types', - meta: { - type: 'suggestion', - docs: { - description: 'Disallow certain types', - }, - fixable: 'code', - deprecated: true, - replacedBy: [ - '@typescript-eslint/no-empty-object-type', - '@typescript-eslint/no-restricted-types', - '@typescript-eslint/no-wrapper-object-types', - ], - hasSuggestions: true, - messages: { - bannedTypeMessage: "Don't use `{{name}}` as a type.{{customMessage}}", - bannedTypeReplacement: 'Replace `{{name}}` with `{{replacement}}`.', - }, - schema: [ - { - $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], - description: 'Bans the type with the default message', - }, - { - type: 'string', - description: 'Bans the type with a custom message', - }, - { - type: 'object', - description: 'Bans a type', - properties: { - message: { - type: 'string', - description: 'Custom error message', - }, - fixWith: { - type: 'string', - description: - 'Type to autofix replace with. Note that autofixers can be applied automatically - so you need to be careful with this option.', - }, - suggest: { - type: 'array', - items: { type: 'string' }, - description: 'Types to suggest replacing with.', - additionalItems: false, - }, - }, - additionalProperties: false, - }, - ], - }, - }, - type: 'object', - properties: { - types: { - type: 'object', - additionalProperties: { - $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, - ); - const bannedTypes = new Map( - Object.entries(types).map(([type, data]) => [removeSpaces(type), data]), - ); - - function checkBannedTypes( - typeNode: TSESTree.Node, - name = stringifyNode(typeNode, context.sourceCode), - ): void { - const bannedType = bannedTypes.get(name); - - if (bannedType === undefined || bannedType === false) { - return; - } - - const customMessage = getCustomMessage(bannedType); - const fixWith = - bannedType && typeof bannedType === 'object' && bannedType.fixWith; - const suggest = - bannedType && typeof bannedType === 'object' - ? bannedType.suggest - : undefined; - - context.report({ - node: typeNode, - messageId: 'bannedTypeMessage', - data: { - name, - customMessage, - }, - fix: fixWith - ? (fixer): TSESLint.RuleFix => fixer.replaceText(typeNode, fixWith) - : null, - suggest: suggest?.map(replacement => ({ - messageId: 'bannedTypeReplacement', - data: { - name, - replacement, - }, - fix: (fixer): TSESLint.RuleFix => - fixer.replaceText(typeNode, replacement), - })), - }); - } - - const keywordSelectors = objectReduceKey( - TYPE_KEYWORDS, - (acc: TSESLint.RuleListener, keyword) => { - if (bannedTypes.has(keyword)) { - acc[TYPE_KEYWORDS[keyword]] = (node: TSESTree.Node): void => - checkBannedTypes(node, keyword); - } - - return acc; - }, - {}, - ); - - return { - ...keywordSelectors, - - TSTypeLiteral(node): void { - if (node.members.length) { - return; - } - - checkBannedTypes(node); - }, - TSTupleType(node): void { - if (node.elementTypes.length === 0) { - checkBannedTypes(node); - } - }, - TSTypeReference(node): void { - checkBannedTypes(node.typeName); - - if (node.typeArguments) { - checkBannedTypes(node); - } - }, - TSInterfaceHeritage(node): void { - checkBannedTypes(node); - }, - TSClassImplements(node): void { - checkBannedTypes(node); - }, - }; - }, -}); diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index 6ecde2da70ad..3ef064eddee5 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'; @@ -83,12 +82,12 @@ import noUnsafeReturn from './no-unsafe-return'; import noUnsafeUnaryMinus from './no-unsafe-unary-minus'; import noUnusedExpressions from './no-unused-expressions'; import noUnusedVars from './no-unused-vars'; -import noWrapperObjectTypes from './no-wrapper-object-types'; import noUseBeforeDefine from './no-use-before-define'; import noUselessConstructor from './no-useless-constructor'; import noUselessEmptyExport from './no-useless-empty-export'; import noUselessTemplateLiterals from './no-useless-template-literals'; 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'; @@ -132,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, @@ -210,12 +208,12 @@ export default { 'no-unsafe-unary-minus': noUnsafeUnaryMinus, 'no-unused-expressions': noUnusedExpressions, 'no-unused-vars': noUnusedVars, - 'no-wrapper-object-types': noWrapperObjectTypes, 'no-use-before-define': noUseBeforeDefine, 'no-useless-constructor': noUselessConstructor, 'no-useless-empty-export': noUselessEmptyExport, 'no-useless-template-literals': noUselessTemplateLiterals, '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/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/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/schema-snapshots/ban-types.shot b/packages/eslint-plugin/tests/schema-snapshots/ban-types.shot deleted file mode 100644 index a7fe2d555cda..000000000000 --- a/packages/eslint-plugin/tests/schema-snapshots/ban-types.shot +++ /dev/null @@ -1,103 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Rule schemas should be convertible to TS types for documentation purposes ban-types 1`] = ` -" -# SCHEMA: - -[ - { - "$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], - "type": "boolean" - }, - { - "description": "Bans the type with a custom message", - "type": "string" - }, - { - "additionalProperties": false, - "description": "Bans a type", - "properties": { - "fixWith": { - "description": "Type to autofix replace with. Note that autofixers can be applied automatically - so you need to be careful with this option.", - "type": "string" - }, - "message": { - "description": "Custom error message", - "type": "string" - }, - "suggest": { - "additionalItems": false, - "description": "Types to suggest replacing with.", - "items": { - "type": "string" - }, - "type": "array" - } - }, - "type": "object" - } - ] - } - }, - "additionalProperties": false, - "properties": { - "extendDefaults": { - "type": "boolean" - }, - "types": { - "additionalProperties": { - "$ref": "#/items/0/$defs/banConfig" - }, - "type": "object" - } - }, - "type": "object" - } -] - - -# TYPES: - -type BanConfig = - /** Bans a type */ - | { - /** Type to autofix replace with. Note that autofixers can be applied automatically - so you need to be careful with this option. */ - fixWith?: string; - /** Custom error message */ - message?: string; - /** Types to suggest replacing with. */ - suggest?: string[]; - } - /** 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; - -type Options = [ - { - extendDefaults?: boolean; - types?: { - [k: string]: BanConfig; - }; - }, -]; -" -`; diff --git a/packages/typescript-eslint/src/configs/all.ts b/packages/typescript-eslint/src/configs/all.ts index 9ca43fa0cf80..c13027b4ba3d 100644 --- a/packages/typescript-eslint/src/configs/all.ts +++ b/packages/typescript-eslint/src/configs/all.ts @@ -115,7 +115,6 @@ 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-use-before-define': 'off', '@typescript-eslint/no-use-before-define': 'error', 'no-useless-constructor': 'off', @@ -123,6 +122,7 @@ export default ( '@typescript-eslint/no-useless-empty-export': 'error', '@typescript-eslint/no-useless-template-literals': 'error', '@typescript-eslint/no-var-requires': '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 c050add93904..8d9fd51c57ac 100644 --- a/packages/typescript-eslint/src/configs/recommended-type-checked.ts +++ b/packages/typescript-eslint/src/configs/recommended-type-checked.ts @@ -50,8 +50,8 @@ export default ( '@typescript-eslint/no-unsafe-return': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', - '@typescript-eslint/no-wrapper-object-types': 'error', '@typescript-eslint/no-var-requires': 'error', + '@typescript-eslint/no-wrapper-object-types': 'error', '@typescript-eslint/prefer-as-const': 'error', 'require-await': 'off', '@typescript-eslint/require-await': 'error', diff --git a/packages/typescript-eslint/src/configs/recommended.ts b/packages/typescript-eslint/src/configs/recommended.ts index 8fc624159d21..87b2e5e27b5b 100644 --- a/packages/typescript-eslint/src/configs/recommended.ts +++ b/packages/typescript-eslint/src/configs/recommended.ts @@ -34,8 +34,8 @@ export default ( '@typescript-eslint/no-unsafe-declaration-merging': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', - '@typescript-eslint/no-wrapper-object-types': 'error', '@typescript-eslint/no-var-requires': 'error', + '@typescript-eslint/no-wrapper-object-types': 'error', '@typescript-eslint/prefer-as-const': '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 a78c9e286fe5..52bec0e39cf3 100644 --- a/packages/typescript-eslint/src/configs/strict-type-checked.ts +++ b/packages/typescript-eslint/src/configs/strict-type-checked.ts @@ -65,11 +65,11 @@ export default ( '@typescript-eslint/no-unsafe-return': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', - '@typescript-eslint/no-wrapper-object-types': 'error', 'no-useless-constructor': 'off', '@typescript-eslint/no-useless-constructor': 'error', '@typescript-eslint/no-useless-template-literals': 'error', '@typescript-eslint/no-var-requires': '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 b8ba651ad316..cd2e72592aa1 100644 --- a/packages/typescript-eslint/src/configs/strict.ts +++ b/packages/typescript-eslint/src/configs/strict.ts @@ -42,10 +42,10 @@ export default ( '@typescript-eslint/no-unsafe-declaration-merging': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', - '@typescript-eslint/no-wrapper-object-types': 'error', 'no-useless-constructor': 'off', '@typescript-eslint/no-useless-constructor': 'error', '@typescript-eslint/no-var-requires': 'error', + '@typescript-eslint/no-wrapper-object-types': 'error', '@typescript-eslint/prefer-as-const': 'error', '@typescript-eslint/prefer-literal-enum-member': 'error', '@typescript-eslint/prefer-ts-expect-error': 'error', 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 385f13fec573..f71f3a12883d 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,9 +187,38 @@ 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`](/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_ object, 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: + +- [`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. +- [`no-empty-object-type`](/rules/no-empty-object-type) bans the built-in `{}` type in confusing locations. +- [`no-wrapper-object-types`](/rules/no-wrapper-object-types) bans built-in class wrappers such as `Number`. + +If you were manually configuring `ban-types` options: + +- If you were banning any configurable types lists, provide a similar configuration to [`no-restricted-types`](/rules/no-restricted-types). +- If you were disabling the ban on `{}`, consider enabling `no-empty-object-type`, as it allows some cases of `{}` that were previously banned. + +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) +- [feat(eslint-plugin): replace ban-types with no-restricted-types, no-wrapper-object-types rules](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 e1920cb501df..ee3878c8951e 100644 --- a/packages/website/plugins/generated-rule-docs/index.ts +++ b/packages/website/plugins/generated-rule-docs/index.ts @@ -8,7 +8,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'; import { @@ -38,7 +37,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 41924617a22b..af4e5bea00b4 100644 --- a/packages/website/plugins/generated-rule-docs/insertions/insertNewRuleReferences.ts +++ b/packages/website/plugins/generated-rule-docs/insertions/insertNewRuleReferences.ts @@ -19,14 +19,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, '..', @@ -189,10 +181,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 a5fb087e8776..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 type { RuleDocsPage } from '../RuleDocsPage'; -import { eslintPluginDirectory } from '../utils'; - -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 = fs - .readFileSync( - path.join(eslintPluginDirectory, 'src/rules/ban-types.ts'), - 'utf8', - ) - .match(/^const defaultTypes.+?^\};$/msu)?.[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); -} From 1a854f16efd72cc3b85b3cf0476728dc782dcf4d Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 27 May 2024 11:26:59 -0400 Subject: [PATCH 12/32] fix website build with permalink to ban-types --- .../blog/2024-05-27-announcing-typescript-eslint-v8-beta.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f71f3a12883d..8f992e65c86e 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 @@ -190,7 +190,7 @@ Several rules are changed in significant enough ways to be considered breaking c #### Replacement of `ban-types` -[`@typescript-eslint/ban-types`](/rules/ban-types) has long been one of the more controversial rules in typescript-eslint. +[`@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 From 2bfa14bd74100d6ce305c5eb2c7e95eaf80a3054 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 27 May 2024 13:18:40 -0400 Subject: [PATCH 13/32] Update configs --- packages/eslint-plugin/src/configs/all.ts | 3 ++- .../src/configs/recommended-type-checked-only.ts | 7 +++++++ .../src/configs/recommended-type-checked.ts | 12 +++++++++++- packages/eslint-plugin/src/configs/recommended.ts | 5 ++++- .../src/configs/strict-type-checked-only.ts | 2 +- .../eslint-plugin/src/configs/strict-type-checked.ts | 7 +++++-- packages/eslint-plugin/src/configs/strict.ts | 5 ++++- .../src/configs/stylistic-type-checked-only.ts | 3 +++ .../src/configs/stylistic-type-checked.ts | 4 +++- packages/eslint-plugin/src/configs/stylistic.ts | 1 - packages/typescript-eslint/src/configs/all.ts | 3 ++- .../src/configs/recommended-type-checked-only.ts | 7 +++++++ .../src/configs/recommended-type-checked.ts | 12 +++++++++++- .../typescript-eslint/src/configs/recommended.ts | 5 ++++- .../src/configs/strict-type-checked-only.ts | 2 +- .../src/configs/strict-type-checked.ts | 7 +++++-- packages/typescript-eslint/src/configs/strict.ts | 5 ++++- .../src/configs/stylistic-type-checked-only.ts | 3 +++ .../src/configs/stylistic-type-checked.ts | 4 +++- packages/typescript-eslint/src/configs/stylistic.ts | 1 - 20 files changed, 80 insertions(+), 18 deletions(-) diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts index 0ac9c3653d97..c7fde8c4dbc9 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', @@ -113,6 +113,7 @@ export = { '@typescript-eslint/no-useless-empty-export': 'error', '@typescript-eslint/no-useless-template-literals': 'error', '@typescript-eslint/no-var-requires': '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-only.ts b/packages/eslint-plugin/src/configs/recommended-type-checked-only.ts index 13a1f5fb4271..237ae21553ff 100644 --- a/packages/eslint-plugin/src/configs/recommended-type-checked-only.ts +++ b/packages/eslint-plugin/src/configs/recommended-type-checked-only.ts @@ -11,6 +11,7 @@ export = { extends: ['./configs/base', './configs/eslint-recommended'], rules: { '@typescript-eslint/await-thenable': 'error', + '@typescript-eslint/no-array-delete': 'error', '@typescript-eslint/no-base-to-string': 'error', '@typescript-eslint/no-duplicate-type-constituents': 'error', '@typescript-eslint/no-floating-promises': 'error', @@ -26,6 +27,12 @@ export = { '@typescript-eslint/no-unsafe-enum-comparison': 'error', '@typescript-eslint/no-unsafe-member-access': 'error', '@typescript-eslint/no-unsafe-return': 'error', + '@typescript-eslint/no-unsafe-unary-minus': 'error', + '@typescript-eslint/no-useless-template-literals': 'error', + 'no-throw-literal': 'off', + '@typescript-eslint/only-throw-error': 'error', + 'prefer-promise-reject-errors': 'off', + '@typescript-eslint/prefer-promise-reject-errors': 'error', 'require-await': 'off', '@typescript-eslint/require-await': 'error', '@typescript-eslint/restrict-plus-operands': 'error', diff --git a/packages/eslint-plugin/src/configs/recommended-type-checked.ts b/packages/eslint-plugin/src/configs/recommended-type-checked.ts index 858eb6555090..6c38a5068ff0 100644 --- a/packages/eslint-plugin/src/configs/recommended-type-checked.ts +++ b/packages/eslint-plugin/src/configs/recommended-type-checked.ts @@ -12,9 +12,9 @@ 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', '@typescript-eslint/no-base-to-string': 'error', '@typescript-eslint/no-duplicate-enum-values': 'error', '@typescript-eslint/no-duplicate-type-constituents': 'error', @@ -40,10 +40,20 @@ export = { '@typescript-eslint/no-unsafe-enum-comparison': 'error', '@typescript-eslint/no-unsafe-member-access': 'error', '@typescript-eslint/no-unsafe-return': 'error', + '@typescript-eslint/no-unsafe-unary-minus': 'error', + 'no-unused-expressions': 'off', + '@typescript-eslint/no-unused-expressions': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', + '@typescript-eslint/no-useless-template-literals': 'error', '@typescript-eslint/no-var-requires': 'error', + '@typescript-eslint/no-wrapper-object-types': 'error', + 'no-throw-literal': 'off', + '@typescript-eslint/only-throw-error': 'error', '@typescript-eslint/prefer-as-const': 'error', + '@typescript-eslint/prefer-namespace-keyword': 'error', + 'prefer-promise-reject-errors': 'off', + '@typescript-eslint/prefer-promise-reject-errors': 'error', 'require-await': 'off', '@typescript-eslint/require-await': 'error', '@typescript-eslint/restrict-plus-operands': 'error', diff --git a/packages/eslint-plugin/src/configs/recommended.ts b/packages/eslint-plugin/src/configs/recommended.ts index c93e38eabb2f..b11045f53e8e 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', @@ -24,10 +23,14 @@ export = { '@typescript-eslint/no-this-alias': 'error', '@typescript-eslint/no-unnecessary-type-constraint': 'error', '@typescript-eslint/no-unsafe-declaration-merging': 'error', + 'no-unused-expressions': 'off', + '@typescript-eslint/no-unused-expressions': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', '@typescript-eslint/no-var-requires': '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', }, } satisfies ClassicConfig.Config; diff --git a/packages/eslint-plugin/src/configs/strict-type-checked-only.ts b/packages/eslint-plugin/src/configs/strict-type-checked-only.ts index 12709933dfb7..c9afa90bd5d7 100644 --- a/packages/eslint-plugin/src/configs/strict-type-checked-only.ts +++ b/packages/eslint-plugin/src/configs/strict-type-checked-only.ts @@ -33,10 +33,10 @@ export = { '@typescript-eslint/no-unsafe-enum-comparison': 'error', '@typescript-eslint/no-unsafe-member-access': 'error', '@typescript-eslint/no-unsafe-return': 'error', + '@typescript-eslint/no-unsafe-unary-minus': 'error', '@typescript-eslint/no-useless-template-literals': 'error', 'no-throw-literal': 'off', '@typescript-eslint/only-throw-error': 'error', - '@typescript-eslint/prefer-includes': 'error', 'prefer-promise-reject-errors': 'off', '@typescript-eslint/prefer-promise-reject-errors': 'error', '@typescript-eslint/prefer-reduce-type-parameter': 'error', diff --git a/packages/eslint-plugin/src/configs/strict-type-checked.ts b/packages/eslint-plugin/src/configs/strict-type-checked.ts index 91fd8b1589df..f1136db6ee09 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,17 +54,21 @@ export = { '@typescript-eslint/no-unsafe-enum-comparison': 'error', '@typescript-eslint/no-unsafe-member-access': 'error', '@typescript-eslint/no-unsafe-return': 'error', + '@typescript-eslint/no-unsafe-unary-minus': '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-useless-template-literals': 'error', '@typescript-eslint/no-var-requires': 'error', + '@typescript-eslint/no-wrapper-object-types': 'error', 'no-throw-literal': 'off', '@typescript-eslint/only-throw-error': 'error', '@typescript-eslint/prefer-as-const': 'error', - '@typescript-eslint/prefer-includes': 'error', '@typescript-eslint/prefer-literal-enum-member': 'error', + '@typescript-eslint/prefer-namespace-keyword': 'error', 'prefer-promise-reject-errors': 'off', '@typescript-eslint/prefer-promise-reject-errors': 'error', '@typescript-eslint/prefer-reduce-type-parameter': 'error', diff --git a/packages/eslint-plugin/src/configs/strict.ts b/packages/eslint-plugin/src/configs/strict.ts index ae000f72d3f5..112dc77dd2e6 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', @@ -32,13 +31,17 @@ export = { '@typescript-eslint/no-this-alias': 'error', '@typescript-eslint/no-unnecessary-type-constraint': 'error', '@typescript-eslint/no-unsafe-declaration-merging': '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-var-requires': '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', '@typescript-eslint/prefer-ts-expect-error': 'error', '@typescript-eslint/triple-slash-reference': 'error', '@typescript-eslint/unified-signatures': 'error', diff --git a/packages/eslint-plugin/src/configs/stylistic-type-checked-only.ts b/packages/eslint-plugin/src/configs/stylistic-type-checked-only.ts index d9026c1db57e..03428e419be5 100644 --- a/packages/eslint-plugin/src/configs/stylistic-type-checked-only.ts +++ b/packages/eslint-plugin/src/configs/stylistic-type-checked-only.ts @@ -13,8 +13,11 @@ export = { 'dot-notation': 'off', '@typescript-eslint/dot-notation': 'error', '@typescript-eslint/non-nullable-type-assertion-style': 'error', + '@typescript-eslint/prefer-find': 'error', + '@typescript-eslint/prefer-includes': 'error', '@typescript-eslint/prefer-nullish-coalescing': 'error', '@typescript-eslint/prefer-optional-chain': 'error', + '@typescript-eslint/prefer-regexp-exec': 'error', '@typescript-eslint/prefer-string-starts-ends-with': 'error', }, } satisfies ClassicConfig.Config; diff --git a/packages/eslint-plugin/src/configs/stylistic-type-checked.ts b/packages/eslint-plugin/src/configs/stylistic-type-checked.ts index 3766c7f5695c..f2512877aebe 100644 --- a/packages/eslint-plugin/src/configs/stylistic-type-checked.ts +++ b/packages/eslint-plugin/src/configs/stylistic-type-checked.ts @@ -25,11 +25,13 @@ export = { '@typescript-eslint/no-empty-function': 'error', '@typescript-eslint/no-inferrable-types': 'error', '@typescript-eslint/non-nullable-type-assertion-style': 'error', + '@typescript-eslint/prefer-find': 'error', '@typescript-eslint/prefer-for-of': 'error', '@typescript-eslint/prefer-function-type': 'error', - '@typescript-eslint/prefer-namespace-keyword': 'error', + '@typescript-eslint/prefer-includes': 'error', '@typescript-eslint/prefer-nullish-coalescing': 'error', '@typescript-eslint/prefer-optional-chain': 'error', + '@typescript-eslint/prefer-regexp-exec': 'error', '@typescript-eslint/prefer-string-starts-ends-with': 'error', }, } satisfies ClassicConfig.Config; diff --git a/packages/eslint-plugin/src/configs/stylistic.ts b/packages/eslint-plugin/src/configs/stylistic.ts index d9ac6faf9d9c..02705c56c034 100644 --- a/packages/eslint-plugin/src/configs/stylistic.ts +++ b/packages/eslint-plugin/src/configs/stylistic.ts @@ -24,6 +24,5 @@ export = { '@typescript-eslint/no-inferrable-types': 'error', '@typescript-eslint/prefer-for-of': 'error', '@typescript-eslint/prefer-function-type': 'error', - '@typescript-eslint/prefer-namespace-keyword': 'error', }, } satisfies ClassicConfig.Config; diff --git a/packages/typescript-eslint/src/configs/all.ts b/packages/typescript-eslint/src/configs/all.ts index 01d4a56f9ee1..c13027b4ba3d 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', @@ -122,6 +122,7 @@ export default ( '@typescript-eslint/no-useless-empty-export': 'error', '@typescript-eslint/no-useless-template-literals': 'error', '@typescript-eslint/no-var-requires': '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-only.ts b/packages/typescript-eslint/src/configs/recommended-type-checked-only.ts index 1d7e8fc5b669..c152d7b1c7e5 100644 --- a/packages/typescript-eslint/src/configs/recommended-type-checked-only.ts +++ b/packages/typescript-eslint/src/configs/recommended-type-checked-only.ts @@ -20,6 +20,7 @@ export default ( name: 'typescript-eslint/recommended-type-checked-only', rules: { '@typescript-eslint/await-thenable': 'error', + '@typescript-eslint/no-array-delete': 'error', '@typescript-eslint/no-base-to-string': 'error', '@typescript-eslint/no-duplicate-type-constituents': 'error', '@typescript-eslint/no-floating-promises': 'error', @@ -35,6 +36,12 @@ export default ( '@typescript-eslint/no-unsafe-enum-comparison': 'error', '@typescript-eslint/no-unsafe-member-access': 'error', '@typescript-eslint/no-unsafe-return': 'error', + '@typescript-eslint/no-unsafe-unary-minus': 'error', + '@typescript-eslint/no-useless-template-literals': 'error', + 'no-throw-literal': 'off', + '@typescript-eslint/only-throw-error': 'error', + 'prefer-promise-reject-errors': 'off', + '@typescript-eslint/prefer-promise-reject-errors': 'error', 'require-await': 'off', '@typescript-eslint/require-await': 'error', '@typescript-eslint/restrict-plus-operands': 'error', diff --git a/packages/typescript-eslint/src/configs/recommended-type-checked.ts b/packages/typescript-eslint/src/configs/recommended-type-checked.ts index 2d954c705819..1efe108bbf7b 100644 --- a/packages/typescript-eslint/src/configs/recommended-type-checked.ts +++ b/packages/typescript-eslint/src/configs/recommended-type-checked.ts @@ -21,9 +21,9 @@ 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', '@typescript-eslint/no-base-to-string': 'error', '@typescript-eslint/no-duplicate-enum-values': 'error', '@typescript-eslint/no-duplicate-type-constituents': 'error', @@ -49,10 +49,20 @@ export default ( '@typescript-eslint/no-unsafe-enum-comparison': 'error', '@typescript-eslint/no-unsafe-member-access': 'error', '@typescript-eslint/no-unsafe-return': 'error', + '@typescript-eslint/no-unsafe-unary-minus': 'error', + 'no-unused-expressions': 'off', + '@typescript-eslint/no-unused-expressions': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', + '@typescript-eslint/no-useless-template-literals': 'error', '@typescript-eslint/no-var-requires': 'error', + '@typescript-eslint/no-wrapper-object-types': 'error', + 'no-throw-literal': 'off', + '@typescript-eslint/only-throw-error': 'error', '@typescript-eslint/prefer-as-const': 'error', + '@typescript-eslint/prefer-namespace-keyword': 'error', + 'prefer-promise-reject-errors': 'off', + '@typescript-eslint/prefer-promise-reject-errors': 'error', 'require-await': 'off', '@typescript-eslint/require-await': 'error', '@typescript-eslint/restrict-plus-operands': 'error', diff --git a/packages/typescript-eslint/src/configs/recommended.ts b/packages/typescript-eslint/src/configs/recommended.ts index 7df78599ea91..b0e2c08f453e 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', @@ -33,10 +32,14 @@ export default ( '@typescript-eslint/no-this-alias': 'error', '@typescript-eslint/no-unnecessary-type-constraint': 'error', '@typescript-eslint/no-unsafe-declaration-merging': 'error', + 'no-unused-expressions': 'off', + '@typescript-eslint/no-unused-expressions': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', '@typescript-eslint/no-var-requires': '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-only.ts b/packages/typescript-eslint/src/configs/strict-type-checked-only.ts index f17b5280ca49..d725cdb4b85f 100644 --- a/packages/typescript-eslint/src/configs/strict-type-checked-only.ts +++ b/packages/typescript-eslint/src/configs/strict-type-checked-only.ts @@ -42,10 +42,10 @@ export default ( '@typescript-eslint/no-unsafe-enum-comparison': 'error', '@typescript-eslint/no-unsafe-member-access': 'error', '@typescript-eslint/no-unsafe-return': 'error', + '@typescript-eslint/no-unsafe-unary-minus': 'error', '@typescript-eslint/no-useless-template-literals': 'error', 'no-throw-literal': 'off', '@typescript-eslint/only-throw-error': 'error', - '@typescript-eslint/prefer-includes': 'error', 'prefer-promise-reject-errors': 'off', '@typescript-eslint/prefer-promise-reject-errors': 'error', '@typescript-eslint/prefer-reduce-type-parameter': 'error', diff --git a/packages/typescript-eslint/src/configs/strict-type-checked.ts b/packages/typescript-eslint/src/configs/strict-type-checked.ts index 1542bf528504..565221ac191f 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,17 +63,21 @@ export default ( '@typescript-eslint/no-unsafe-enum-comparison': 'error', '@typescript-eslint/no-unsafe-member-access': 'error', '@typescript-eslint/no-unsafe-return': 'error', + '@typescript-eslint/no-unsafe-unary-minus': '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-useless-template-literals': 'error', '@typescript-eslint/no-var-requires': 'error', + '@typescript-eslint/no-wrapper-object-types': 'error', 'no-throw-literal': 'off', '@typescript-eslint/only-throw-error': 'error', '@typescript-eslint/prefer-as-const': 'error', - '@typescript-eslint/prefer-includes': 'error', '@typescript-eslint/prefer-literal-enum-member': 'error', + '@typescript-eslint/prefer-namespace-keyword': 'error', 'prefer-promise-reject-errors': 'off', '@typescript-eslint/prefer-promise-reject-errors': 'error', '@typescript-eslint/prefer-reduce-type-parameter': 'error', diff --git a/packages/typescript-eslint/src/configs/strict.ts b/packages/typescript-eslint/src/configs/strict.ts index 680813d7d64f..db9971b8d570 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', @@ -41,13 +40,17 @@ export default ( '@typescript-eslint/no-this-alias': 'error', '@typescript-eslint/no-unnecessary-type-constraint': 'error', '@typescript-eslint/no-unsafe-declaration-merging': '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-var-requires': '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', '@typescript-eslint/prefer-ts-expect-error': 'error', '@typescript-eslint/triple-slash-reference': 'error', '@typescript-eslint/unified-signatures': 'error', diff --git a/packages/typescript-eslint/src/configs/stylistic-type-checked-only.ts b/packages/typescript-eslint/src/configs/stylistic-type-checked-only.ts index 2cd41eba5178..39354a3cc759 100644 --- a/packages/typescript-eslint/src/configs/stylistic-type-checked-only.ts +++ b/packages/typescript-eslint/src/configs/stylistic-type-checked-only.ts @@ -22,8 +22,11 @@ export default ( 'dot-notation': 'off', '@typescript-eslint/dot-notation': 'error', '@typescript-eslint/non-nullable-type-assertion-style': 'error', + '@typescript-eslint/prefer-find': 'error', + '@typescript-eslint/prefer-includes': 'error', '@typescript-eslint/prefer-nullish-coalescing': 'error', '@typescript-eslint/prefer-optional-chain': 'error', + '@typescript-eslint/prefer-regexp-exec': 'error', '@typescript-eslint/prefer-string-starts-ends-with': 'error', }, }, diff --git a/packages/typescript-eslint/src/configs/stylistic-type-checked.ts b/packages/typescript-eslint/src/configs/stylistic-type-checked.ts index 63ef5a71d0f4..f1c1efaf2e10 100644 --- a/packages/typescript-eslint/src/configs/stylistic-type-checked.ts +++ b/packages/typescript-eslint/src/configs/stylistic-type-checked.ts @@ -34,11 +34,13 @@ export default ( '@typescript-eslint/no-empty-function': 'error', '@typescript-eslint/no-inferrable-types': 'error', '@typescript-eslint/non-nullable-type-assertion-style': 'error', + '@typescript-eslint/prefer-find': 'error', '@typescript-eslint/prefer-for-of': 'error', '@typescript-eslint/prefer-function-type': 'error', - '@typescript-eslint/prefer-namespace-keyword': 'error', + '@typescript-eslint/prefer-includes': 'error', '@typescript-eslint/prefer-nullish-coalescing': 'error', '@typescript-eslint/prefer-optional-chain': 'error', + '@typescript-eslint/prefer-regexp-exec': 'error', '@typescript-eslint/prefer-string-starts-ends-with': 'error', }, }, diff --git a/packages/typescript-eslint/src/configs/stylistic.ts b/packages/typescript-eslint/src/configs/stylistic.ts index 6e12fe9de23a..cd165bf5bdac 100644 --- a/packages/typescript-eslint/src/configs/stylistic.ts +++ b/packages/typescript-eslint/src/configs/stylistic.ts @@ -33,7 +33,6 @@ export default ( '@typescript-eslint/no-inferrable-types': 'error', '@typescript-eslint/prefer-for-of': 'error', '@typescript-eslint/prefer-function-type': 'error', - '@typescript-eslint/prefer-namespace-keyword': 'error', }, }, ]; From 43fa7982ddfc726f3e8eb206505e486577bc2ce1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Tue, 28 May 2024 08:26:21 -0400 Subject: [PATCH 14/32] Apply suggestions from code review Co-authored-by: Joshua Chen Co-authored-by: Brad Zacher --- packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx | 2 +- packages/eslint-plugin/src/rules/no-wrapper-object-types.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx b/packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx index b2a0e7556d41..58fc099f0dea 100644 --- a/packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx +++ b/packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx @@ -13,7 +13,7 @@ The JavaScript language includes a set of primitives such as `boolean` and `numb JavaScript also contains 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). TypeScript represents the primitive in lower-case types and instances of the wrapper classes with upper-case. -Using the primitives like `0` instead of class constructors like `Number(0)` is generally considered a JavaScript best practice. +Using the primitives like `0` instead of class constructors like `new Number(0)` is generally considered a JavaScript best practice. As a result, using the lower-case primitive type names like `number` instead of class names like `Number` is generally considered a TypeScript best practice. Similarly, TypeScript's `Function` type refers to objects created with the `Function` constructor, i.e. `new Function("eval'd code with implicit args")`. diff --git a/packages/eslint-plugin/src/rules/no-wrapper-object-types.ts b/packages/eslint-plugin/src/rules/no-wrapper-object-types.ts index c31503f3021e..7826a51026a4 100644 --- a/packages/eslint-plugin/src/rules/no-wrapper-object-types.ts +++ b/packages/eslint-plugin/src/rules/no-wrapper-object-types.ts @@ -26,7 +26,7 @@ export default createRule({ fixable: 'code', messages: { bannedClassType: - 'Prefer using the primitive `{{preferred}}` as a type name, rather than the class wrapper `{{typeName}}`.', + 'Prefer using the primitive `{{preferred}}` as a type name, rather than the upper-cased `{{typeName}}`.', bannedFunctionType: [ '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.', From c220790aa24f67c92f7918be10b1e334eed9f9ad Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 28 May 2024 08:33:58 -0400 Subject: [PATCH 15/32] fix ban-types.md --- packages/eslint-plugin/docs/rules/ban-types.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/ban-types.md b/packages/eslint-plugin/docs/rules/ban-types.md index 0d2b16627159..c2f4df846cf5 100644 --- a/packages/eslint-plugin/docs/rules/ban-types.md +++ b/packages/eslint-plugin/docs/rules/ban-types.md @@ -14,8 +14,8 @@ The default options from `ban-types` are now covered by: See [Announcing typescript-eslint v8 Beta](/announcing-typescript-eslint-v8-beta) for more details. ::: - From 732e81222dcca7c55e76ee4d9ed449534d5b98a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Fri, 31 May 2024 15:33:22 -0400 Subject: [PATCH 16/32] Update packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx Co-authored-by: Joshua Chen --- .../docs/rules/no-wrapper-object-types.mdx | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx b/packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx index 58fc099f0dea..a102063fc787 100644 --- a/packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx +++ b/packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx @@ -9,16 +9,18 @@ import TabItem from '@theme/TabItem'; > > See **https://typescript-eslint.io/rules/no-wrapper-object-types** for documentation. -The JavaScript language includes a set of primitives such as `boolean` and `number`s. -JavaScript also contains 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). -TypeScript represents the primitive in lower-case types and instances of the wrapper classes with upper-case. - -Using the primitives like `0` instead of class constructors like `new Number(0)` is generally considered a JavaScript best practice. -As a result, using the lower-case primitive type names like `number` instead of class names like `Number` is generally considered a TypeScript best practice. - -Similarly, TypeScript's `Function` type refers to objects created with the `Function` constructor, i.e. `new Function("eval'd code with implicit args")`. -`Function` allows being called with any number of arguments and returns type `any`. -It's generally better to specify function parameter and return types with function type syntax. +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. +In general, your functions want to work with real numbers, instead of objects that "look like" numbers. +As a result, using the lowercase type names like `number` instead of the uppercase names like `Number` helps make your code behave more reliably. + +TypeScript's `Function` type suffers from the same issue of structural typing. +In addition, `Function` allows being called with any number of arguments and returns type `any`, while in reality, it may be a class which is never callable, or it may be a plain object that happens to possess all properties of the `Function` class! +It's generally better to specify function parameters and return types with the function type syntax. If you want a generic "callable" type, use `(...args: never) => unknown`. Examples of code for this rule: From b5e541039fdfe170dd27090c8688878f715ea044 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Fri, 31 May 2024 15:36:43 -0400 Subject: [PATCH 17/32] lil nit --- packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx b/packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx index a102063fc787..563c00cf3af6 100644 --- a/packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx +++ b/packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx @@ -15,7 +15,7 @@ JavaScript also has a "wrapper" class object for each of those primitives, such 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. -In general, your functions want to work with real numbers, instead of objects that "look like" numbers. +JavaScript programs typically work with the real number primitives, rather than objects that "look like" numbers. As a result, using the lowercase type names like `number` instead of the uppercase names like `Number` helps make your code behave more reliably. TypeScript's `Function` type suffers from the same issue of structural typing. From 45df4d6f44aa3c74bef334965bfc4a385a301eeb Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Fri, 31 May 2024 15:37:09 -0400 Subject: [PATCH 18/32] remove callable type --- packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx b/packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx index 563c00cf3af6..86f696c5e97b 100644 --- a/packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx +++ b/packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx @@ -20,7 +20,7 @@ As a result, using the lowercase type names like `number` instead of the upperca TypeScript's `Function` type suffers from the same issue of structural typing. In addition, `Function` allows being called with any number of arguments and returns type `any`, while in reality, it may be a class which is never callable, or it may be a plain object that happens to possess all properties of the `Function` class! -It's generally better to specify function parameters and return types with the function type syntax. If you want a generic "callable" type, use `(...args: never) => unknown`. +It's generally better to specify function parameters and return types with the function type syntax. Examples of code for this rule: From fec4311eb8439c95afc57c8459f69a9d62778ac5 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sun, 2 Jun 2024 14:02:43 -0400 Subject: [PATCH 19/32] Split out no-unsafe-function-type too --- .../eslint-plugin/docs/rules/ban-types.md | 5 +- .../docs/rules/no-unsafe-function-type.mdx | 59 ++++++++++++++ .../docs/rules/no-wrapper-object-types.mdx | 10 --- packages/eslint-plugin/src/configs/all.ts | 1 + .../src/configs/recommended-type-checked.ts | 1 + .../eslint-plugin/src/configs/recommended.ts | 1 + .../src/configs/strict-type-checked.ts | 1 + packages/eslint-plugin/src/configs/strict.ts | 1 + packages/eslint-plugin/src/rules/index.ts | 2 + .../src/rules/no-unsafe-function-type.ts | 46 +++++++++++ .../src/rules/no-wrapper-object-types.ts | 15 ---- .../no-unsafe-function-type.shot | 35 +++++++++ .../no-wrapper-object-types.shot | 23 ++---- packages/eslint-plugin/tests/docs.test.ts | 1 + .../rules/no-unsafe-function-type.test.ts | 76 +++++++++++++++++++ .../rules/no-wrapper-object-types.test.ts | 13 +++- .../no-unsafe-function-type.shot | 14 ++++ packages/typescript-eslint/src/configs/all.ts | 1 + .../src/configs/recommended-type-checked.ts | 1 + .../src/configs/recommended.ts | 1 + .../src/configs/strict-type-checked.ts | 1 + .../typescript-eslint/src/configs/strict.ts | 1 + 22 files changed, 262 insertions(+), 47 deletions(-) create mode 100644 packages/eslint-plugin/docs/rules/no-unsafe-function-type.mdx create mode 100644 packages/eslint-plugin/src/rules/no-unsafe-function-type.ts create mode 100644 packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unsafe-function-type.shot create mode 100644 packages/eslint-plugin/tests/rules/no-unsafe-function-type.test.ts create mode 100644 packages/eslint-plugin/tests/schema-snapshots/no-unsafe-function-type.shot diff --git a/packages/eslint-plugin/docs/rules/ban-types.md b/packages/eslint-plugin/docs/rules/ban-types.md index c2f4df846cf5..07a2a8f800a1 100644 --- a/packages/eslint-plugin/docs/rules/ban-types.md +++ b/packages/eslint-plugin/docs/rules/ban-types.md @@ -7,8 +7,9 @@ It has no options enabled by default. The default options from `ban-types` are now covered by: -- **[`no-empty-object-type`](./no-empty-object-type)**: banning the built-in `{}` type in confusing locations -- **[`no-wrapper-object-types`](./no-wrapper-object-types.mdx)**: banning built-in class wrappers such as `Number` +- **[`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](/announcing-typescript-eslint-v8-beta) for more details. 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..24ff8d7f9bbd --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-unsafe-function-type.mdx @@ -0,0 +1,59 @@ +--- +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 object that happens to possess all properties of the `Function` class. +It's generally better to specify function parameters and return types with the function type syntax. + +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 + +- [`ban-types`](./ban-types.md) +- [`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 index 86f696c5e97b..75a7f38ecb30 100644 --- a/packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx +++ b/packages/eslint-plugin/docs/rules/no-wrapper-object-types.mdx @@ -18,10 +18,6 @@ Using the primitives like `0` instead of object wrappers like `new Number(0)` is JavaScript programs typically work with the real number primitives, rather than objects that "look like" numbers. As a result, using the lowercase type names like `number` instead of the uppercase names like `Number` helps make your code behave more reliably. -TypeScript's `Function` type suffers from the same issue of structural typing. -In addition, `Function` allows being called with any number of arguments and returns type `any`, while in reality, it may be a class which is never callable, or it may be a plain object that happens to possess all properties of the `Function` class! -It's generally better to specify function parameters and return types with the function type syntax. - Examples of code for this rule: @@ -35,9 +31,6 @@ let myString: String; let mySymbol: Symbol; let myObject: Object = 'allowed by TypeScript'; - -let myFunction: Function; -myFunction = () => {}; ``` @@ -51,9 +44,6 @@ let myString: string; let mySymbol: symbol; let myObject: object = "Type 'string' is not assignable to type 'object'."; - -let myFunction: () => void; -myFunction = () => {}; ``` diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts index c7fde8c4dbc9..97ec2915b638 100644 --- a/packages/eslint-plugin/src/configs/all.ts +++ b/packages/eslint-plugin/src/configs/all.ts @@ -99,6 +99,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', diff --git a/packages/eslint-plugin/src/configs/recommended-type-checked.ts b/packages/eslint-plugin/src/configs/recommended-type-checked.ts index 6c38a5068ff0..6d88ce1208e3 100644 --- a/packages/eslint-plugin/src/configs/recommended-type-checked.ts +++ b/packages/eslint-plugin/src/configs/recommended-type-checked.ts @@ -38,6 +38,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', diff --git a/packages/eslint-plugin/src/configs/recommended.ts b/packages/eslint-plugin/src/configs/recommended.ts index b11045f53e8e..ab7d48b6d4c4 100644 --- a/packages/eslint-plugin/src/configs/recommended.ts +++ b/packages/eslint-plugin/src/configs/recommended.ts @@ -23,6 +23,7 @@ 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', diff --git a/packages/eslint-plugin/src/configs/strict-type-checked.ts b/packages/eslint-plugin/src/configs/strict-type-checked.ts index f1136db6ee09..c9671d089862 100644 --- a/packages/eslint-plugin/src/configs/strict-type-checked.ts +++ b/packages/eslint-plugin/src/configs/strict-type-checked.ts @@ -52,6 +52,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', diff --git a/packages/eslint-plugin/src/configs/strict.ts b/packages/eslint-plugin/src/configs/strict.ts index 112dc77dd2e6..48d99a4d4505 100644 --- a/packages/eslint-plugin/src/configs/strict.ts +++ b/packages/eslint-plugin/src/configs/strict.ts @@ -31,6 +31,7 @@ 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', diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index d90ff7fef1bc..0443a9892c58 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -76,6 +76,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'; @@ -201,6 +202,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, 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..099f6e10edda --- /dev/null +++ b/packages/eslint-plugin/src/rules/no-unsafe-function-type.ts @@ -0,0 +1,46 @@ +import type { TSESTree } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; + +import { createRule } 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') { + 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 index 7826a51026a4..f0f0fca228d7 100644 --- a/packages/eslint-plugin/src/rules/no-wrapper-object-types.ts +++ b/packages/eslint-plugin/src/rules/no-wrapper-object-types.ts @@ -7,7 +7,6 @@ const classNames = new Set([ 'BigInt', // eslint-disable-next-line @typescript-eslint/internal/prefer-ast-types-enum 'Boolean', - 'Function', 'Number', 'Object', // eslint-disable-next-line @typescript-eslint/internal/prefer-ast-types-enum @@ -27,12 +26,6 @@ export default createRule({ messages: { bannedClassType: 'Prefer using the primitive `{{preferred}}` as a type name, rather than the upper-cased `{{typeName}}`.', - bannedFunctionType: [ - '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'), }, schema: [], }, @@ -47,14 +40,6 @@ export default createRule({ return; } - if (typeName === 'Function') { - context.report({ - node, - messageId: 'bannedFunctionType', - }); - return; - } - const preferred = typeName.toLowerCase(); context.report({ 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..5290bd481dcd --- /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 index f71e067a55a7..4303b79a77a7 100644 --- 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 @@ -4,25 +4,18 @@ exports[`Validating rule docs no-wrapper-object-types.mdx code examples ESLint o "Incorrect let myBigInt: BigInt; - ~~~~~~ Prefer using the primitive \`bigint\` as a type name, rather than the class wrapper \`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 class wrapper \`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 class wrapper \`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 class wrapper \`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 class wrapper \`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 class wrapper \`Object\`. - -let myFunction: Function; - ~~~~~~~~ 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. -myFunction = () => {}; + ~~~~~~ Prefer using the primitive \`object\` as a type name, rather than the upper-cased \`Object\`. " `; @@ -32,13 +25,9 @@ exports[`Validating rule docs no-wrapper-object-types.mdx code examples ESLint o let myBigint: bigint; let myBoolean: boolean; let myNumber: number; -let myObject: object; let myString: string; let mySymbol: symbol; let myObject: object = "Type 'string' is not assignable to type 'object'."; - -let myFunction: () => void; -myFunction = () => {}; " `; diff --git a/packages/eslint-plugin/tests/docs.test.ts b/packages/eslint-plugin/tests/docs.test.ts index e71524a0beac..6637e57963a4 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', ...oldStylisticRules, 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..2eccacfe5b50 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/no-unsafe-function-type.test.ts @@ -0,0 +1,76 @@ +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;'], + 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 index e15117ea327d..de365a6e37ef 100644 --- a/packages/eslint-plugin/tests/rules/no-wrapper-object-types.test.ts +++ b/packages/eslint-plugin/tests/rules/no-wrapper-object-types.test.ts @@ -125,14 +125,21 @@ ruleTester.run('no-wrapper-object-types', rule, { ], }, { - code: 'let value: Function;', - output: null, + code: 'let value: Number | Symbol;', + output: 'let value: number | symbol;', errors: [ { - messageId: 'bannedFunctionType', + data: { typeName: 'Number', preferred: 'number' }, + messageId: 'bannedClassType', line: 1, column: 12, }, + { + data: { typeName: 'Symbol', preferred: 'symbol' }, + messageId: 'bannedClassType', + line: 1, + column: 21, + }, ], }, { 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/typescript-eslint/src/configs/all.ts b/packages/typescript-eslint/src/configs/all.ts index c13027b4ba3d..41612e36b48d 100644 --- a/packages/typescript-eslint/src/configs/all.ts +++ b/packages/typescript-eslint/src/configs/all.ts @@ -108,6 +108,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', diff --git a/packages/typescript-eslint/src/configs/recommended-type-checked.ts b/packages/typescript-eslint/src/configs/recommended-type-checked.ts index 1efe108bbf7b..63232a18105e 100644 --- a/packages/typescript-eslint/src/configs/recommended-type-checked.ts +++ b/packages/typescript-eslint/src/configs/recommended-type-checked.ts @@ -47,6 +47,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', diff --git a/packages/typescript-eslint/src/configs/recommended.ts b/packages/typescript-eslint/src/configs/recommended.ts index b0e2c08f453e..472bdb424241 100644 --- a/packages/typescript-eslint/src/configs/recommended.ts +++ b/packages/typescript-eslint/src/configs/recommended.ts @@ -32,6 +32,7 @@ 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', diff --git a/packages/typescript-eslint/src/configs/strict-type-checked.ts b/packages/typescript-eslint/src/configs/strict-type-checked.ts index 565221ac191f..e1c82c11d018 100644 --- a/packages/typescript-eslint/src/configs/strict-type-checked.ts +++ b/packages/typescript-eslint/src/configs/strict-type-checked.ts @@ -61,6 +61,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', diff --git a/packages/typescript-eslint/src/configs/strict.ts b/packages/typescript-eslint/src/configs/strict.ts index db9971b8d570..9a03900bdced 100644 --- a/packages/typescript-eslint/src/configs/strict.ts +++ b/packages/typescript-eslint/src/configs/strict.ts @@ -40,6 +40,7 @@ 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', From e357c8e49d82b6e90ad97e11b7651ad8c464fd92 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sun, 2 Jun 2024 14:36:07 -0400 Subject: [PATCH 20/32] fix /blog link in ban-types.md --- packages/eslint-plugin/docs/rules/ban-types.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/ban-types.md b/packages/eslint-plugin/docs/rules/ban-types.md index 07a2a8f800a1..148f68d383f6 100644 --- a/packages/eslint-plugin/docs/rules/ban-types.md +++ b/packages/eslint-plugin/docs/rules/ban-types.md @@ -12,7 +12,7 @@ The default options from `ban-types` are now covered by: - **[`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](/announcing-typescript-eslint-v8-beta) for more details. +See [Announcing typescript-eslint v8 Beta](/blog/announcing-typescript-eslint-v8-beta) for more details. :::

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