diff --git a/packages/eslint-plugin/src/rules/no-explicit-any.ts b/packages/eslint-plugin/src/rules/no-explicit-any.ts index 1571617d8242..27079fac7184 100644 --- a/packages/eslint-plugin/src/rules/no-explicit-any.ts +++ b/packages/eslint-plugin/src/rules/no-explicit-any.ts @@ -10,7 +10,11 @@ export type Options = [ ignoreRestArgs?: boolean; }, ]; -export type MessageIds = 'suggestNever' | 'suggestUnknown' | 'unexpectedAny'; +export type MessageIds = + | 'suggestNever' + | 'suggestPropertyKey' + | 'suggestUnknown' + | 'unexpectedAny'; export default createRule({ name: 'no-explicit-any', @@ -25,6 +29,8 @@ export default createRule({ messages: { suggestNever: "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of.", + suggestPropertyKey: + 'Use `PropertyKey` instead, this is more explicit than `keyof any`.', suggestUnknown: 'Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct.', unexpectedAny: 'Unexpected any. Specify a different type.', @@ -170,8 +176,35 @@ export default createRule({ ); } + /** + * Checks if the node is within a keyof any expression + * @param node the node to be validated. + * @returns true if the node is within a keyof any expression, false otherwise + * @private + */ + function isNodeWithinKeyofAny(node: TSESTree.TSAnyKeyword): boolean { + return ( + node.parent.type === AST_NODE_TYPES.TSTypeOperator && + node.parent.operator === 'keyof' + ); + } + + /** + * Creates a fixer that replaces a keyof any with PropertyKey + * @param node the node to be fixed. + * @returns a function that will fix the node. + * @private + */ + function createPropertyKeyFixer(node: TSESTree.TSAnyKeyword) { + return (fixer: TSESLint.RuleFixer) => { + return fixer.replaceText(node.parent, 'PropertyKey'); + }; + } + return { TSAnyKeyword(node): void { + const isKeyofAny = isNodeWithinKeyofAny(node); + if (ignoreRestArgs && isNodeDescendantOfRestElementInFunction(node)) { return; } @@ -181,25 +214,29 @@ export default createRule({ suggest: TSESLint.ReportSuggestionArray | null; } = { fix: null, - suggest: [ - { - messageId: 'suggestUnknown', - fix(fixer): TSESLint.RuleFix { - return fixer.replaceText(node, 'unknown'); - }, - }, - { - messageId: 'suggestNever', - fix(fixer): TSESLint.RuleFix { - return fixer.replaceText(node, 'never'); - }, - }, - ], + suggest: isKeyofAny + ? [ + { + messageId: 'suggestPropertyKey', + fix: createPropertyKeyFixer(node), + }, + ] + : [ + { + messageId: 'suggestUnknown', + fix: fixer => fixer.replaceText(node, 'unknown'), + }, + { + messageId: 'suggestNever', + fix: fixer => fixer.replaceText(node, 'never'), + }, + ], }; if (fixToUnknown) { - fixOrSuggest.fix = (fixer): TSESLint.RuleFix => - fixer.replaceText(node, 'unknown'); + fixOrSuggest.fix = isKeyofAny + ? createPropertyKeyFixer(node) + : fixer => fixer.replaceText(node, 'unknown'); } context.report({ diff --git a/packages/eslint-plugin/tests/rules/no-explicit-any.test.ts b/packages/eslint-plugin/tests/rules/no-explicit-any.test.ts index 223623556846..25e4ce3f56e7 100644 --- a/packages/eslint-plugin/tests/rules/no-explicit-any.test.ts +++ b/packages/eslint-plugin/tests/rules/no-explicit-any.test.ts @@ -1,17 +1,7 @@ -import type { - InvalidTestCase, - SuggestionOutput, -} from '@typescript-eslint/rule-tester'; - import { RuleTester } from '@typescript-eslint/rule-tester'; -import type { MessageIds, Options } from '../../src/rules/no-explicit-any'; - import rule from '../../src/rules/no-explicit-any'; -type RuleInvalidTestCase = InvalidTestCase; -type RuleSuggestionOutput = SuggestionOutput; - const ruleTester = new RuleTester(); ruleTester.run('no-explicit-any', rule, { @@ -372,876 +362,1725 @@ interface Garply4 { options: [{ ignoreRestArgs: true }], }, ], - invalid: ( - [ - { - code: 'const number: any = 1', - errors: [ - { - column: 15, - line: 1, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: 'function generic(): any {}', - errors: [ - { - column: 21, - line: 1, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: 'function generic(): Array {}', - errors: [ - { - column: 27, - line: 1, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: 'function generic(): any[] {}', - errors: [ - { - column: 21, - line: 1, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: 'function generic(param: Array): number {}', - errors: [ - { - column: 31, - line: 1, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: 'function generic(param: any[]): number {}', - errors: [ - { - column: 25, - line: 1, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: 'function generic(param: Array): Array {}', - errors: [ - { - column: 31, - line: 1, - messageId: 'unexpectedAny', - suggestions: [ - { - messageId: 'suggestUnknown', - output: - 'function generic(param: Array): Array {}', - }, - { - messageId: 'suggestNever', - output: 'function generic(param: Array): Array {}', - }, - ], - }, - { - column: 44, - line: 1, - messageId: 'unexpectedAny', - suggestions: [ - { - messageId: 'suggestUnknown', - output: - 'function generic(param: Array): Array {}', - }, - { - messageId: 'suggestNever', - output: 'function generic(param: Array): Array {}', - }, - ], - }, - ], - }, - { - code: 'function generic(): Array> {}', - errors: [ - { - column: 33, - line: 1, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: 'function generic(): Array {}', - errors: [ - { - column: 27, - line: 1, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: ` + invalid: [ + { + code: 'const number: any = 1;', + errors: [ + { + column: 15, + line: 1, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: 'const number: unknown = 1;', + }, + { + messageId: 'suggestNever', + output: 'const number: never = 1;', + }, + ], + }, + ], + }, + { + code: 'function generic(): any {}', + errors: [ + { + column: 21, + line: 1, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: 'function generic(): unknown {}', + }, + { + messageId: 'suggestNever', + output: 'function generic(): never {}', + }, + ], + }, + ], + }, + { + code: 'function generic(): Array {}', + errors: [ + { + column: 27, + line: 1, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: 'function generic(): Array {}', + }, + { + messageId: 'suggestNever', + output: 'function generic(): Array {}', + }, + ], + }, + ], + }, + { + code: 'function generic(): any[] {}', + errors: [ + { + column: 21, + line: 1, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: 'function generic(): unknown[] {}', + }, + { + messageId: 'suggestNever', + output: 'function generic(): never[] {}', + }, + ], + }, + ], + }, + { + code: 'function generic(param: Array): number {}', + errors: [ + { + column: 31, + line: 1, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: 'function generic(param: Array): number {}', + }, + { + messageId: 'suggestNever', + output: 'function generic(param: Array): number {}', + }, + ], + }, + ], + }, + { + code: 'function generic(param: any[]): number {}', + errors: [ + { + column: 25, + line: 1, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: 'function generic(param: unknown[]): number {}', + }, + { + messageId: 'suggestNever', + output: 'function generic(param: never[]): number {}', + }, + ], + }, + ], + }, + { + code: 'function generic(param: Array): Array {}', + errors: [ + { + column: 31, + line: 1, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: 'function generic(param: Array): Array {}', + }, + { + messageId: 'suggestNever', + output: 'function generic(param: Array): Array {}', + }, + ], + }, + { + column: 44, + line: 1, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: 'function generic(param: Array): Array {}', + }, + { + messageId: 'suggestNever', + output: 'function generic(param: Array): Array {}', + }, + ], + }, + ], + }, + { + code: 'function generic(): Array> {}', + errors: [ + { + column: 33, + line: 1, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: 'function generic(): Array> {}', + }, + { + messageId: 'suggestNever', + output: 'function generic(): Array> {}', + }, + ], + }, + ], + }, + { + code: 'function generic(): Array {}', + errors: [ + { + column: 27, + line: 1, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: 'function generic(): Array {}', + }, + { + messageId: 'suggestNever', + output: 'function generic(): Array {}', + }, + ], + }, + ], + }, + { + code: ` +class Greeter { + constructor(param: Array) {} +} + `, + errors: [ + { + column: 28, + line: 3, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: ` +class Greeter { + constructor(param: Array) {} +} + `, + }, + { + messageId: 'suggestNever', + output: ` +class Greeter { + constructor(param: Array) {} +} + `, + }, + ], + }, + ], + }, + { + code: ` +class Greeter { + message: any; +} + `, + errors: [ + { + column: 12, + line: 3, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: ` +class Greeter { + message: unknown; +} + `, + }, + { + messageId: 'suggestNever', + output: ` +class Greeter { + message: never; +} + `, + }, + ], + }, + ], + }, + { + code: ` +class Greeter { + message: Array; +} + `, + errors: [ + { + column: 18, + line: 3, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: ` +class Greeter { + message: Array; +} + `, + }, + { + messageId: 'suggestNever', + output: ` +class Greeter { + message: Array; +} + `, + }, + ], + }, + ], + }, + { + code: ` +class Greeter { + message: any[]; +} + `, + errors: [ + { + column: 12, + line: 3, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: ` +class Greeter { + message: unknown[]; +} + `, + }, + { + messageId: 'suggestNever', + output: ` class Greeter { - constructor(param: Array) {} -} - `, - errors: [ - { - column: 30, - line: 3, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: ` + message: never[]; +} + `, + }, + ], + }, + ], + }, + { + code: ` class Greeter { - message: any; -} - `, - errors: [ - { - column: 14, - line: 3, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: ` + message: Array>; +} + `, + errors: [ + { + column: 24, + line: 3, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: ` class Greeter { - message: Array; -} - `, - errors: [ - { - column: 20, - line: 3, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: ` + message: Array>; +} + `, + }, + { + messageId: 'suggestNever', + output: ` +class Greeter { + message: Array>; +} + `, + }, + ], + }, + ], + }, + { + code: ` class Greeter { - message: any[]; -} - `, - errors: [ - { - column: 14, - line: 3, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: ` + message: Array; +} + `, + errors: [ + { + column: 18, + line: 3, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: ` class Greeter { - message: Array>; -} - `, - errors: [ - { - column: 26, - line: 3, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: ` + message: Array; +} + `, + }, + { + messageId: 'suggestNever', + output: ` class Greeter { - message: Array; -} - `, - errors: [ - { - column: 20, - line: 3, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: ` + message: Array; +} + `, + }, + ], + }, + ], + }, + { + code: ` +interface Greeter { + message: any; +} + `, + errors: [ + { + column: 12, + line: 3, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: ` interface Greeter { - message: any; -} - `, - errors: [ - { - column: 14, - line: 3, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: ` + message: unknown; +} + `, + }, + { + messageId: 'suggestNever', + output: ` interface Greeter { - message: Array; -} - `, - errors: [ - { - column: 20, - line: 3, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: ` + message: never; +} + `, + }, + ], + }, + ], + }, + { + code: ` interface Greeter { - message: any[]; -} - `, - errors: [ - { - column: 14, - line: 3, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: ` + message: Array; +} + `, + errors: [ + { + column: 18, + line: 3, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: ` interface Greeter { - message: Array>; -} - `, - errors: [ - { - column: 26, - line: 3, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: ` + message: Array; +} + `, + }, + { + messageId: 'suggestNever', + output: ` interface Greeter { - message: Array; -} - `, - errors: [ - { - column: 20, - line: 3, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: ` + message: Array; +} + `, + }, + ], + }, + ], + }, + { + code: ` +interface Greeter { + message: any[]; +} + `, + errors: [ + { + column: 12, + line: 3, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: ` +interface Greeter { + message: unknown[]; +} + `, + }, + { + messageId: 'suggestNever', + output: ` +interface Greeter { + message: never[]; +} + `, + }, + ], + }, + ], + }, + { + code: ` +interface Greeter { + message: Array>; +} + `, + errors: [ + { + column: 24, + line: 3, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: ` +interface Greeter { + message: Array>; +} + `, + }, + { + messageId: 'suggestNever', + output: ` +interface Greeter { + message: Array>; +} + `, + }, + ], + }, + ], + }, + { + code: ` +interface Greeter { + message: Array; +} + `, + errors: [ + { + column: 18, + line: 3, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: ` +interface Greeter { + message: Array; +} + `, + }, + { + messageId: 'suggestNever', + output: ` +interface Greeter { + message: Array; +} + `, + }, + ], + }, + ], + }, + { + code: ` +type obj = { + message: any; +}; + `, + errors: [ + { + column: 12, + line: 3, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: ` +type obj = { + message: unknown; +}; + `, + }, + { + messageId: 'suggestNever', + output: ` +type obj = { + message: never; +}; + `, + }, + ], + }, + ], + }, + { + code: ` +type obj = { + message: Array; +}; + `, + errors: [ + { + column: 18, + line: 3, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: ` +type obj = { + message: Array; +}; + `, + }, + { + messageId: 'suggestNever', + output: ` +type obj = { + message: Array; +}; + `, + }, + ], + }, + ], + }, + { + code: ` +type obj = { + message: any[]; +}; + `, + errors: [ + { + column: 12, + line: 3, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: ` +type obj = { + message: unknown[]; +}; + `, + }, + { + messageId: 'suggestNever', + output: ` +type obj = { + message: never[]; +}; + `, + }, + ], + }, + ], + }, + { + code: ` +type obj = { + message: Array>; +}; + `, + errors: [ + { + column: 24, + line: 3, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: ` +type obj = { + message: Array>; +}; + `, + }, + { + messageId: 'suggestNever', + output: ` +type obj = { + message: Array>; +}; + `, + }, + ], + }, + ], + }, + { + code: ` +type obj = { + message: Array; +}; + `, + errors: [ + { + column: 18, + line: 3, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: ` +type obj = { + message: Array; +}; + `, + }, + { + messageId: 'suggestNever', + output: ` +type obj = { + message: Array; +}; + `, + }, + ], + }, + ], + }, + { + code: ` +type obj = { + message: string | any; +}; + `, + errors: [ + { + column: 21, + line: 3, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: ` type obj = { - message: any; -} - `, - errors: [ - { - column: 14, - line: 3, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: ` + message: string | unknown; +}; + `, + }, + { + messageId: 'suggestNever', + output: ` +type obj = { + message: string | never; +}; + `, + }, + ], + }, + ], + }, + { + code: ` +type obj = { + message: string | Array; +}; + `, + errors: [ + { + column: 27, + line: 3, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: ` +type obj = { + message: string | Array; +}; + `, + }, + { + messageId: 'suggestNever', + output: ` +type obj = { + message: string | Array; +}; + `, + }, + ], + }, + ], + }, + { + code: ` +type obj = { + message: string | any[]; +}; + `, + errors: [ + { + column: 21, + line: 3, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: ` +type obj = { + message: string | unknown[]; +}; + `, + }, + { + messageId: 'suggestNever', + output: ` +type obj = { + message: string | never[]; +}; + `, + }, + ], + }, + ], + }, + { + code: ` +type obj = { + message: string | Array>; +}; + `, + errors: [ + { + column: 33, + line: 3, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: ` +type obj = { + message: string | Array>; +}; + `, + }, + { + messageId: 'suggestNever', + output: ` +type obj = { + message: string | Array>; +}; + `, + }, + ], + }, + ], + }, + { + code: ` +type obj = { + message: string | Array; +}; + `, + errors: [ + { + column: 27, + line: 3, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: ` +type obj = { + message: string | Array; +}; + `, + }, + { + messageId: 'suggestNever', + output: ` +type obj = { + message: string | Array; +}; + `, + }, + ], + }, + ], + }, + { + code: ` type obj = { - message: Array; -} - `, - errors: [ - { - column: 20, - line: 3, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: ` + message: string & any; +}; + `, + errors: [ + { + column: 21, + line: 3, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: ` type obj = { - message: any[]; -} - `, - errors: [ - { - column: 14, - line: 3, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: ` + message: string & unknown; +}; + `, + }, + { + messageId: 'suggestNever', + output: ` +type obj = { + message: string & never; +}; + `, + }, + ], + }, + ], + }, + { + code: ` type obj = { - message: Array>; -} - `, - errors: [ - { - column: 26, - line: 3, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: ` + message: string & Array; +}; + `, + errors: [ + { + column: 27, + line: 3, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: ` type obj = { - message: Array; -} - `, - errors: [ - { - column: 20, - line: 3, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: ` + message: string & Array; +}; + `, + }, + { + messageId: 'suggestNever', + output: ` type obj = { - message: string | any; -} - `, - errors: [ - { - column: 23, - line: 3, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: ` + message: string & Array; +}; + `, + }, + ], + }, + ], + }, + { + code: ` type obj = { - message: string | Array; -} - `, - errors: [ - { - column: 29, - line: 3, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: ` + message: string & any[]; +}; + `, + errors: [ + { + column: 21, + line: 3, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: ` type obj = { - message: string | any[]; -} - `, - errors: [ - { - column: 23, - line: 3, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: ` + message: string & unknown[]; +}; + `, + }, + { + messageId: 'suggestNever', + output: ` type obj = { - message: string | Array>; -} - `, - errors: [ - { - column: 35, - line: 3, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: ` + message: string & never[]; +}; + `, + }, + ], + }, + ], + }, + { + code: ` type obj = { - message: string | Array; -} - `, - errors: [ - { - column: 29, - line: 3, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: ` + message: string & Array>; +}; + `, + errors: [ + { + column: 33, + line: 3, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: ` type obj = { - message: string & any; -} - `, - errors: [ - { - column: 23, - line: 3, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: ` + message: string & Array>; +}; + `, + }, + { + messageId: 'suggestNever', + output: ` type obj = { - message: string & Array; -} - `, - errors: [ - { - column: 29, - line: 3, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: ` + message: string & Array>; +}; + `, + }, + ], + }, + ], + }, + { + code: ` type obj = { - message: string & any[]; -} - `, - errors: [ - { - column: 23, - line: 3, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: ` + message: string & Array; +}; + `, + errors: [ + { + column: 27, + line: 3, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: ` type obj = { - message: string & Array>; -} - `, - errors: [ - { - column: 35, - line: 3, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: ` + message: string & Array; +}; + `, + }, + { + messageId: 'suggestNever', + output: ` type obj = { - message: string & Array; -} - `, - errors: [ - { - column: 29, - line: 3, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: 'class Foo extends Bar {}', - errors: [ - { - column: 15, - line: 1, - messageId: 'unexpectedAny', - suggestions: [ - { - messageId: 'suggestUnknown', - output: 'class Foo extends Bar {}', - }, - { - messageId: 'suggestNever', - output: 'class Foo extends Bar {}', - }, - ], - }, - { - column: 32, - line: 1, - messageId: 'unexpectedAny', - suggestions: [ - { - messageId: 'suggestUnknown', - output: 'class Foo extends Bar {}', - }, - { - messageId: 'suggestNever', - output: 'class Foo extends Bar {}', - }, - ], - }, - ], - }, - { - code: 'abstract class Foo extends Bar {}', - errors: [ - { - column: 24, - line: 1, - messageId: 'unexpectedAny', - suggestions: [ - { - messageId: 'suggestUnknown', - output: 'abstract class Foo extends Bar {}', - }, - { - messageId: 'suggestNever', - output: 'abstract class Foo extends Bar {}', - }, - ], - }, - { - column: 41, - line: 1, - messageId: 'unexpectedAny', - suggestions: [ - { - messageId: 'suggestUnknown', - output: 'abstract class Foo extends Bar {}', - }, - { - messageId: 'suggestNever', - output: 'abstract class Foo extends Bar {}', - }, - ], - }, - ], - }, - { - code: 'abstract class Foo implements Bar, Baz {}', - errors: [ - { - column: 24, - line: 1, - messageId: 'unexpectedAny', - suggestions: [ - { - messageId: 'suggestUnknown', - output: - 'abstract class Foo implements Bar, Baz {}', - }, - { - messageId: 'suggestNever', - output: - 'abstract class Foo implements Bar, Baz {}', - }, - ], - }, - { - column: 44, - line: 1, - messageId: 'unexpectedAny', - suggestions: [ - { - messageId: 'suggestUnknown', - output: - 'abstract class Foo implements Bar, Baz {}', - }, - { - messageId: 'suggestNever', - output: - 'abstract class Foo implements Bar, Baz {}', - }, - ], - }, - { - column: 54, - line: 1, - messageId: 'unexpectedAny', - suggestions: [ - { - messageId: 'suggestUnknown', - output: - 'abstract class Foo implements Bar, Baz {}', - }, - { - messageId: 'suggestNever', - output: - 'abstract class Foo implements Bar, Baz {}', - }, - ], - }, - ], - }, - { - code: 'new Foo()', - errors: [ - { - column: 9, - line: 1, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: 'Foo()', - errors: [ - { - column: 5, - line: 1, - messageId: 'unexpectedAny', - }, - ], - }, - { - // https://github.com/typescript-eslint/typescript-eslint/issues/64 - code: ` + message: string & Array; +}; + `, + }, + ], + }, + ], + }, + { + code: 'class Foo extends Bar {}', + errors: [ + { + column: 15, + line: 1, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: 'class Foo extends Bar {}', + }, + { + messageId: 'suggestNever', + output: 'class Foo extends Bar {}', + }, + ], + }, + { + column: 32, + line: 1, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: 'class Foo extends Bar {}', + }, + { + messageId: 'suggestNever', + output: 'class Foo extends Bar {}', + }, + ], + }, + ], + }, + { + code: 'abstract class Foo extends Bar {}', + errors: [ + { + column: 24, + line: 1, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: 'abstract class Foo extends Bar {}', + }, + { + messageId: 'suggestNever', + output: 'abstract class Foo extends Bar {}', + }, + ], + }, + { + column: 41, + line: 1, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: 'abstract class Foo extends Bar {}', + }, + { + messageId: 'suggestNever', + output: 'abstract class Foo extends Bar {}', + }, + ], + }, + ], + }, + { + code: 'abstract class Foo implements Bar, Baz {}', + errors: [ + { + column: 24, + line: 1, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: + 'abstract class Foo implements Bar, Baz {}', + }, + { + messageId: 'suggestNever', + output: + 'abstract class Foo implements Bar, Baz {}', + }, + ], + }, + { + column: 44, + line: 1, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: + 'abstract class Foo implements Bar, Baz {}', + }, + { + messageId: 'suggestNever', + output: + 'abstract class Foo implements Bar, Baz {}', + }, + ], + }, + { + column: 54, + line: 1, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: + 'abstract class Foo implements Bar, Baz {}', + }, + { + messageId: 'suggestNever', + output: + 'abstract class Foo implements Bar, Baz {}', + }, + ], + }, + ], + }, + { + code: 'new Foo();', + errors: [ + { + column: 9, + line: 1, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: 'new Foo();', + }, + { + messageId: 'suggestNever', + output: 'new Foo();', + }, + ], + }, + ], + }, + { + code: 'Foo();', + errors: [ + { + column: 5, + line: 1, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: 'Foo();', + }, + { + messageId: 'suggestNever', + output: 'Foo();', + }, + ], + }, + ], + }, + { + // https://github.com/typescript-eslint/typescript-eslint/issues/64 + code: ` function test>() {} const test = >() => {}; `, - errors: [ - { - column: 33, - line: 2, - messageId: 'unexpectedAny', - suggestions: [ - { - messageId: 'suggestUnknown', - output: ` + errors: [ + { + column: 33, + line: 2, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: ` function test>() {} const test = >() => {}; `, - }, - { - messageId: 'suggestNever', - output: ` + }, + { + messageId: 'suggestNever', + output: ` function test>() {} const test = >() => {}; `, - }, - ], - }, - { - column: 33, - line: 3, - messageId: 'unexpectedAny', - suggestions: [ - { - messageId: 'suggestUnknown', - output: ` + }, + ], + }, + { + column: 33, + line: 3, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: ` function test>() {} const test = >() => {}; `, - }, - { - messageId: 'suggestNever', - output: ` + }, + { + messageId: 'suggestNever', + output: ` function test>() {} const test = >() => {}; `, - }, - ], - }, - ], - }, - { - // https://github.com/eslint/typescript-eslint-parser/issues/397 - code: ` + }, + ], + }, + ], + }, + { + // https://github.com/eslint/typescript-eslint-parser/issues/397 + code: ` function foo(a: number, ...rest: any[]): void { return; } `, - errors: [ - { - column: 42, - line: 2, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: 'type Any = any;', - errors: [ - { - column: 12, - line: 1, - messageId: 'unexpectedAny', - }, - ], - options: [{ ignoreRestArgs: true }], - }, - { - code: 'function foo5(...args: any) {}', - errors: [ - { - column: 24, - line: 1, - messageId: 'unexpectedAny', - }, - ], - options: [{ ignoreRestArgs: true }], - }, - { - code: 'const bar5 = function (...args: any) {}', - errors: [ - { - column: 33, - line: 1, - messageId: 'unexpectedAny', - }, - ], - options: [{ ignoreRestArgs: true }], - }, - { - code: 'const baz5 = (...args: any) => {}', - errors: [ - { - column: 24, - line: 1, - messageId: 'unexpectedAny', - }, - ], - options: [{ ignoreRestArgs: true }], - }, - { - code: 'interface Qux5 { (...args: any): void; }', - errors: [ - { - column: 28, - line: 1, - messageId: 'unexpectedAny', - }, - ], - options: [{ ignoreRestArgs: true }], - }, - { - code: 'function quux5(fn: (...args: any) => void): void {}', - errors: [ - { - column: 30, - line: 1, - messageId: 'unexpectedAny', - }, - ], - options: [{ ignoreRestArgs: true }], - }, - { - code: 'function quuz5(): ((...args: any) => void) {}', - errors: [ - { - column: 30, - line: 1, - messageId: 'unexpectedAny', - }, - ], - options: [{ ignoreRestArgs: true }], - }, - { - code: 'type Fred5 = (...args: any) => void;', - errors: [ - { - column: 24, - line: 1, - messageId: 'unexpectedAny', - }, - ], - options: [{ ignoreRestArgs: true }], - }, - { - code: 'type Corge5 = new (...args: any) => void;', - errors: [ - { - column: 29, - line: 1, - messageId: 'unexpectedAny', - }, - ], - options: [{ ignoreRestArgs: true }], - }, - { - code: 'interface Grault5 { new (...args: any): void; }', - errors: [ - { - column: 35, - line: 1, - messageId: 'unexpectedAny', - }, - ], - options: [{ ignoreRestArgs: true }], - }, - { - code: 'interface Garply5 { f(...args: any): void; }', - errors: [ - { - column: 32, - line: 1, - messageId: 'unexpectedAny', - }, - ], - options: [{ ignoreRestArgs: true }], - }, - { - code: 'declare function waldo5(...args: any): void;', - errors: [ - { - column: 34, - line: 1, - messageId: 'unexpectedAny', - }, - ], - options: [{ ignoreRestArgs: true }], - }, - ] as RuleInvalidTestCase[] - ).flatMap(testCase => { - const suggestions = (code: string): RuleSuggestionOutput[] => [ - { - messageId: 'suggestUnknown', - output: code.replace(/any/, 'unknown'), - }, - { - messageId: 'suggestNever', - output: code.replace(/any/, 'never'), - }, - ]; - const code = `// fixToUnknown: true\n${testCase.code}`; - return [ - { - ...testCase, - errors: testCase.errors.map(e => ({ - ...e, - suggestions: e.suggestions ?? suggestions(testCase.code), - })), - }, - { - code, - errors: testCase.errors.map(err => { - if (err.line == null) { - return err; - } - - return { - ...err, - line: err.line + 1, - suggestions: - err.suggestions?.map( - (s): RuleSuggestionOutput => ({ - ...s, - output: `// fixToUnknown: true\n${s.output}`, - }), - ) ?? suggestions(code), - }; - }), - options: [{ ...testCase.options?.[0], fixToUnknown: true }], - output: code.replaceAll('any', 'unknown'), - }, - ]; - }), + errors: [ + { + column: 42, + line: 2, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: ` + function foo(a: number, ...rest: unknown[]): void { + return; + } + `, + }, + { + messageId: 'suggestNever', + output: ` + function foo(a: number, ...rest: never[]): void { + return; + } + `, + }, + ], + }, + ], + }, + { + code: 'type Any = any;', + errors: [ + { + column: 12, + line: 1, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: 'type Any = unknown;', + }, + { + messageId: 'suggestNever', + output: 'type Any = never;', + }, + ], + }, + ], + options: [{ ignoreRestArgs: true }], + }, + { + code: 'function foo5(...args: any) {}', + errors: [ + { + column: 24, + line: 1, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: 'function foo5(...args: unknown) {}', + }, + { + messageId: 'suggestNever', + output: 'function foo5(...args: never) {}', + }, + ], + }, + ], + options: [{ ignoreRestArgs: true }], + }, + { + code: 'const bar5 = function (...args: any) {};', + errors: [ + { + column: 33, + line: 1, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: 'const bar5 = function (...args: unknown) {};', + }, + { + messageId: 'suggestNever', + output: 'const bar5 = function (...args: never) {};', + }, + ], + }, + ], + options: [{ ignoreRestArgs: true }], + }, + { + code: 'const baz5 = (...args: any) => {};', + errors: [ + { + column: 24, + line: 1, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: 'const baz5 = (...args: unknown) => {};', + }, + { + messageId: 'suggestNever', + output: 'const baz5 = (...args: never) => {};', + }, + ], + }, + ], + options: [{ ignoreRestArgs: true }], + }, + { + code: ` +interface Qux5 { + (...args: any): void; +} + `, + errors: [ + { + column: 13, + line: 3, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: ` +interface Qux5 { + (...args: unknown): void; +} + `, + }, + { + messageId: 'suggestNever', + output: ` +interface Qux5 { + (...args: never): void; +} + `, + }, + ], + }, + ], + options: [{ ignoreRestArgs: true }], + }, + { + code: 'function quux5(fn: (...args: any) => void): void {}', + errors: [ + { + column: 30, + line: 1, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: 'function quux5(fn: (...args: unknown) => void): void {}', + }, + { + messageId: 'suggestNever', + output: 'function quux5(fn: (...args: never) => void): void {}', + }, + ], + }, + ], + options: [{ ignoreRestArgs: true }], + }, + { + code: 'function quuz5(): (...args: any) => void {}', + errors: [ + { + column: 29, + line: 1, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: 'function quuz5(): (...args: unknown) => void {}', + }, + { + messageId: 'suggestNever', + output: 'function quuz5(): (...args: never) => void {}', + }, + ], + }, + ], + }, + { + code: 'type Fred5 = (...args: any) => void;', + errors: [ + { + column: 24, + line: 1, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: 'type Fred5 = (...args: unknown) => void;', + }, + { + messageId: 'suggestNever', + output: 'type Fred5 = (...args: never) => void;', + }, + ], + }, + ], + options: [{ ignoreRestArgs: true }], + }, + { + code: 'type Corge5 = new (...args: any) => void;', + errors: [ + { + column: 29, + line: 1, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: 'type Corge5 = new (...args: unknown) => void;', + }, + { + messageId: 'suggestNever', + output: 'type Corge5 = new (...args: never) => void;', + }, + ], + }, + ], + options: [{ ignoreRestArgs: true }], + }, + { + code: ` +interface Grault5 { + new (...args: any): void; +} + `, + errors: [ + { + column: 17, + line: 3, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: ` +interface Grault5 { + new (...args: unknown): void; +} + `, + }, + { + messageId: 'suggestNever', + output: ` +interface Grault5 { + new (...args: never): void; +} + `, + }, + ], + }, + ], + options: [{ ignoreRestArgs: true }], + }, + { + code: ` +interface Garply5 { + f(...args: any): void; +} + `, + errors: [ + { + column: 14, + line: 3, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: ` +interface Garply5 { + f(...args: unknown): void; +} + `, + }, + { + messageId: 'suggestNever', + output: ` +interface Garply5 { + f(...args: never): void; +} + `, + }, + ], + }, + ], + options: [{ ignoreRestArgs: true }], + }, + { + code: 'declare function waldo5(...args: any): void;', + errors: [ + { + column: 34, + line: 1, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: 'declare function waldo5(...args: unknown): void;', + }, + { + messageId: 'suggestNever', + output: 'declare function waldo5(...args: never): void;', + }, + ], + }, + ], + options: [{ ignoreRestArgs: true }], + }, + { + code: 'type Keys = keyof any;', + errors: [ + { + column: 19, + line: 1, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestPropertyKey', + output: 'type Keys = PropertyKey;', + }, + ], + }, + ], + }, + { + code: ` +const integer = < + TKey extends keyof any, + TTarget extends { [K in TKey]: number }, +>( + target: TTarget, + key: TKey, +) => { + /* ... */ +}; + `, + errors: [ + { + column: 22, + line: 3, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestPropertyKey', + output: ` +const integer = < + TKey extends PropertyKey, + TTarget extends { [K in TKey]: number }, +>( + target: TTarget, + key: TKey, +) => { + /* ... */ +}; + `, + }, + ], + }, + ], + }, + { + code: '// fixToUnknown: true\ntype Keys = keyof any;', + errors: [ + { + column: 19, + line: 2, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestPropertyKey', + output: '// fixToUnknown: true\ntype Keys = PropertyKey;', + }, + ], + }, + ], + options: [{ fixToUnknown: true }], + output: '// fixToUnknown: true\ntype Keys = PropertyKey;', + }, + { + code: ` +// fixToUnknown: true +const integer = < + TKey extends keyof any, + TTarget extends { [K in TKey]: number }, +>( + target: TTarget, + key: TKey, +) => { + /* ... */ +}; + `, + errors: [ + { + column: 22, + line: 4, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestPropertyKey', + output: ` +// fixToUnknown: true +const integer = < + TKey extends PropertyKey, + TTarget extends { [K in TKey]: number }, +>( + target: TTarget, + key: TKey, +) => { + /* ... */ +}; + `, + }, + ], + }, + ], + options: [{ fixToUnknown: true }], + output: ` +// fixToUnknown: true +const integer = < + TKey extends PropertyKey, + TTarget extends { [K in TKey]: number }, +>( + target: TTarget, + key: TKey, +) => { + /* ... */ +}; + `, + }, + { + code: ` +// fixToUnknown: true +const number: any = 1; + `, + errors: [ + { + column: 15, + line: 3, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: ` +// fixToUnknown: true +const number: unknown = 1; + `, + }, + { + messageId: 'suggestNever', + output: ` +// fixToUnknown: true +const number: never = 1; + `, + }, + ], + }, + ], + options: [{ fixToUnknown: true }], + output: ` +// fixToUnknown: true +const number: unknown = 1; + `, + }, + ], });