diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index d5163109b1e..a6e9a3c60a2 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -145,7 +145,7 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e | [`@typescript-eslint/no-array-constructor`](./docs/rules/no-array-constructor.md) | Disallow generic `Array` constructors | :heavy_check_mark: | :wrench: | | | [`@typescript-eslint/no-empty-function`](./docs/rules/no-empty-function.md) | Disallow empty functions | | | | | [`@typescript-eslint/no-empty-interface`](./docs/rules/no-empty-interface.md) | Disallow the declaration of empty interfaces | :heavy_check_mark: | | | -| [`@typescript-eslint/no-explicit-any`](./docs/rules/no-explicit-any.md) | Disallow usage of the `any` type | :heavy_check_mark: | | | +| [`@typescript-eslint/no-explicit-any`](./docs/rules/no-explicit-any.md) | Disallow usage of the `any` type | :heavy_check_mark: | :wrench: | | | [`@typescript-eslint/no-extra-parens`](./docs/rules/no-extra-parens.md) | Disallow unnecessary parentheses | | :wrench: | | | [`@typescript-eslint/no-extraneous-class`](./docs/rules/no-extraneous-class.md) | Forbids the use of classes as namespaces | | | | | [`@typescript-eslint/no-floating-promises`](./docs/rules/no-floating-promises.md) | Requires Promise-like values to be handled appropriately. | | | :thought_balloon: | diff --git a/packages/eslint-plugin/docs/rules/no-explicit-any.md b/packages/eslint-plugin/docs/rules/no-explicit-any.md index 3e3a6391a6f..ccec9e95231 100644 --- a/packages/eslint-plugin/docs/rules/no-explicit-any.md +++ b/packages/eslint-plugin/docs/rules/no-explicit-any.md @@ -87,6 +87,24 @@ function greet(param: Array): string {} function greet(param: Array): Array {} ``` +## Options + +The rule accepts an options object with the following properties: + +```ts +type Options = { + // if true, auto-fixing will be made available in which the "any" type is converted to an "unknown" type + fixToUnknown: boolean; + // specify if arrays from the rest operator are considered okay + ignoreRestArgs: boolean; +}; + +const defaults = { + fixToUnknown: false, + ignoreRestArgs: false, +}; +``` + ### ignoreRestArgs A boolean to specify if arrays from the rest operator are considered okay. `false` by default. diff --git a/packages/eslint-plugin/src/rules/no-explicit-any.ts b/packages/eslint-plugin/src/rules/no-explicit-any.ts index c27b4ab5992..f5a55593361 100644 --- a/packages/eslint-plugin/src/rules/no-explicit-any.ts +++ b/packages/eslint-plugin/src/rules/no-explicit-any.ts @@ -3,8 +3,17 @@ import { AST_NODE_TYPES, } from '@typescript-eslint/experimental-utils'; import * as util from '../util'; +import { TSESLint } from '@typescript-eslint/experimental-utils'; -export default util.createRule({ +export type Options = [ + { + fixToUnknown?: boolean; + ignoreRestArgs?: boolean; + } +]; +export type MessageIds = 'unexpectedAny'; + +export default util.createRule({ name: 'no-explicit-any', meta: { type: 'suggestion', @@ -13,6 +22,7 @@ export default util.createRule({ category: 'Best Practices', recommended: 'warn', }, + fixable: 'code', messages: { unexpectedAny: 'Unexpected any. Specify a different type.', }, @@ -21,6 +31,9 @@ export default util.createRule({ type: 'object', additionalProperties: false, properties: { + fixToUnknown: { + type: 'boolean', + }, ignoreRestArgs: { type: 'boolean', }, @@ -30,10 +43,11 @@ export default util.createRule({ }, defaultOptions: [ { + fixToUnknown: false, ignoreRestArgs: false, }, ], - create(context, [{ ignoreRestArgs }]) { + create(context, [{ ignoreRestArgs, fixToUnknown }]) { /** * Checks if the node is an arrow function, function declaration or function expression * @param node the node to be validated. @@ -155,9 +169,17 @@ export default util.createRule({ if (ignoreRestArgs && isNodeDescendantOfRestElementInFunction(node)) { return; } + + let fix: TSESLint.ReportFixFunction | null = null; + + if (fixToUnknown) { + fix = fixer => fixer.replaceText(node, 'unknown'); + } + context.report({ node, messageId: 'unexpectedAny', + fix, }); }, }; 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 cf596ffecbc..b27cc8111bc 100644 --- a/packages/eslint-plugin/tests/rules/no-explicit-any.test.ts +++ b/packages/eslint-plugin/tests/rules/no-explicit-any.test.ts @@ -1,5 +1,8 @@ -import rule from '../../src/rules/no-explicit-any'; +import rule, { MessageIds, Options } from '../../src/rules/no-explicit-any'; import { RuleTester } from '../RuleTester'; +import { TSESLint } from '@typescript-eslint/experimental-utils'; + +type InvalidTestCase = TSESLint.InvalidTestCase; const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', @@ -187,7 +190,7 @@ type obj = { options: [{ ignoreRestArgs: true }], }, ], - invalid: [ + invalid: ([ { code: 'const number: any = 1', errors: [ @@ -784,5 +787,23 @@ type obj = { }, ], }, - ], + ] as InvalidTestCase[]).reduce((acc, testCase) => { + acc.push(testCase); + const options = testCase.options || []; + const code = `// fixToUnknown: true\n${testCase.code}`; + acc.push({ + code, + output: code.replace(/any/g, 'unknown'), + options: [{ ...options[0], fixToUnknown: true }], + errors: testCase.errors.map(err => { + if (err.line === undefined) { + return err; + } + + return { ...err, line: err.line + 1 }; + }), + }); + + return acc; + }, []), });