From e6f8a168f5a24befc48e544de6802f126a1a83d2 Mon Sep 17 00:00:00 2001 From: dbarabashh Date: Sat, 5 Apr 2025 19:05:06 +0200 Subject: [PATCH 1/5] propertykey for keyof any implementation --- .../src/rules/no-explicit-any.ts | 92 +++++++++++++++---- .../tests/rules/no-explicit-any.test.ts | 60 ++++++++++++ 2 files changed, 135 insertions(+), 17 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-explicit-any.ts b/packages/eslint-plugin/src/rules/no-explicit-any.ts index 1571617d8242..d50f44c89288 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,36 +176,88 @@ 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.Node): 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.Node) { + return (fixer: TSESLint.RuleFixer) => { + if (node.parent && node.parent.type === AST_NODE_TYPES.TSTypeOperator) { + return fixer.replaceText(node.parent, 'PropertyKey'); + } + return fixer.replaceText(node, 'unknown'); + }; + } + + /** + * Creates a fixer that replaces any with unknown + * @param node the node to be fixed. + * @returns a function that will fix the node. + * @private + */ + function createUnknownFixer(node: TSESTree.Node) { + return (fixer: TSESLint.RuleFixer): TSESLint.RuleFix => { + return fixer.replaceText(node, 'unknown'); + }; + } + return { TSAnyKeyword(node): void { + const isKeyofAny = isNodeWithinKeyofAny(node); + if (ignoreRestArgs && isNodeDescendantOfRestElementInFunction(node)) { return; } + const propertyKeySuggestion: TSESLint.SuggestionReportDescriptor = + { + messageId: 'suggestPropertyKey', + fix: createPropertyKeyFixer(node), + }; + + const unknownSuggestion: TSESLint.SuggestionReportDescriptor = + { + messageId: 'suggestUnknown', + fix: createUnknownFixer(node), + }; + + const neverSuggestion: TSESLint.SuggestionReportDescriptor = + { + messageId: 'suggestNever', + fix(fixer: TSESLint.RuleFixer): TSESLint.RuleFix { + return fixer.replaceText(node, 'never'); + }, + }; + const fixOrSuggest: { fix: TSESLint.ReportFixFunction | null; 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 + ? [propertyKeySuggestion] + : [unknownSuggestion, neverSuggestion], }; if (fixToUnknown) { - fixOrSuggest.fix = (fixer): TSESLint.RuleFix => - fixer.replaceText(node, 'unknown'); + fixOrSuggest.fix = isKeyofAny + ? createPropertyKeyFixer(node) + : createUnknownFixer(node); } 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..19ca529dd492 100644 --- a/packages/eslint-plugin/tests/rules/no-explicit-any.test.ts +++ b/packages/eslint-plugin/tests/rules/no-explicit-any.test.ts @@ -1199,8 +1199,68 @@ const test = >() => {}; ], 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 = (target: TTarget, key: TKey) => { /* ... */ };', + errors: [ + { + column: 37, + line: 1, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestPropertyKey', + output: + 'const integer = (target: TTarget, key: TKey) => { /* ... */ };', + }, + ], + }, + ], + }, ] as RuleInvalidTestCase[] ).flatMap(testCase => { + if (testCase.code.includes('keyof any')) { + return [ + testCase, + { + code: `// fixToUnknown: true\n${testCase.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}`, + }), + ), + }; + }), + options: [{ ...testCase.options?.[0], fixToUnknown: true }], + output: `// fixToUnknown: true\n${testCase.code.replaceAll('keyof any', 'PropertyKey')}`, + }, + ]; + } + const suggestions = (code: string): RuleSuggestionOutput[] => [ { messageId: 'suggestUnknown', From 620d613912a03276a10a9539f38409245d477054 Mon Sep 17 00:00:00 2001 From: dbarabashh Date: Mon, 7 Apr 2025 17:41:17 +0200 Subject: [PATCH 2/5] simplify property key fixer --- packages/eslint-plugin/src/rules/no-explicit-any.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-explicit-any.ts b/packages/eslint-plugin/src/rules/no-explicit-any.ts index d50f44c89288..90eedd2f18f2 100644 --- a/packages/eslint-plugin/src/rules/no-explicit-any.ts +++ b/packages/eslint-plugin/src/rules/no-explicit-any.ts @@ -197,10 +197,10 @@ export default createRule({ */ function createPropertyKeyFixer(node: TSESTree.Node) { return (fixer: TSESLint.RuleFixer) => { - if (node.parent && node.parent.type === AST_NODE_TYPES.TSTypeOperator) { - return fixer.replaceText(node.parent, 'PropertyKey'); - } - return fixer.replaceText(node, 'unknown'); + return fixer.replaceText( + node.parent as TSESTree.TSTypeOperator, + 'PropertyKey', + ); }; } From 1ddd122e4a59338720134d538a6f5dce5f22dfc7 Mon Sep 17 00:00:00 2001 From: dbarabashh Date: Tue, 8 Apr 2025 22:08:31 +0200 Subject: [PATCH 3/5] updated tests and refactored code --- .../src/rules/no-explicit-any.ts | 23 +- .../tests/rules/no-explicit-any.test.ts | 2577 +++++++++++------ 2 files changed, 1687 insertions(+), 913 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-explicit-any.ts b/packages/eslint-plugin/src/rules/no-explicit-any.ts index 90eedd2f18f2..903186e9c4d3 100644 --- a/packages/eslint-plugin/src/rules/no-explicit-any.ts +++ b/packages/eslint-plugin/src/rules/no-explicit-any.ts @@ -195,24 +195,9 @@ export default createRule({ * @returns a function that will fix the node. * @private */ - function createPropertyKeyFixer(node: TSESTree.Node) { + function createPropertyKeyFixer(node: TSESTree.TSAnyKeyword) { return (fixer: TSESLint.RuleFixer) => { - return fixer.replaceText( - node.parent as TSESTree.TSTypeOperator, - 'PropertyKey', - ); - }; - } - - /** - * Creates a fixer that replaces any with unknown - * @param node the node to be fixed. - * @returns a function that will fix the node. - * @private - */ - function createUnknownFixer(node: TSESTree.Node) { - return (fixer: TSESLint.RuleFixer): TSESLint.RuleFix => { - return fixer.replaceText(node, 'unknown'); + return fixer.replaceText(node.parent, 'PropertyKey'); }; } @@ -233,7 +218,7 @@ export default createRule({ const unknownSuggestion: TSESLint.SuggestionReportDescriptor = { messageId: 'suggestUnknown', - fix: createUnknownFixer(node), + fix: fixer => fixer.replaceText(node, 'unknown'), }; const neverSuggestion: TSESLint.SuggestionReportDescriptor = @@ -257,7 +242,7 @@ export default createRule({ if (fixToUnknown) { fixOrSuggest.fix = isKeyofAny ? createPropertyKeyFixer(node) - : createUnknownFixer(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 19ca529dd492..6d82064af257 100644 --- a/packages/eslint-plugin/tests/rules/no-explicit-any.test.ts +++ b/packages/eslint-plugin/tests/rules/no-explicit-any.test.ts @@ -1,16 +1,16 @@ -import type { - InvalidTestCase, - SuggestionOutput, -} from '@typescript-eslint/rule-tester'; +// 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 type { MessageIds, Options } from '../../src/rules/no-explicit-any'; import rule from '../../src/rules/no-explicit-any'; -type RuleInvalidTestCase = InvalidTestCase; -type RuleSuggestionOutput = SuggestionOutput; +// type RuleInvalidTestCase = InvalidTestCase; +// type RuleSuggestionOutput = SuggestionOutput; const ruleTester = new RuleTester(); @@ -372,936 +372,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 { - constructor(param: Array) {} -} - `, - errors: [ - { - column: 30, - line: 3, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: ` + 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 { + message: never[]; +} + `, + }, + ], + }, + ], + }, + { + code: ` +class Greeter { + message: Array>; +} + `, + errors: [ + { + column: 24, + line: 3, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: ` class Greeter { - message: any; -} - `, - errors: [ - { - column: 14, - 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: ` 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: never; +} + `, + }, + ], + }, + ], + }, + { + code: ` interface Greeter { - message: Array; -} - `, - errors: [ - { - column: 20, - line: 3, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: ` + message: Array; +} + `, + errors: [ + { + column: 18, + line: 3, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: ` interface Greeter { - message: any[]; -} - `, - errors: [ - { - column: 14, - line: 3, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: ` + message: Array; +} + `, + }, + { + messageId: 'suggestNever', + output: ` interface Greeter { - message: Array>; -} - `, - errors: [ - { - column: 26, - line: 3, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: ` + message: Array; +} + `, + }, + ], + }, + ], + }, + { + code: ` interface Greeter { - message: Array; -} - `, - errors: [ - { - column: 20, - line: 3, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: ` + 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: 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: any; -} - `, - errors: [ - { - column: 14, - line: 3, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: ` + message: string & any; +}; + `, + errors: [ + { + column: 21, + line: 3, + messageId: 'unexpectedAny', + suggestions: [ + { + messageId: 'suggestUnknown', + output: ` type obj = { - message: Array; -} - `, - errors: [ - { - column: 20, - line: 3, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: ` + message: string & unknown; +}; + `, + }, + { + messageId: 'suggestNever', + output: ` type obj = { - message: any[]; -} - `, - errors: [ - { - column: 14, - line: 3, - messageId: 'unexpectedAny', - }, - ], - }, - { - code: ` + 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 }], - }, - { - code: 'type Keys = keyof any;', - errors: [ - { - column: 19, - line: 1, - messageId: 'unexpectedAny', - suggestions: [ - { - messageId: 'suggestPropertyKey', - output: 'type Keys = PropertyKey;', - }, - ], - }, - ], - }, - { - code: 'const integer = (target: TTarget, key: TKey) => { /* ... */ };', - errors: [ - { - column: 37, - line: 1, - messageId: 'unexpectedAny', - suggestions: [ - { - messageId: 'suggestPropertyKey', - output: - 'const integer = (target: TTarget, key: TKey) => { /* ... */ };', - }, - ], - }, - ], - }, - ] as RuleInvalidTestCase[] - ).flatMap(testCase => { - if (testCase.code.includes('keyof any')) { - return [ - testCase, - { - code: `// fixToUnknown: true\n${testCase.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}`, - }), - ), - }; - }), - options: [{ ...testCase.options?.[0], fixToUnknown: true }], - output: `// fixToUnknown: true\n${testCase.code.replaceAll('keyof any', 'PropertyKey')}`, - }, - ]; - } - - 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; + `, + }, + ], }); From 9f778cd2f4eb833cf538e81fdafbe53cfab71dea Mon Sep 17 00:00:00 2001 From: dbarabashh Date: Tue, 8 Apr 2025 22:10:03 +0200 Subject: [PATCH 4/5] removed unused imports --- .../eslint-plugin/tests/rules/no-explicit-any.test.ts | 10 ---------- 1 file changed, 10 deletions(-) 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 6d82064af257..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, { From 2c6c6faaad0a9d00ea8b7028c212dc1676c3c767 Mon Sep 17 00:00:00 2001 From: dbarabashh Date: Wed, 9 Apr 2025 15:44:56 +0200 Subject: [PATCH 5/5] inline suggestion objects and fix type annotation --- .../src/rules/no-explicit-any.ts | 42 ++++++++----------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-explicit-any.ts b/packages/eslint-plugin/src/rules/no-explicit-any.ts index 903186e9c4d3..27079fac7184 100644 --- a/packages/eslint-plugin/src/rules/no-explicit-any.ts +++ b/packages/eslint-plugin/src/rules/no-explicit-any.ts @@ -182,9 +182,9 @@ export default createRule({ * @returns true if the node is within a keyof any expression, false otherwise * @private */ - function isNodeWithinKeyofAny(node: TSESTree.Node): boolean { + function isNodeWithinKeyofAny(node: TSESTree.TSAnyKeyword): boolean { return ( - node.parent?.type === AST_NODE_TYPES.TSTypeOperator && + node.parent.type === AST_NODE_TYPES.TSTypeOperator && node.parent.operator === 'keyof' ); } @@ -209,34 +209,28 @@ export default createRule({ return; } - const propertyKeySuggestion: TSESLint.SuggestionReportDescriptor = - { - messageId: 'suggestPropertyKey', - fix: createPropertyKeyFixer(node), - }; - - const unknownSuggestion: TSESLint.SuggestionReportDescriptor = - { - messageId: 'suggestUnknown', - fix: fixer => fixer.replaceText(node, 'unknown'), - }; - - const neverSuggestion: TSESLint.SuggestionReportDescriptor = - { - messageId: 'suggestNever', - fix(fixer: TSESLint.RuleFixer): TSESLint.RuleFix { - return fixer.replaceText(node, 'never'); - }, - }; - const fixOrSuggest: { fix: TSESLint.ReportFixFunction | null; suggest: TSESLint.ReportSuggestionArray | null; } = { fix: null, suggest: isKeyofAny - ? [propertyKeySuggestion] - : [unknownSuggestion, neverSuggestion], + ? [ + { + messageId: 'suggestPropertyKey', + fix: createPropertyKeyFixer(node), + }, + ] + : [ + { + messageId: 'suggestUnknown', + fix: fixer => fixer.replaceText(node, 'unknown'), + }, + { + messageId: 'suggestNever', + fix: fixer => fixer.replaceText(node, 'never'), + }, + ], }; if (fixToUnknown) {