Skip to content

Commit

Permalink
feat(eslint-plugin): add rule no-unsafe-call
Browse files Browse the repository at this point in the history
  • Loading branch information
bradzacher committed Feb 28, 2020
1 parent c92375d commit ee060d9
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/eslint-plugin/README.md
Expand Up @@ -131,6 +131,7 @@ Pro Tip: For larger codebases you may want to consider splitting our linting int
| [`@typescript-eslint/no-unnecessary-qualifier`](./docs/rules/no-unnecessary-qualifier.md) | Warns when a namespace qualifier is unnecessary | | :wrench: | :thought_balloon: |
| [`@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: |
| [`@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: |
| [`@typescript-eslint/no-unsafe-call`](./docs/rules/no-unsafe-call.md) | Disallows calling an any type value | | | :thought_balloon: |
| [`@typescript-eslint/no-unsafe-member-access`](./docs/rules/no-unsafe-member-access.md) | Disallows member access on any typed variables | | | :thought_balloon: |
| [`@typescript-eslint/no-unsafe-return`](./docs/rules/no-unsafe-return.md) | Disallows returning any from a function | | | :thought_balloon: |
| [`@typescript-eslint/no-unused-vars-experimental`](./docs/rules/no-unused-vars-experimental.md) | Disallow unused variables and arguments | | | :thought_balloon: |
Expand Down
36 changes: 36 additions & 0 deletions packages/eslint-plugin/docs/rules/no-unsafe-call.md
@@ -0,0 +1,36 @@
# 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']();
```

Examples of **correct** code for this rule:

```ts
declare const properlyTyped: { prop: { a: () => void } };

nestedAny.prop.a();

(() => {})();
```

## Related to

- [`no-explicit-any`](./no-explicit-any.md)
- TSLint: [`no-unsafe-any`](https://palantir.github.io/tslint/rules/no-unsafe-any/)
1 change: 1 addition & 0 deletions packages/eslint-plugin/src/configs/all.json
Expand Up @@ -60,6 +60,7 @@
"@typescript-eslint/no-unnecessary-qualifier": "error",
"@typescript-eslint/no-unnecessary-type-arguments": "error",
"@typescript-eslint/no-unnecessary-type-assertion": "error",
"@typescript-eslint/no-unsafe-call": "error",
"@typescript-eslint/no-unsafe-member-access": "error",
"@typescript-eslint/no-unsafe-return": "error",
"no-unused-expressions": "off",
Expand Down
2 changes: 2 additions & 0 deletions packages/eslint-plugin/src/rules/index.ts
Expand Up @@ -51,6 +51,7 @@ import noUnnecessaryBooleanLiteralCompare from './no-unnecessary-boolean-literal
import noUnnecessaryCondition from './no-unnecessary-condition';
import noUnnecessaryQualifier from './no-unnecessary-qualifier';
import noUnnecessaryTypeAssertion from './no-unnecessary-type-assertion';
import noUnsafeCall from './no-unsafe-call';
import noUnsafeMemberAccess from './no-unsafe-member-access';
import noUnsafeReturn from './no-unsafe-return';
import noUntypedPublicSignature from './no-untyped-public-signature';
Expand Down Expand Up @@ -143,6 +144,7 @@ export default {
'no-unnecessary-qualifier': noUnnecessaryQualifier,
'no-unnecessary-type-arguments': useDefaultTypeParameter,
'no-unnecessary-type-assertion': noUnnecessaryTypeAssertion,
'no-unsafe-call': noUnsafeCall,
'no-unsafe-member-access': noUnsafeMemberAccess,
'no-unsafe-return': noUnsafeReturn,
'no-untyped-public-signature': noUntypedPublicSignature,
Expand Down
38 changes: 38 additions & 0 deletions packages/eslint-plugin/src/rules/no-unsafe-call.ts
@@ -0,0 +1,38 @@
import * as util from '../util';
import { TSESTree } from '@typescript-eslint/experimental-utils';

export default util.createRule({
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',
},
schema: [],
},
defaultOptions: [],
create(context) {
const { program, esTreeNodeToTSNodeMap } = util.getParserServices(context);
const checker = program.getTypeChecker();

return {
'CallExpression, OptionalCallExpression'(
node: TSESTree.CallExpression | TSESTree.OptionalCallExpression,
): void {
const tsNode = esTreeNodeToTSNodeMap.get(node.callee);
if (util.isAnyType(tsNode, checker)) {
context.report({
node: node.callee,
messageId: 'unsafeCall',
});
}
},
};
},
});
85 changes: 85 additions & 0 deletions packages/eslint-plugin/tests/rules/no-unsafe-call.test.ts
@@ -0,0 +1,85 @@
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?.() }',
],
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,
},
],
}),
],
});

0 comments on commit ee060d9

Please sign in to comment.