Skip to content

Commit 753ad75

Browse files
Dilatorilybradzacher
authored andcommitted
feat(eslint-plugin): [no-explicit-any] ignoreRestArgs (typescript-eslint#548)
1 parent c4df4ff commit 753ad75

File tree

3 files changed

+291
-3
lines changed

3 files changed

+291
-3
lines changed

packages/eslint-plugin/docs/rules/no-explicit-any.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,52 @@ function greet(param: Array<string>): string {}
8787
function greet(param: Array<string>): Array<string> {}
8888
```
8989

90+
### ignoreRestArgs
91+
92+
A boolean to specify if arrays from the rest operator are considered okay. `false` by default.
93+
94+
Examples of **incorrect** code for the `{ "ignoreRestArgs": false }` option:
95+
96+
```ts
97+
/*eslint @typescript-eslint/no-explicit-any: ["error", { "ignoreRestArgs": false }]*/
98+
99+
function foo1(...args: any[]): void {}
100+
function foo2(...args: readonly any[]): void {}
101+
function foo3(...args: Array<any>): void {}
102+
function foo4(...args: ReadonlyArray<any>): void {}
103+
104+
const bar1 = (...args: any[]): void {}
105+
const bar2 = (...args: readonly any[]): void {}
106+
const bar3 = (...args: Array<any>): void {}
107+
const bar4 = (...args: ReadonlyArray<any>): void {}
108+
109+
const baz1 = function (...args: any[]) {}
110+
const baz2 = function (...args: readonly any[]) {}
111+
const baz3 = function (...args: Array<any>) {}
112+
const baz4 = function (...args: ReadonlyArray<any>) {}
113+
```
114+
115+
Examples of **correct** code for the `{ "ignoreRestArgs": true }` option:
116+
117+
```ts
118+
/*eslint @typescript-eslint/no-explicit-any: ["error", { "ignoreRestArgs": true }]*/
119+
120+
function foo1(...args: any[]): void {}
121+
function foo2(...args: readonly any[]): void {}
122+
function foo3(...args: Array<any>): void {}
123+
function foo4(...args: ReadonlyArray<any>): void {}
124+
125+
const bar1 = (...args: any[]): void {}
126+
const bar2 = (...args: readonly any[]): void {}
127+
const bar3 = (...args: Array<any>): void {}
128+
const bar4 = (...args: ReadonlyArray<any>): void {}
129+
130+
const baz1 = function (...args: any[]) {}
131+
const baz2 = function (...args: readonly any[]) {}
132+
const baz3 = function (...args: Array<any>) {}
133+
const baz4 = function (...args: ReadonlyArray<any>) {}
134+
```
135+
90136
## When Not To Use It
91137

92138
If an unknown type or a library without typings is used

packages/eslint-plugin/src/rules/no-explicit-any.ts

Lines changed: 140 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import {
2+
TSESTree,
3+
AST_NODE_TYPES,
4+
} from '@typescript-eslint/experimental-utils';
15
import * as util from '../util';
26

37
export default util.createRule({
@@ -12,12 +16,145 @@ export default util.createRule({
1216
messages: {
1317
unexpectedAny: 'Unexpected any. Specify a different type.',
1418
},
15-
schema: [],
19+
schema: [
20+
{
21+
type: 'object',
22+
additionalProperties: false,
23+
properties: {
24+
ignoreRestArgs: {
25+
type: 'boolean',
26+
},
27+
},
28+
},
29+
],
1630
},
17-
defaultOptions: [],
18-
create(context) {
31+
defaultOptions: [
32+
{
33+
ignoreRestArgs: false,
34+
},
35+
],
36+
create(context, [{ ignoreRestArgs }]) {
37+
/**
38+
* Checks if the node is an arrow function, function declaration or function expression
39+
* @param node the node to be validated.
40+
* @returns true if the node is an arrow function, function declaration or function expression
41+
* @private
42+
*/
43+
function isNodeValidFunction(node: TSESTree.Node): boolean {
44+
return [
45+
AST_NODE_TYPES.ArrowFunctionExpression,
46+
AST_NODE_TYPES.FunctionDeclaration,
47+
AST_NODE_TYPES.FunctionExpression,
48+
].includes(node.type);
49+
}
50+
51+
/**
52+
* Checks if the node is a rest element child node of a function
53+
* @param node the node to be validated.
54+
* @returns true if the node is a rest element child node of a function
55+
* @private
56+
*/
57+
function isNodeRestElementInFunction(node: TSESTree.Node): boolean {
58+
return (
59+
node.type === AST_NODE_TYPES.RestElement &&
60+
typeof node.parent !== 'undefined' &&
61+
isNodeValidFunction(node.parent)
62+
);
63+
}
64+
65+
/**
66+
* Checks if the node is a TSTypeOperator node with a readonly operator
67+
* @param node the node to be validated.
68+
* @returns true if the node is a TSTypeOperator node with a readonly operator
69+
* @private
70+
*/
71+
function isNodeReadonlyTSTypeOperator(node: TSESTree.Node): boolean {
72+
return (
73+
node.type === AST_NODE_TYPES.TSTypeOperator &&
74+
node.operator === 'readonly'
75+
);
76+
}
77+
78+
/**
79+
* Checks if the node is a TSTypeReference node with an Array identifier
80+
* @param node the node to be validated.
81+
* @returns true if the node is a TSTypeReference node with an Array identifier
82+
* @private
83+
*/
84+
function isNodeValidArrayTSTypeReference(node: TSESTree.Node): boolean {
85+
return (
86+
node.type === AST_NODE_TYPES.TSTypeReference &&
87+
typeof node.typeName !== 'undefined' &&
88+
node.typeName.type === AST_NODE_TYPES.Identifier &&
89+
['Array', 'ReadonlyArray'].includes(node.typeName.name)
90+
);
91+
}
92+
93+
/**
94+
* Checks if the node is a valid TSTypeOperator or TSTypeReference node
95+
* @param node the node to be validated.
96+
* @returns true if the node is a valid TSTypeOperator or TSTypeReference node
97+
* @private
98+
*/
99+
function isNodeValidTSType(node: TSESTree.Node): boolean {
100+
return (
101+
isNodeReadonlyTSTypeOperator(node) ||
102+
isNodeValidArrayTSTypeReference(node)
103+
);
104+
}
105+
106+
/**
107+
* Checks if the great grand-parent node is a RestElement node in a function
108+
* @param node the node to be validated.
109+
* @returns true if the great grand-parent node is a RestElement node in a function
110+
* @private
111+
*/
112+
function isGreatGrandparentRestElement(node: TSESTree.Node): boolean {
113+
return (
114+
typeof node.parent !== 'undefined' &&
115+
typeof node.parent.parent !== 'undefined' &&
116+
typeof node.parent.parent.parent !== 'undefined' &&
117+
isNodeRestElementInFunction(node.parent.parent.parent)
118+
);
119+
}
120+
121+
/**
122+
* Checks if the great great grand-parent node is a valid RestElement node in a function
123+
* @param node the node to be validated.
124+
* @returns true if the great great grand-parent node is a valid RestElement node in a function
125+
* @private
126+
*/
127+
function isGreatGreatGrandparentRestElement(node: TSESTree.Node): boolean {
128+
return (
129+
typeof node.parent !== 'undefined' &&
130+
typeof node.parent.parent !== 'undefined' &&
131+
isNodeValidTSType(node.parent.parent) &&
132+
typeof node.parent.parent.parent !== 'undefined' &&
133+
typeof node.parent.parent.parent.parent !== 'undefined' &&
134+
isNodeRestElementInFunction(node.parent.parent.parent.parent)
135+
);
136+
}
137+
138+
/**
139+
* Checks if the great grand-parent or the great great grand-parent node is a RestElement node
140+
* @param node the node to be validated.
141+
* @returns true if the great grand-parent or the great great grand-parent node is a RestElement node
142+
* @private
143+
*/
144+
function isNodeDescendantOfRestElementInFunction(
145+
node: TSESTree.Node,
146+
): boolean {
147+
return (
148+
isGreatGrandparentRestElement(node) ||
149+
isGreatGreatGrandparentRestElement(node)
150+
);
151+
}
152+
19153
return {
20154
TSAnyKeyword(node) {
155+
if (ignoreRestArgs && isNodeDescendantOfRestElementInFunction(node)) {
156+
return;
157+
}
21158
context.report({
22159
node,
23160
messageId: 'unexpectedAny',

packages/eslint-plugin/tests/rules/no-explicit-any.test.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,63 @@ type obj = {
129129
message: string & Array<Array<string>>;
130130
}
131131
`,
132+
// https://github.com/eslint/typescript-eslint-parser/issues/397
133+
{
134+
code: `
135+
function foo(a: number, ...rest: any[]): void {
136+
return;
137+
}
138+
`,
139+
options: [{ ignoreRestArgs: true }],
140+
},
141+
{
142+
code: `function foo1(...args: any[]) {}`,
143+
options: [{ ignoreRestArgs: true }],
144+
},
145+
{
146+
code: `const bar1 = function (...args: any[]) {}`,
147+
options: [{ ignoreRestArgs: true }],
148+
},
149+
{
150+
code: `const baz1 = (...args: any[]) => {}`,
151+
options: [{ ignoreRestArgs: true }],
152+
},
153+
{
154+
code: `function foo2(...args: readonly any[]) {}`,
155+
options: [{ ignoreRestArgs: true }],
156+
},
157+
{
158+
code: `const bar2 = function (...args: readonly any[]) {}`,
159+
options: [{ ignoreRestArgs: true }],
160+
},
161+
{
162+
code: `const baz2 = (...args: readonly any[]) => {}`,
163+
options: [{ ignoreRestArgs: true }],
164+
},
165+
{
166+
code: `function foo3(...args: Array<any>) {}`,
167+
options: [{ ignoreRestArgs: true }],
168+
},
169+
{
170+
code: `const bar3 = function (...args: Array<any>) {}`,
171+
options: [{ ignoreRestArgs: true }],
172+
},
173+
{
174+
code: `const baz3 = (...args: Array<any>) => {}`,
175+
options: [{ ignoreRestArgs: true }],
176+
},
177+
{
178+
code: `function foo4(...args: ReadonlyArray<any>) {}`,
179+
options: [{ ignoreRestArgs: true }],
180+
},
181+
{
182+
code: `const bar4 = function (...args: ReadonlyArray<any>) {}`,
183+
options: [{ ignoreRestArgs: true }],
184+
},
185+
{
186+
code: `const baz4 = (...args: ReadonlyArray<any>) => {}`,
187+
options: [{ ignoreRestArgs: true }],
188+
},
132189
],
133190
invalid: [
134191
{
@@ -679,5 +736,53 @@ type obj = {
679736
},
680737
],
681738
},
739+
{
740+
// https://github.com/eslint/typescript-eslint-parser/issues/397
741+
code: `
742+
function foo(a: number, ...rest: any[]): void {
743+
return;
744+
}
745+
`,
746+
errors: [
747+
{
748+
messageId: 'unexpectedAny',
749+
line: 2,
750+
column: 42,
751+
},
752+
],
753+
},
754+
{
755+
code: `function foo5(...args: any) {}`,
756+
options: [{ ignoreRestArgs: true }],
757+
errors: [
758+
{
759+
messageId: 'unexpectedAny',
760+
line: 1,
761+
column: 24,
762+
},
763+
],
764+
},
765+
{
766+
code: `const bar5 = function (...args: any) {}`,
767+
options: [{ ignoreRestArgs: true }],
768+
errors: [
769+
{
770+
messageId: 'unexpectedAny',
771+
line: 1,
772+
column: 33,
773+
},
774+
],
775+
},
776+
{
777+
code: `const baz5 = (...args: any) => {}`,
778+
options: [{ ignoreRestArgs: true }],
779+
errors: [
780+
{
781+
messageId: 'unexpectedAny',
782+
line: 1,
783+
column: 24,
784+
},
785+
],
786+
},
682787
],
683788
});

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy