Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(eslint-plugin): add rule no-unsafe-call
- Loading branch information
1 parent
296b0e7
commit 202dca3
Showing
6 changed files
with
202 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# Disallows calling an any type value (`no-unsafe-call`) | ||
|
||
Despite your best intentions, the `any` type can sometimes leak into your codebase. | ||
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. | ||
|
||
## Rule Details | ||
|
||
This rule disallows calling any variable that is typed as `any`. | ||
|
||
Examples of **incorrect** code for this rule: | ||
|
||
```ts | ||
declare const anyVar: any; | ||
declare const nestedAny: { prop: any }; | ||
|
||
anyVar(); | ||
anyVar.a.b(); | ||
|
||
nestedAny.prop(); | ||
nestedAny.prop['a'](); | ||
|
||
new anyVar(); | ||
new nestedAny.prop(); | ||
``` | ||
|
||
Examples of **correct** code for this rule: | ||
|
||
```ts | ||
declare const properlyTyped: { prop: { a: () => void } }; | ||
|
||
nestedAny.prop.a(); | ||
|
||
(() => {})(); | ||
|
||
new Map(); | ||
``` | ||
|
||
## Related to | ||
|
||
- [`no-explicit-any`](./no-explicit-any.md) | ||
- TSLint: [`no-unsafe-any`](https://palantir.github.io/tslint/rules/no-unsafe-any/) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { TSESTree } from '@typescript-eslint/experimental-utils'; | ||
import * as util from '../util'; | ||
|
||
type MessageIds = 'unsafeCall' | 'unsafeNew'; | ||
|
||
export default util.createRule<[], MessageIds>({ | ||
name: 'no-unsafe-call', | ||
meta: { | ||
type: 'problem', | ||
docs: { | ||
description: 'Disallows calling an any type value', | ||
category: 'Possible Errors', | ||
recommended: false, | ||
requiresTypeChecking: true, | ||
}, | ||
messages: { | ||
unsafeCall: 'Unsafe call of an any typed value', | ||
unsafeNew: 'Unsafe construction of an any type value', | ||
}, | ||
schema: [], | ||
}, | ||
defaultOptions: [], | ||
create(context) { | ||
const { program, esTreeNodeToTSNodeMap } = util.getParserServices(context); | ||
const checker = program.getTypeChecker(); | ||
|
||
function checkCall( | ||
node: | ||
| TSESTree.CallExpression | ||
| TSESTree.OptionalCallExpression | ||
| TSESTree.NewExpression, | ||
reportingNode: TSESTree.Expression = node.callee, | ||
messageId: MessageIds = 'unsafeCall', | ||
): void { | ||
const tsNode = esTreeNodeToTSNodeMap.get(node.callee); | ||
if (util.isAnyType(tsNode, checker)) { | ||
context.report({ | ||
node: reportingNode, | ||
messageId: messageId, | ||
}); | ||
} | ||
} | ||
|
||
return { | ||
'CallExpression, OptionalCallExpression': checkCall, | ||
NewExpression(node): void { | ||
checkCall(node, node, 'unsafeNew'); | ||
}, | ||
}; | ||
}, | ||
}); |
106 changes: 106 additions & 0 deletions
106
packages/eslint-plugin/tests/rules/no-unsafe-call.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import rule from '../../src/rules/no-unsafe-call'; | ||
import { | ||
RuleTester, | ||
batchedSingleLineTests, | ||
getFixturesRootDir, | ||
} from '../RuleTester'; | ||
|
||
const ruleTester = new RuleTester({ | ||
parser: '@typescript-eslint/parser', | ||
parserOptions: { | ||
project: './tsconfig.json', | ||
tsconfigRootDir: getFixturesRootDir(), | ||
}, | ||
}); | ||
|
||
ruleTester.run('no-unsafe-call', rule, { | ||
valid: [ | ||
'function foo(x: () => void) { x() }', | ||
'function foo(x?: { a: () => void }) { x?.a() }', | ||
'function foo(x: { a?: () => void }) { x.a?.() }', | ||
'new Map()', | ||
], | ||
invalid: [ | ||
...batchedSingleLineTests({ | ||
code: ` | ||
function foo(x: any) { x() } | ||
function foo(x: any) { x?.() } | ||
function foo(x: any) { x.a.b.c.d.e.f.g() } | ||
function foo(x: any) { x.a.b.c.d.e.f.g?.() } | ||
`, | ||
errors: [ | ||
{ | ||
messageId: 'unsafeCall', | ||
line: 2, | ||
column: 24, | ||
endColumn: 25, | ||
}, | ||
{ | ||
messageId: 'unsafeCall', | ||
line: 3, | ||
column: 24, | ||
endColumn: 25, | ||
}, | ||
{ | ||
messageId: 'unsafeCall', | ||
line: 4, | ||
column: 24, | ||
endColumn: 39, | ||
}, | ||
{ | ||
messageId: 'unsafeCall', | ||
line: 5, | ||
column: 24, | ||
endColumn: 39, | ||
}, | ||
], | ||
}), | ||
...batchedSingleLineTests({ | ||
code: ` | ||
function foo(x: { a: any }) { x.a() } | ||
function foo(x: { a: any }) { x?.a() } | ||
function foo(x: { a: any }) { x.a?.() } | ||
`, | ||
errors: [ | ||
{ | ||
messageId: 'unsafeCall', | ||
line: 2, | ||
column: 31, | ||
endColumn: 34, | ||
}, | ||
{ | ||
messageId: 'unsafeCall', | ||
line: 3, | ||
column: 31, | ||
endColumn: 35, | ||
}, | ||
{ | ||
messageId: 'unsafeCall', | ||
line: 4, | ||
column: 31, | ||
endColumn: 34, | ||
}, | ||
], | ||
}), | ||
...batchedSingleLineTests({ | ||
code: ` | ||
function foo(x: any) { new x() } | ||
function foo(x: { a: any }) { new x.a() } | ||
`, | ||
errors: [ | ||
{ | ||
messageId: 'unsafeNew', | ||
line: 2, | ||
column: 24, | ||
endColumn: 31, | ||
}, | ||
{ | ||
messageId: 'unsafeNew', | ||
line: 3, | ||
column: 31, | ||
endColumn: 40, | ||
}, | ||
], | ||
}), | ||
], | ||
}); |