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

Lines changed: 9 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 3 additions & 1 deletion
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

Lines changed: 149 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 17 additions & 15 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy