Skip to content

Commit c8a4dad

Browse files
authored
feat(eslint-plugin): add rule no-void-expression (typescript-eslint#2605)
1 parent fb1d9b1 commit c8a4dad

File tree

8 files changed

+802
-16
lines changed

8 files changed

+802
-16
lines changed

.eslintrc.js

+9
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,15 @@ module.exports = {
221221
'@typescript-eslint/internal/plugin-test-formatting': 'error',
222222
},
223223
},
224+
// files which list all the things
225+
{
226+
files: ['packages/eslint-plugin/src/rules/index.ts'],
227+
rules: {
228+
// enforce alphabetical ordering
229+
'sort-keys': 'error',
230+
'import/order': ['error', { alphabetize: { order: 'asc' } }],
231+
},
232+
},
224233
// tools and tests
225234
{
226235
files: ['**/tools/**/*.ts', '**/tests/**/*.ts'],

packages/eslint-plugin/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ Pro Tip: For larger codebases you may want to consider splitting our linting int
117117
| [`@typescript-eslint/naming-convention`](./docs/rules/naming-convention.md) | Enforces naming conventions for everything across a codebase | | | :thought_balloon: |
118118
| [`@typescript-eslint/no-base-to-string`](./docs/rules/no-base-to-string.md) | Requires that `.toString()` is only called on objects which provide useful information when stringified | | | :thought_balloon: |
119119
| [`@typescript-eslint/no-confusing-non-null-assertion`](./docs/rules/no-confusing-non-null-assertion.md) | Disallow non-null assertion in locations that may be confusing | | :wrench: | |
120+
| [`@typescript-eslint/no-confusing-void-expression`](./docs/rules/no-confusing-void-expression.md) | Requires expressions of type void to appear in statement position | | :wrench: | :thought_balloon: |
120121
| [`@typescript-eslint/no-dynamic-delete`](./docs/rules/no-dynamic-delete.md) | Disallow the delete operator with computed key expressions | | :wrench: | |
121122
| [`@typescript-eslint/no-empty-interface`](./docs/rules/no-empty-interface.md) | Disallow the declaration of empty interfaces | :heavy_check_mark: | :wrench: | |
122123
| [`@typescript-eslint/no-explicit-any`](./docs/rules/no-explicit-any.md) | Disallow usage of the `any` type | :heavy_check_mark: | :wrench: | |

packages/eslint-plugin/ROADMAP.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ It lists all TSLint rules along side rules from the ESLint ecosystem that are th
9696
| [`no-unused-variable`] | 🌓 | [`@typescript-eslint/no-unused-vars`] |
9797
| [`no-use-before-declare`] || [`@typescript-eslint/no-use-before-define`] |
9898
| [`no-var-keyword`] | 🌟 | [`no-var`][no-var] |
99-
| [`no-void-expression`] | 🛑 | N/A (unrelated to the similarly named ESLint rule `no-void`) |
99+
| [`no-void-expression`] | | [`@typescript-eslint/no-confusing-void-expression`] |
100100
| [`prefer-conditional-expression`] | 🛑 | N/A |
101101
| [`prefer-object-spread`] | 🌟 | [`prefer-object-spread`][prefer-object-spread] |
102102
| [`radix`] | 🌟 | [`radix`][radix] |
@@ -650,6 +650,8 @@ Relevant plugins: [`chai-expect-keywords`](https://github.com/gavinaiken/eslint-
650650
[`@typescript-eslint/no-floating-promises`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-floating-promises.md
651651
[`@typescript-eslint/no-magic-numbers`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-magic-numbers.md
652652
[`@typescript-eslint/no-unsafe-member-access`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unsafe-member-access.md
653+
[`@typescript-eslint/restrict-template-expressions`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/restrict-template-expressions.md
654+
[`@typescript-eslint/no-confusing-void-expression`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-confusing-void-expression.md
653655

654656
<!-- eslint-plugin-import -->
655657

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# Requires expressions of type void to appear in statement position (`no-confusing-void-expression`)
2+
3+
Returning the results of an expression whose type is void can be misleading.
4+
Attempting to do so is likely a symptom of expecting a different return type from a function.
5+
Even if used correctly, it can be misleading for other developers,
6+
who don't know what a particular function does and if its result matters.
7+
8+
This rule provides automatic fixes for most common cases.
9+
10+
## Examples
11+
12+
Examples of **incorrect** code for this rule:
13+
14+
```ts
15+
// somebody forgot that `alert` doesn't return anything
16+
const response = alert('Are you sure?');
17+
console.log(alert('Are you sure?'));
18+
19+
// it's not obvious whether the chained promise will contain the response (fixable)
20+
promise.then(value => window.postMessage(value));
21+
22+
// it looks like we are returning the result of `console.error` (fixable)
23+
function doSomething() {
24+
if (!somethingToDo) {
25+
return console.error('Nothing to do!');
26+
}
27+
28+
console.log('Doing a thing...');
29+
}
30+
```
31+
32+
Examples of **correct** code for this rule:
33+
34+
```ts
35+
// just a regular void function in a statement position
36+
alert('Hello, world!');
37+
38+
// this function returns a boolean value so it's ok
39+
const response = confirm('Are you sure?');
40+
console.log(confirm('Are you sure?'));
41+
42+
// now it's obvious that `postMessage` doesn't return any response
43+
promise.then(value => {
44+
window.postMessage(value);
45+
});
46+
47+
// now it's explicit that we want to log the error and return early
48+
function doSomething() {
49+
if (!somethingToDo) {
50+
console.error('Nothing to do!');
51+
return;
52+
}
53+
54+
console.log('Doing a thing...');
55+
}
56+
57+
// using logical expressions for their side effects is fine
58+
cond && console.log('true');
59+
cond || console.error('false');
60+
cond ? console.log('true') : console.error('false');
61+
```
62+
63+
## Options
64+
65+
An object option can be specified. Each boolean flag makes the rule less strict.
66+
67+
```ts
68+
type Options = {
69+
ignoreArrowShorthand?: boolean;
70+
ignoreVoidOperator?: boolean;
71+
};
72+
73+
const defaults: Options = {
74+
ignoreArrowShorthand: false,
75+
ignoreVoidOperator: false,
76+
};
77+
```
78+
79+
### `ignoreArrowShorthand`
80+
81+
`false` by default.
82+
83+
```json
84+
{
85+
"@typescript-eslint/no-confusing-void-expression": [
86+
"error",
87+
{ "ignoreArrowShorthand": true }
88+
]
89+
}
90+
```
91+
92+
It might be undesirable to wrap every arrow function shorthand expression with braces.
93+
Especially when using Prettier formatter, which spreads such code across 3 lines instead of 1.
94+
95+
Examples of additional **correct** code with this option enabled:
96+
97+
```ts
98+
promise.then(value => window.postMessage(value));
99+
```
100+
101+
### `ignoreVoidOperator`
102+
103+
`false` by default.
104+
105+
```json
106+
{
107+
"@typescript-eslint/no-confusing-void-expression": [
108+
"error",
109+
{ "ignoreVoidOperator": true }
110+
]
111+
}
112+
```
113+
114+
It might be preferable to only use some distinct syntax
115+
to explicitly mark the confusing but valid usage of void expressions.
116+
This option allows void expressions which are explicitly wrapped in the `void` operator.
117+
This can help avoid confusion among other developers as long as they are made aware of this code style.
118+
119+
This option also changes the automatic fixes for common cases to use the `void` operator.
120+
It also enables a suggestion fix to wrap the void expression with `void` operator for every problem reported.
121+
122+
Examples of additional **correct** code with this option enabled:
123+
124+
```ts
125+
// now it's obvious that we don't expect any response
126+
promise.then(value => void window.postMessage(value));
127+
128+
// now it's explicit that we don't want to return anything
129+
function doSomething() {
130+
if (!somethingToDo) {
131+
return void console.error('Nothing to do!');
132+
}
133+
134+
console.log('Doing a thing...');
135+
}
136+
137+
// we are sure that we want to always log `undefined`
138+
console.log(void alert('Hello, world!'));
139+
```
140+
141+
## When Not To Use It
142+
143+
The return type of a function can be inspected by going to its definition or hovering over it in an IDE.
144+
If you don't care about being explicit about the void type in actual code then don't use this rule.
145+
Also, if you prefer concise coding style then also don't use it.
146+
147+
## Related to
148+
149+
- TSLint: ['no-void-expression'](https://palantir.github.io/tslint/rules/no-void-expression/)

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

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export = {
4747
'@typescript-eslint/no-array-constructor': 'error',
4848
'@typescript-eslint/no-base-to-string': 'error',
4949
'@typescript-eslint/no-confusing-non-null-assertion': 'error',
50+
'@typescript-eslint/no-confusing-void-expression': 'error',
5051
'no-dupe-class-members': 'off',
5152
'@typescript-eslint/no-dupe-class-members': 'error',
5253
'no-duplicate-imports': 'off',

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

+17-15
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,12 @@ import braceStyle from './brace-style';
88
import classLiteralPropertyStyle from './class-literal-property-style';
99
import commaDangle from './comma-dangle';
1010
import commaSpacing from './comma-spacing';
11-
import confusingNonNullAssertionLikeNotEqual from './no-confusing-non-null-assertion';
1211
import consistentIndexedObjectStyle from './consistent-indexed-object-style';
1312
import consistentTypeAssertions from './consistent-type-assertions';
1413
import consistentTypeDefinitions from './consistent-type-definitions';
1514
import consistentTypeImports from './consistent-type-imports';
1615
import defaultParamLast from './default-param-last';
1716
import dotNotation from './dot-notation';
18-
import enumMembersSpacing from './space-infix-ops';
1917
import explicitFunctionReturnType from './explicit-function-return-type';
2018
import explicitMemberAccessibility from './explicit-member-accessibility';
2119
import explicitModuleBoundaryTypes from './explicit-module-boundary-types';
@@ -30,25 +28,27 @@ import methodSignatureStyle from './method-signature-style';
3028
import namingConvention from './naming-convention';
3129
import noArrayConstructor from './no-array-constructor';
3230
import noBaseToString from './no-base-to-string';
31+
import confusingNonNullAssertionLikeNotEqual from './no-confusing-non-null-assertion';
32+
import noConfusingVoidExpression from './no-confusing-void-expression';
3333
import noDupeClassMembers from './no-dupe-class-members';
34+
import noDuplicateImports from './no-duplicate-imports';
3435
import noDynamicDelete from './no-dynamic-delete';
3536
import noEmptyFunction from './no-empty-function';
3637
import noEmptyInterface from './no-empty-interface';
3738
import noExplicitAny from './no-explicit-any';
38-
import noImplicitAnyCatch from './no-implicit-any-catch';
39-
import noExtraneousClass from './no-extraneous-class';
4039
import noExtraNonNullAssertion from './no-extra-non-null-assertion';
4140
import noExtraParens from './no-extra-parens';
4241
import noExtraSemi from './no-extra-semi';
42+
import noExtraneousClass from './no-extraneous-class';
4343
import noFloatingPromises from './no-floating-promises';
4444
import noForInArray from './no-for-in-array';
45-
import preferLiteralEnumMember from './prefer-literal-enum-member';
45+
import noImplicitAnyCatch from './no-implicit-any-catch';
4646
import noImpliedEval from './no-implied-eval';
4747
import noInferrableTypes from './no-inferrable-types';
4848
import noInvalidThis from './no-invalid-this';
4949
import noInvalidVoidType from './no-invalid-void-type';
50-
import noLossOfPrecision from './no-loss-of-precision';
5150
import noLoopFunc from './no-loop-func';
51+
import noLossOfPrecision from './no-loss-of-precision';
5252
import noMagicNumbers from './no-magic-numbers';
5353
import noMisusedNew from './no-misused-new';
5454
import noMisusedPromises from './no-misused-promises';
@@ -83,6 +83,7 @@ import preferEnumInitializers from './prefer-enum-initializers';
8383
import preferForOf from './prefer-for-of';
8484
import preferFunctionType from './prefer-function-type';
8585
import preferIncludes from './prefer-includes';
86+
import preferLiteralEnumMember from './prefer-literal-enum-member';
8687
import preferNamespaceKeyword from './prefer-namespace-keyword';
8788
import preferNullishCoalescing from './prefer-nullish-coalescing';
8889
import preferOptionalChain from './prefer-optional-chain';
@@ -101,14 +102,14 @@ import restrictTemplateExpressions from './restrict-template-expressions';
101102
import returnAwait from './return-await';
102103
import semi from './semi';
103104
import spaceBeforeFunctionParen from './space-before-function-paren';
105+
import spaceInfixOps from './space-infix-ops';
104106
import strictBooleanExpressions from './strict-boolean-expressions';
105107
import switchExhaustivenessCheck from './switch-exhaustiveness-check';
106108
import tripleSlashReference from './triple-slash-reference';
107109
import typeAnnotationSpacing from './type-annotation-spacing';
108110
import typedef from './typedef';
109111
import unboundMethod from './unbound-method';
110112
import unifiedSignatures from './unified-signatures';
111-
import noDuplicateImports from './no-duplicate-imports';
112113

113114
export default {
114115
'adjacent-overload-signatures': adjacentOverloadSignatures,
@@ -127,11 +128,11 @@ export default {
127128
'consistent-type-imports': consistentTypeImports,
128129
'default-param-last': defaultParamLast,
129130
'dot-notation': dotNotation,
130-
'space-infix-ops': enumMembersSpacing,
131131
'explicit-function-return-type': explicitFunctionReturnType,
132132
'explicit-member-accessibility': explicitMemberAccessibility,
133133
'explicit-module-boundary-types': explicitModuleBoundaryTypes,
134134
'func-call-spacing': funcCallSpacing,
135+
indent: indent,
135136
'init-declarations': initDeclarations,
136137
'keyword-spacing': keywordSpacing,
137138
'lines-between-class-members': linesBetweenClassMembers,
@@ -142,7 +143,9 @@ export default {
142143
'no-array-constructor': noArrayConstructor,
143144
'no-base-to-string': noBaseToString,
144145
'no-confusing-non-null-assertion': confusingNonNullAssertionLikeNotEqual,
146+
'no-confusing-void-expression': noConfusingVoidExpression,
145147
'no-dupe-class-members': noDupeClassMembers,
148+
'no-duplicate-imports': noDuplicateImports,
146149
'no-dynamic-delete': noDynamicDelete,
147150
'no-empty-function': noEmptyFunction,
148151
'no-empty-interface': noEmptyInterface,
@@ -184,8 +187,8 @@ export default {
184187
'no-unsafe-member-access': noUnsafeMemberAccess,
185188
'no-unsafe-return': noUnsafeReturn,
186189
'no-unused-expressions': noUnusedExpressions,
187-
'no-unused-vars-experimental': noUnusedVarsExperimental,
188190
'no-unused-vars': noUnusedVars,
191+
'no-unused-vars-experimental': noUnusedVarsExperimental,
189192
'no-use-before-define': noUseBeforeDefine,
190193
'no-useless-constructor': noUselessConstructor,
191194
'no-var-requires': noVarRequires,
@@ -198,28 +201,27 @@ export default {
198201
'prefer-namespace-keyword': preferNamespaceKeyword,
199202
'prefer-nullish-coalescing': preferNullishCoalescing,
200203
'prefer-optional-chain': preferOptionalChain,
201-
'prefer-readonly-parameter-types': preferReadonlyParameterTypes,
202204
'prefer-readonly': preferReadonly,
205+
'prefer-readonly-parameter-types': preferReadonlyParameterTypes,
203206
'prefer-reduce-type-parameter': preferReduceTypeParameter,
204207
'prefer-regexp-exec': preferRegexpExec,
205208
'prefer-string-starts-ends-with': preferStringStartsEndsWith,
206209
'prefer-ts-expect-error': preferTsExpectError,
207210
'promise-function-async': promiseFunctionAsync,
211+
quotes: quotes,
208212
'require-array-sort-compare': requireArraySortCompare,
209213
'require-await': requireAwait,
210214
'restrict-plus-operands': restrictPlusOperands,
211215
'restrict-template-expressions': restrictTemplateExpressions,
212216
'return-await': returnAwait,
217+
semi: semi,
213218
'space-before-function-paren': spaceBeforeFunctionParen,
219+
'space-infix-ops': spaceInfixOps,
214220
'strict-boolean-expressions': strictBooleanExpressions,
215221
'switch-exhaustiveness-check': switchExhaustivenessCheck,
216222
'triple-slash-reference': tripleSlashReference,
217223
'type-annotation-spacing': typeAnnotationSpacing,
224+
typedef: typedef,
218225
'unbound-method': unboundMethod,
219226
'unified-signatures': unifiedSignatures,
220-
'no-duplicate-imports': noDuplicateImports,
221-
indent: indent,
222-
quotes: quotes,
223-
semi: semi,
224-
typedef: typedef,
225227
};

0 commit comments

Comments
 (0)