Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ea40ab6

Browse files
authored
feat(eslint-plugin): add no-meaningless-void-operator rule (typescript-eslint#3641)
1 parent f79ae9b commit ea40ab6

File tree

6 files changed

+244
-0
lines changed

6 files changed

+244
-0
lines changed

packages/eslint-plugin/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ Pro Tip: For larger codebases you may want to consider splitting our linting int
128128
| [`@typescript-eslint/no-implicit-any-catch`](./docs/rules/no-implicit-any-catch.md) | Disallow usage of the implicit `any` type in catch clauses | | :wrench: | |
129129
| [`@typescript-eslint/no-inferrable-types`](./docs/rules/no-inferrable-types.md) | Disallows explicit type declarations for variables or parameters initialized to a number, string, or boolean | :white_check_mark: | :wrench: | |
130130
| [`@typescript-eslint/no-invalid-void-type`](./docs/rules/no-invalid-void-type.md) | Disallows usage of `void` type outside of generic or return types | | | |
131+
| [`@typescript-eslint/no-meaningless-void-operator`](./docs/rules/no-meaningless-void-operator.md) | Disallow the `void` operator except when used to discard a value | | :wrench: | :thought_balloon: |
131132
| [`@typescript-eslint/no-misused-new`](./docs/rules/no-misused-new.md) | Enforce valid definition of `new` and `constructor` | :white_check_mark: | | |
132133
| [`@typescript-eslint/no-misused-promises`](./docs/rules/no-misused-promises.md) | Avoid using promises in places not designed to handle them | :white_check_mark: | | :thought_balloon: |
133134
| [`@typescript-eslint/no-namespace`](./docs/rules/no-namespace.md) | Disallow the use of custom TypeScript modules and namespaces | :white_check_mark: | | |
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Disallow the `void` operator except when used to discard a value (`no-meaningless-void-operator`)
2+
3+
Disallow the `void` operator when its argument is already of type `void` or `undefined`.
4+
5+
## Rule Details
6+
7+
The `void` operator is a useful tool to convey the programmer's intent to discard a value. For example, it is recommended as one way of suppressing [`@typescript-eslint/no-floating-promises`](./no-floating-promises.md) instead of adding `.catch()` to a promise.
8+
9+
This rule helps an author catch API changes where previously a value was being discarded at a call site, but the callee changed so it no longer returns a value. When combined with [no-unused-expressions](https://eslint.org/docs/rules/no-unused-expressions), it also helps _readers_ of the code by ensuring consistency: a statement that looks like `void foo();` is **always** discarding a return value, and a statement that looks like `foo();` is **never** discarding a return value.
10+
11+
Examples of **incorrect** code for this rule:
12+
13+
```ts
14+
void (() => {})();
15+
16+
function foo() {}
17+
void foo();
18+
```
19+
20+
Examples of **correct** code for this rule:
21+
22+
```ts
23+
(() => {})();
24+
25+
function foo() {}
26+
foo(); // nothing to discard
27+
28+
function bar(x: number) {
29+
void x; // discarding a number
30+
return 2;
31+
}
32+
void bar(); // discarding a number
33+
```
34+
35+
### Options
36+
37+
This rule accepts a single object option with the following default configuration:
38+
39+
```json
40+
{
41+
"@typescript-eslint/no-meaningless-void-operator": [
42+
"error",
43+
{
44+
"checkNever": false
45+
}
46+
]
47+
}
48+
```
49+
50+
- `checkNever: true` will suggest removing `void` when the argument has type `never`.

packages/eslint-plugin/src/configs/all.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export = {
7878
'@typescript-eslint/no-loss-of-precision': 'error',
7979
'no-magic-numbers': 'off',
8080
'@typescript-eslint/no-magic-numbers': 'error',
81+
'@typescript-eslint/no-meaningless-void-operator': 'error',
8182
'@typescript-eslint/no-misused-new': 'error',
8283
'@typescript-eslint/no-misused-promises': 'error',
8384
'@typescript-eslint/no-namespace': 'error',

packages/eslint-plugin/src/rules/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import noInvalidVoidType from './no-invalid-void-type';
5050
import noLoopFunc from './no-loop-func';
5151
import noLossOfPrecision from './no-loss-of-precision';
5252
import noMagicNumbers from './no-magic-numbers';
53+
import noMeaninglessVoidOperator from './no-meaningless-void-operator';
5354
import noMisusedNew from './no-misused-new';
5455
import noMisusedPromises from './no-misused-promises';
5556
import noNamespace from './no-namespace';
@@ -170,6 +171,7 @@ export default {
170171
'no-loop-func': noLoopFunc,
171172
'no-loss-of-precision': noLossOfPrecision,
172173
'no-magic-numbers': noMagicNumbers,
174+
'no-meaningless-void-operator': noMeaninglessVoidOperator,
173175
'no-misused-new': noMisusedNew,
174176
'no-misused-promises': noMisusedPromises,
175177
'no-namespace': noNamespace,
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import {
2+
ESLintUtils,
3+
TSESLint,
4+
TSESTree,
5+
} from '@typescript-eslint/experimental-utils';
6+
import * as tsutils from 'tsutils';
7+
import * as util from '../util';
8+
import * as ts from 'typescript';
9+
10+
type Options = [
11+
{
12+
checkNever: boolean;
13+
},
14+
];
15+
16+
export default util.createRule<
17+
Options,
18+
'meaninglessVoidOperator' | 'removeVoid'
19+
>({
20+
name: 'no-meaningless-void-operator',
21+
meta: {
22+
type: 'suggestion',
23+
docs: {
24+
description:
25+
'Disallow the `void` operator except when used to discard a value',
26+
category: 'Best Practices',
27+
recommended: false,
28+
suggestion: true,
29+
requiresTypeChecking: true,
30+
},
31+
fixable: 'code',
32+
messages: {
33+
meaninglessVoidOperator:
34+
"void operator shouldn't be used on {{type}}; it should convey that a return value is being ignored",
35+
removeVoid: "Remove 'void'",
36+
},
37+
schema: [
38+
{
39+
type: 'object',
40+
properties: {
41+
checkNever: {
42+
type: 'boolean',
43+
default: false,
44+
},
45+
},
46+
additionalProperties: false,
47+
},
48+
],
49+
},
50+
defaultOptions: [{ checkNever: false }],
51+
52+
create(context, [{ checkNever }]) {
53+
const parserServices = ESLintUtils.getParserServices(context);
54+
const checker = parserServices.program.getTypeChecker();
55+
const sourceCode = context.getSourceCode();
56+
57+
return {
58+
'UnaryExpression[operator="void"]'(node: TSESTree.UnaryExpression): void {
59+
const fix = (fixer: TSESLint.RuleFixer): TSESLint.RuleFix => {
60+
return fixer.removeRange([
61+
sourceCode.getTokens(node)[0].range[0],
62+
sourceCode.getTokens(node)[1].range[0],
63+
]);
64+
};
65+
66+
const argTsNode = parserServices.esTreeNodeToTSNodeMap.get(
67+
node.argument,
68+
);
69+
const argType = checker.getTypeAtLocation(argTsNode);
70+
const unionParts = tsutils.unionTypeParts(argType);
71+
if (
72+
unionParts.every(
73+
part => part.flags & (ts.TypeFlags.Void | ts.TypeFlags.Undefined),
74+
)
75+
) {
76+
context.report({
77+
node,
78+
messageId: 'meaninglessVoidOperator',
79+
data: { type: checker.typeToString(argType) },
80+
fix,
81+
});
82+
} else if (
83+
checkNever &&
84+
unionParts.every(
85+
part =>
86+
part.flags &
87+
(ts.TypeFlags.Void | ts.TypeFlags.Undefined | ts.TypeFlags.Never),
88+
)
89+
) {
90+
context.report({
91+
node,
92+
messageId: 'meaninglessVoidOperator',
93+
data: { type: checker.typeToString(argType) },
94+
suggest: [{ messageId: 'removeVoid', fix }],
95+
});
96+
}
97+
},
98+
};
99+
},
100+
});
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import rule from '../../src/rules/no-meaningless-void-operator';
2+
import { RuleTester, getFixturesRootDir } from '../RuleTester';
3+
4+
const rootDir = getFixturesRootDir();
5+
6+
const ruleTester = new RuleTester({
7+
parserOptions: {
8+
ecmaVersion: 2018,
9+
tsconfigRootDir: rootDir,
10+
project: './tsconfig.json',
11+
},
12+
parser: '@typescript-eslint/parser',
13+
});
14+
15+
ruleTester.run('no-meaningless-void-operator', rule, {
16+
valid: [
17+
`
18+
(() => {})();
19+
20+
function foo() {}
21+
foo(); // nothing to discard
22+
23+
function bar(x: number) {
24+
void x;
25+
return 2;
26+
}
27+
void bar(); // discarding a number
28+
`,
29+
`
30+
function bar(x: never) {
31+
void x;
32+
}
33+
`,
34+
],
35+
invalid: [
36+
{
37+
code: 'void (() => {})();',
38+
output: '(() => {})();',
39+
errors: [
40+
{
41+
messageId: 'meaninglessVoidOperator',
42+
line: 1,
43+
column: 1,
44+
},
45+
],
46+
},
47+
{
48+
code: `
49+
function foo() {}
50+
void foo();
51+
`,
52+
output: `
53+
function foo() {}
54+
foo();
55+
`,
56+
errors: [
57+
{
58+
messageId: 'meaninglessVoidOperator',
59+
line: 3,
60+
column: 1,
61+
},
62+
],
63+
},
64+
{
65+
options: [{ checkNever: true }],
66+
code: `
67+
function bar(x: never) {
68+
void x;
69+
}
70+
`.trimRight(),
71+
errors: [
72+
{
73+
messageId: 'meaninglessVoidOperator',
74+
line: 3,
75+
column: 3,
76+
suggestions: [
77+
{
78+
messageId: 'removeVoid',
79+
output: `
80+
function bar(x: never) {
81+
x;
82+
}
83+
`.trimRight(),
84+
},
85+
],
86+
},
87+
],
88+
},
89+
],
90+
});

0 commit comments

Comments
 (0)