Skip to content

Commit 91423e4

Browse files
authored
feat(eslint-plugin): add rule no-unsafe-call (#1647)
1 parent cfc3ef1 commit 91423e4

File tree

6 files changed

+203
-0
lines changed

6 files changed

+203
-0
lines changed

Diff for: packages/eslint-plugin/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ Pro Tip: For larger codebases you may want to consider splitting our linting int
132132
| [`@typescript-eslint/no-unnecessary-qualifier`](./docs/rules/no-unnecessary-qualifier.md) | Warns when a namespace qualifier is unnecessary | | :wrench: | :thought_balloon: |
133133
| [`@typescript-eslint/no-unnecessary-type-arguments`](./docs/rules/no-unnecessary-type-arguments.md) | Enforces that type arguments will not be used if not required | | :wrench: | :thought_balloon: |
134134
| [`@typescript-eslint/no-unnecessary-type-assertion`](./docs/rules/no-unnecessary-type-assertion.md) | Warns if a type assertion does not change the type of an expression | :heavy_check_mark: | :wrench: | :thought_balloon: |
135+
| [`@typescript-eslint/no-unsafe-call`](./docs/rules/no-unsafe-call.md) | Disallows calling an any type value | | | :thought_balloon: |
135136
| [`@typescript-eslint/no-unsafe-member-access`](./docs/rules/no-unsafe-member-access.md) | Disallows member access on any typed variables | | | :thought_balloon: |
136137
| [`@typescript-eslint/no-unsafe-return`](./docs/rules/no-unsafe-return.md) | Disallows returning any from a function | | | :thought_balloon: |
137138
| [`@typescript-eslint/no-unused-vars-experimental`](./docs/rules/no-unused-vars-experimental.md) | Disallow unused variables and arguments | | | :thought_balloon: |

Diff for: packages/eslint-plugin/docs/rules/no-unsafe-call.md

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Disallows calling an any type value (`no-unsafe-call`)
2+
3+
Despite your best intentions, the `any` type can sometimes leak into your codebase.
4+
Member access on `any` typed variables is not checked at all by TypeScript, so it creates a potential safety hole, and source of bugs in your codebase.
5+
6+
## Rule Details
7+
8+
This rule disallows calling any variable that is typed as `any`.
9+
10+
Examples of **incorrect** code for this rule:
11+
12+
```ts
13+
declare const anyVar: any;
14+
declare const nestedAny: { prop: any };
15+
16+
anyVar();
17+
anyVar.a.b();
18+
19+
nestedAny.prop();
20+
nestedAny.prop['a']();
21+
22+
new anyVar();
23+
new nestedAny.prop();
24+
```
25+
26+
Examples of **correct** code for this rule:
27+
28+
```ts
29+
declare const properlyTyped: { prop: { a: () => void } };
30+
31+
nestedAny.prop.a();
32+
33+
(() => {})();
34+
35+
new Map();
36+
```
37+
38+
## Related to
39+
40+
- [`no-explicit-any`](./no-explicit-any.md)
41+
- TSLint: [`no-unsafe-any`](https://palantir.github.io/tslint/rules/no-unsafe-any/)

Diff for: packages/eslint-plugin/src/configs/all.json

+1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
"@typescript-eslint/no-unnecessary-qualifier": "error",
6262
"@typescript-eslint/no-unnecessary-type-arguments": "error",
6363
"@typescript-eslint/no-unnecessary-type-assertion": "error",
64+
"@typescript-eslint/no-unsafe-call": "error",
6465
"@typescript-eslint/no-unsafe-member-access": "error",
6566
"@typescript-eslint/no-unsafe-return": "error",
6667
"no-unused-expressions": "off",

Diff for: packages/eslint-plugin/src/rules/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import noUnnecessaryCondition from './no-unnecessary-condition';
5353
import noUnnecessaryQualifier from './no-unnecessary-qualifier';
5454
import noUnnecessaryTypeArguments from './no-unnecessary-type-arguments';
5555
import noUnnecessaryTypeAssertion from './no-unnecessary-type-assertion';
56+
import noUnsafeCall from './no-unsafe-call';
5657
import noUnsafeMemberAccess from './no-unsafe-member-access';
5758
import noUnsafeReturn from './no-unsafe-return';
5859
import noUntypedPublicSignature from './no-untyped-public-signature';
@@ -146,6 +147,7 @@ export default {
146147
'no-unnecessary-qualifier': noUnnecessaryQualifier,
147148
'no-unnecessary-type-arguments': noUnnecessaryTypeArguments,
148149
'no-unnecessary-type-assertion': noUnnecessaryTypeAssertion,
150+
'no-unsafe-call': noUnsafeCall,
149151
'no-unsafe-member-access': noUnsafeMemberAccess,
150152
'no-unsafe-return': noUnsafeReturn,
151153
'no-untyped-public-signature': noUntypedPublicSignature,

Diff for: packages/eslint-plugin/src/rules/no-unsafe-call.ts

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { TSESTree } from '@typescript-eslint/experimental-utils';
2+
import * as util from '../util';
3+
4+
type MessageIds = 'unsafeCall' | 'unsafeNew';
5+
6+
export default util.createRule<[], MessageIds>({
7+
name: 'no-unsafe-call',
8+
meta: {
9+
type: 'problem',
10+
docs: {
11+
description: 'Disallows calling an any type value',
12+
category: 'Possible Errors',
13+
recommended: false,
14+
requiresTypeChecking: true,
15+
},
16+
messages: {
17+
unsafeCall: 'Unsafe call of an any typed value',
18+
unsafeNew: 'Unsafe construction of an any type value',
19+
},
20+
schema: [],
21+
},
22+
defaultOptions: [],
23+
create(context) {
24+
const { program, esTreeNodeToTSNodeMap } = util.getParserServices(context);
25+
const checker = program.getTypeChecker();
26+
27+
function checkCall(
28+
node:
29+
| TSESTree.CallExpression
30+
| TSESTree.OptionalCallExpression
31+
| TSESTree.NewExpression,
32+
reportingNode: TSESTree.Expression = node.callee,
33+
messageId: MessageIds = 'unsafeCall',
34+
): void {
35+
const tsNode = esTreeNodeToTSNodeMap.get(node.callee);
36+
const type = checker.getTypeAtLocation(tsNode);
37+
if (util.isTypeAnyType(type)) {
38+
context.report({
39+
node: reportingNode,
40+
messageId: messageId,
41+
});
42+
}
43+
}
44+
45+
return {
46+
'CallExpression, OptionalCallExpression': checkCall,
47+
NewExpression(node): void {
48+
checkCall(node, node, 'unsafeNew');
49+
},
50+
};
51+
},
52+
});
+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import rule from '../../src/rules/no-unsafe-call';
2+
import {
3+
RuleTester,
4+
batchedSingleLineTests,
5+
getFixturesRootDir,
6+
} from '../RuleTester';
7+
8+
const ruleTester = new RuleTester({
9+
parser: '@typescript-eslint/parser',
10+
parserOptions: {
11+
project: './tsconfig.json',
12+
tsconfigRootDir: getFixturesRootDir(),
13+
},
14+
});
15+
16+
ruleTester.run('no-unsafe-call', rule, {
17+
valid: [
18+
'function foo(x: () => void) { x() }',
19+
'function foo(x?: { a: () => void }) { x?.a() }',
20+
'function foo(x: { a?: () => void }) { x.a?.() }',
21+
'new Map()',
22+
],
23+
invalid: [
24+
...batchedSingleLineTests({
25+
code: `
26+
function foo(x: any) { x() }
27+
function foo(x: any) { x?.() }
28+
function foo(x: any) { x.a.b.c.d.e.f.g() }
29+
function foo(x: any) { x.a.b.c.d.e.f.g?.() }
30+
`,
31+
errors: [
32+
{
33+
messageId: 'unsafeCall',
34+
line: 2,
35+
column: 24,
36+
endColumn: 25,
37+
},
38+
{
39+
messageId: 'unsafeCall',
40+
line: 3,
41+
column: 24,
42+
endColumn: 25,
43+
},
44+
{
45+
messageId: 'unsafeCall',
46+
line: 4,
47+
column: 24,
48+
endColumn: 39,
49+
},
50+
{
51+
messageId: 'unsafeCall',
52+
line: 5,
53+
column: 24,
54+
endColumn: 39,
55+
},
56+
],
57+
}),
58+
...batchedSingleLineTests({
59+
code: `
60+
function foo(x: { a: any }) { x.a() }
61+
function foo(x: { a: any }) { x?.a() }
62+
function foo(x: { a: any }) { x.a?.() }
63+
`,
64+
errors: [
65+
{
66+
messageId: 'unsafeCall',
67+
line: 2,
68+
column: 31,
69+
endColumn: 34,
70+
},
71+
{
72+
messageId: 'unsafeCall',
73+
line: 3,
74+
column: 31,
75+
endColumn: 35,
76+
},
77+
{
78+
messageId: 'unsafeCall',
79+
line: 4,
80+
column: 31,
81+
endColumn: 34,
82+
},
83+
],
84+
}),
85+
...batchedSingleLineTests({
86+
code: `
87+
function foo(x: any) { new x() }
88+
function foo(x: { a: any }) { new x.a() }
89+
`,
90+
errors: [
91+
{
92+
messageId: 'unsafeNew',
93+
line: 2,
94+
column: 24,
95+
endColumn: 31,
96+
},
97+
{
98+
messageId: 'unsafeNew',
99+
line: 3,
100+
column: 31,
101+
endColumn: 40,
102+
},
103+
],
104+
}),
105+
],
106+
});

0 commit comments

Comments
 (0)