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] ignoreRestArgs #548

Merged
merged 4 commits into from Jun 16, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
46 changes: 46 additions & 0 deletions packages/eslint-plugin/docs/rules/no-explicit-any.md
Expand Up @@ -87,6 +87,52 @@ function greet(param: Array<string>): string {}
function greet(param: Array<string>): Array<string> {}
```

### ignoreRestArgs

A boolean to specify if arrays from the rest operator are considered okay. `false` by default.

Examples of **incorrect** code for the `{ "ignoreRestArgs": false }` option:

```ts
/*eslint @typescript-eslint/no-explicit-any: ["error", { "ignoreRestArgs": false }]*/

function foo1(...args: any[]): void {}
function foo2(...args: readonly any[]): void {}
function foo3(...args: Array<any>): void {}
function foo4(...args: ReadonlyArray<any>): void {}

const bar1 = (...args: any[]): void {}
const bar2 = (...args: readonly any[]): void {}
const bar3 = (...args: Array<any>): void {}
const bar4 = (...args: ReadonlyArray<any>): void {}

const baz1 = function (...args: any[]) {}
const baz2 = function (...args: readonly any[]) {}
const baz3 = function (...args: Array<any>) {}
const baz4 = function (...args: ReadonlyArray<any>) {}
```

Examples of **correct** code for the `{ "ignoreRestArgs": true }` option:

```ts
/*eslint @typescript-eslint/no-explicit-any: ["error", { "ignoreRestArgs": true }]*/

function foo1(...args: any[]): void {}
function foo2(...args: readonly any[]): void {}
function foo3(...args: Array<any>): void {}
function foo4(...args: ReadonlyArray<any>): void {}

const bar1 = (...args: any[]): void {}
const bar2 = (...args: readonly any[]): void {}
const bar3 = (...args: Array<any>): void {}
const bar4 = (...args: ReadonlyArray<any>): void {}

const baz1 = function (...args: any[]) {}
const baz2 = function (...args: readonly any[]) {}
const baz3 = function (...args: Array<any>) {}
const baz4 = function (...args: ReadonlyArray<any>) {}
```

## When Not To Use It

If an unknown type or a library without typings is used
Expand Down
143 changes: 140 additions & 3 deletions packages/eslint-plugin/src/rules/no-explicit-any.ts
@@ -1,3 +1,7 @@
import {
TSESTree,
AST_NODE_TYPES,
} from '@typescript-eslint/experimental-utils';
import * as util from '../util';

export default util.createRule({
Expand All @@ -12,12 +16,145 @@ export default util.createRule({
messages: {
unexpectedAny: 'Unexpected any. Specify a different type.',
},
schema: [],
schema: [
{
type: 'object',
additionalProperties: false,
properties: {
ignoreRestArgs: {
type: 'boolean',
},
},
},
],
},
defaultOptions: [],
create(context) {
defaultOptions: [
{
ignoreRestArgs: false,
},
],
create(context, [{ ignoreRestArgs }]) {
/**
* Checks if the node is an arrow function, function declaration or function expression
* @param node the node to be validated.
* @returns true if the node is an arrow function, function declaration or function expression
* @private
*/
function isNodeValidFunction(node: TSESTree.Node): boolean {
return [
AST_NODE_TYPES.ArrowFunctionExpression,
AST_NODE_TYPES.FunctionDeclaration,
AST_NODE_TYPES.FunctionExpression,
].includes(node.type);
}

/**
* Checks if the node is a rest element child node of a function
* @param node the node to be validated.
* @returns true if the node is a rest element child node of a function
* @private
*/
function isNodeRestElementInFunction(node: TSESTree.Node): boolean {
return (
node.type === AST_NODE_TYPES.RestElement &&
typeof node.parent !== 'undefined' &&
isNodeValidFunction(node.parent)
);
}

/**
* Checks if the node is a TSTypeOperator node with a readonly operator
* @param node the node to be validated.
* @returns true if the node is a TSTypeOperator node with a readonly operator
* @private
*/
function isNodeReadonlyTSTypeOperator(node: TSESTree.Node): boolean {
return (
node.type === AST_NODE_TYPES.TSTypeOperator &&
node.operator === 'readonly'
);
}

/**
* Checks if the node is a TSTypeReference node with an Array identifier
* @param node the node to be validated.
* @returns true if the node is a TSTypeReference node with an Array identifier
* @private
*/
function isNodeValidArrayTSTypeReference(node: TSESTree.Node): boolean {
return (
node.type === AST_NODE_TYPES.TSTypeReference &&
typeof node.typeName !== 'undefined' &&
node.typeName.type === AST_NODE_TYPES.Identifier &&
['Array', 'ReadonlyArray'].includes(node.typeName.name)
);
}

/**
* Checks if the node is a valid TSTypeOperator or TSTypeReference node
* @param node the node to be validated.
* @returns true if the node is a valid TSTypeOperator or TSTypeReference node
* @private
*/
function isNodeValidTSType(node: TSESTree.Node): boolean {
return (
isNodeReadonlyTSTypeOperator(node) ||
isNodeValidArrayTSTypeReference(node)
);
}

/**
* Checks if the great grand-parent node is a RestElement node in a function
* @param node the node to be validated.
* @returns true if the great grand-parent node is a RestElement node in a function
* @private
*/
function isGreatGrandparentRestElement(node: TSESTree.Node): boolean {
return (
typeof node.parent !== 'undefined' &&
typeof node.parent.parent !== 'undefined' &&
typeof node.parent.parent.parent !== 'undefined' &&
isNodeRestElementInFunction(node.parent.parent.parent)
);
}

/**
* Checks if the great great grand-parent node is a valid RestElement node in a function
* @param node the node to be validated.
* @returns true if the great great grand-parent node is a valid RestElement node in a function
* @private
*/
function isGreatGreatGrandparentRestElement(node: TSESTree.Node): boolean {
return (
typeof node.parent !== 'undefined' &&
typeof node.parent.parent !== 'undefined' &&
isNodeValidTSType(node.parent.parent) &&
typeof node.parent.parent.parent !== 'undefined' &&
typeof node.parent.parent.parent.parent !== 'undefined' &&
isNodeRestElementInFunction(node.parent.parent.parent.parent)
);
}

/**
* Checks if the great grand-parent or the great great grand-parent node is a RestElement node
* @param node the node to be validated.
* @returns true if the great grand-parent or the great great grand-parent node is a RestElement node
* @private
*/
function isNodeDescendantOfRestElementInFunction(
node: TSESTree.Node,
): boolean {
return (
isGreatGrandparentRestElement(node) ||
isGreatGreatGrandparentRestElement(node)
);
}

return {
TSAnyKeyword(node) {
if (ignoreRestArgs && isNodeDescendantOfRestElementInFunction(node)) {
return;
}
context.report({
node,
messageId: 'unexpectedAny',
Expand Down
105 changes: 105 additions & 0 deletions packages/eslint-plugin/tests/rules/no-explicit-any.test.ts
Expand Up @@ -129,6 +129,63 @@ type obj = {
message: string & Array<Array<string>>;
}
`,
// https://github.com/eslint/typescript-eslint-parser/issues/397
{
code: `
function foo(a: number, ...rest: any[]): void {
return;
}
`,
options: [{ ignoreRestArgs: true }],
},
{
code: `function foo1(...args: any[]) {}`,
options: [{ ignoreRestArgs: true }],
},
{
code: `const bar1 = function (...args: any[]) {}`,
options: [{ ignoreRestArgs: true }],
},
{
code: `const baz1 = (...args: any[]) => {}`,
options: [{ ignoreRestArgs: true }],
},
{
code: `function foo2(...args: readonly any[]) {}`,
options: [{ ignoreRestArgs: true }],
},
{
code: `const bar2 = function (...args: readonly any[]) {}`,
options: [{ ignoreRestArgs: true }],
},
{
code: `const baz2 = (...args: readonly any[]) => {}`,
options: [{ ignoreRestArgs: true }],
},
{
code: `function foo3(...args: Array<any>) {}`,
options: [{ ignoreRestArgs: true }],
},
{
code: `const bar3 = function (...args: Array<any>) {}`,
options: [{ ignoreRestArgs: true }],
},
{
code: `const baz3 = (...args: Array<any>) => {}`,
options: [{ ignoreRestArgs: true }],
},
{
code: `function foo4(...args: ReadonlyArray<any>) {}`,
options: [{ ignoreRestArgs: true }],
},
{
code: `const bar4 = function (...args: ReadonlyArray<any>) {}`,
options: [{ ignoreRestArgs: true }],
},
{
code: `const baz4 = (...args: ReadonlyArray<any>) => {}`,
options: [{ ignoreRestArgs: true }],
},
],
invalid: [
{
Expand Down Expand Up @@ -679,5 +736,53 @@ type obj = {
},
],
},
{
// https://github.com/eslint/typescript-eslint-parser/issues/397
code: `
function foo(a: number, ...rest: any[]): void {
return;
}
`,
errors: [
{
messageId: 'unexpectedAny',
line: 2,
column: 42,
},
],
},
{
code: `function foo5(...args: any) {}`,
options: [{ ignoreRestArgs: true }],
errors: [
{
messageId: 'unexpectedAny',
line: 1,
column: 24,
},
],
},
{
code: `const bar5 = function (...args: any) {}`,
options: [{ ignoreRestArgs: true }],
errors: [
{
messageId: 'unexpectedAny',
line: 1,
column: 33,
},
],
},
{
code: `const baz5 = (...args: any) => {}`,
options: [{ ignoreRestArgs: true }],
errors: [
{
messageId: 'unexpectedAny',
line: 1,
column: 24,
},
],
},
],
});