Skip to content

Commit

Permalink
feat(eslint-plugin): [no-explicit-any] Add an optional fixer (#609)
Browse files Browse the repository at this point in the history
  • Loading branch information
aggmoulik authored and bradzacher committed Jun 28, 2019
1 parent 76b89a5 commit 606fc70
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 6 deletions.
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;
}, []),
});

0 comments on commit 606fc70

Please sign in to comment.