Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(eslint-plugin): [no-explicit-any] Add an optional fixer #609

Merged
merged 10 commits into from Jun 28, 2019
2 changes: 1 addition & 1 deletion packages/eslint-plugin/README.md
Expand Up @@ -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: |
Expand Down
18 changes: 18 additions & 0 deletions packages/eslint-plugin/docs/rules/no-explicit-any.md
Expand Up @@ -87,6 +87,24 @@ function greet(param: Array<string>): string {}
function greet(param: Array<string>): Array<string> {}
```

## 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.
Expand Down
26 changes: 24 additions & 2 deletions packages/eslint-plugin/src/rules/no-explicit-any.ts
Expand Up @@ -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<Options, MessageIds>({
name: 'no-explicit-any',
meta: {
type: 'suggestion',
Expand All @@ -13,6 +22,7 @@ export default util.createRule({
category: 'Best Practices',
recommended: 'warn',
},
fixable: 'code',
messages: {
unexpectedAny: 'Unexpected any. Specify a different type.',
},
Expand All @@ -21,6 +31,9 @@ export default util.createRule({
type: 'object',
additionalProperties: false,
properties: {
fixToUnknown: {
type: 'boolean',
},
ignoreRestArgs: {
type: 'boolean',
},
Expand All @@ -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.
Expand Down Expand Up @@ -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,
});
},
};
Expand Down
27 changes: 24 additions & 3 deletions 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<MessageIds, Options>;

const ruleTester = new RuleTester({
parser: '@typescript-eslint/parser',
Expand Down Expand Up @@ -187,7 +190,7 @@ type obj = {
options: [{ ignoreRestArgs: true }],
},
],
invalid: [
invalid: ([
{
code: 'const number: any = 1',
errors: [
Expand Down Expand Up @@ -784,5 +787,23 @@ type obj = {
},
],
},
],
] as InvalidTestCase[]).reduce<InvalidTestCase[]>((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;
}, []),
});