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

Skip to content

Commit f667ff1

Browse files
authored
feat(eslint-plugin): create no-invalid-void-type rule (typescript-eslint#1847)
1 parent f91ff20 commit f667ff1

File tree

7 files changed

+699
-1
lines changed

7 files changed

+699
-1
lines changed

packages/eslint-plugin/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ Pro Tip: For larger codebases you may want to consider splitting our linting int
119119
| [`@typescript-eslint/no-for-in-array`](./docs/rules/no-for-in-array.md) | Disallow iterating over an array with a for-in loop | :heavy_check_mark: | | :thought_balloon: |
120120
| [`@typescript-eslint/no-implied-eval`](./docs/rules/no-implied-eval.md) | Disallow the use of `eval()`-like methods | | | :thought_balloon: |
121121
| [`@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 | :heavy_check_mark: | :wrench: | |
122+
| [`@typescript-eslint/no-invalid-void-type`](./docs/rules/no-invalid-void-type.md) | Disallows usage of `void` type outside of generic or return types | | | |
122123
| [`@typescript-eslint/no-misused-new`](./docs/rules/no-misused-new.md) | Enforce valid definition of `new` and `constructor` | :heavy_check_mark: | | |
123124
| [`@typescript-eslint/no-misused-promises`](./docs/rules/no-misused-promises.md) | Avoid using promises in places not designed to handle them | :heavy_check_mark: | | :thought_balloon: |
124125
| [`@typescript-eslint/no-namespace`](./docs/rules/no-namespace.md) | Disallow the use of custom TypeScript modules and namespaces | :heavy_check_mark: | | |

packages/eslint-plugin/ROADMAP.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ It lists all TSLint rules along side rules from the ESLint ecosystem that are th
1818
| [`adjacent-overload-signatures`] || [`@typescript-eslint/adjacent-overload-signatures`] |
1919
| [`ban-ts-ignore`] || [`@typescript-eslint/ban-ts-ignore`] |
2020
| [`ban-types`] | 🌓 | [`@typescript-eslint/ban-types`]<sup>[1]</sup> |
21-
| [`invalid-void`] | 🛑 | N/A |
21+
| [`invalid-void`] | | [`@typescript-eslint/no-invalid-void-type`] |
2222
| [`member-access`] || [`@typescript-eslint/explicit-member-accessibility`] |
2323
| [`member-ordering`] || [`@typescript-eslint/member-ordering`] |
2424
| [`no-any`] || [`@typescript-eslint/no-explicit-any`] |
@@ -623,6 +623,7 @@ Relevant plugins: [`chai-expect-keywords`](https://github.com/gavinaiken/eslint-
623623
[`@typescript-eslint/restrict-plus-operands`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/restrict-plus-operands.md
624624
[`@typescript-eslint/strict-boolean-expressions`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/strict-boolean-expressions.md
625625
[`@typescript-eslint/indent`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/indent.md
626+
[`@typescript-eslint/no-invalid-void-type`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-invalid-void-type.md
626627
[`@typescript-eslint/no-require-imports`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-require-imports.md
627628
[`@typescript-eslint/array-type`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/array-type.md
628629
[`@typescript-eslint/class-name-casing`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/class-name-casing.md
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# Disallows usage of `void` type outside of generic or return types (`no-invalid-void-type`)
2+
3+
Disallows usage of `void` type outside of return types or generic type arguments.
4+
If `void` is used as return type, it shouldn’t be a part of intersection/union type.
5+
6+
## Rationale
7+
8+
The `void` type means “nothing” or that a function does not return any value,
9+
in contrast with implicit `undefined` type which means that a function returns a value `undefined`.
10+
So “nothing” cannot be mixed with any other types. If you need this - use the `undefined` type instead.
11+
12+
## Rule Details
13+
14+
This rule aims to ensure that the `void` type is only used in valid places.
15+
16+
The following patterns are considered warnings:
17+
18+
```ts
19+
type PossibleValues = string | number | void;
20+
type MorePossibleValues = string | ((number & any) | (string | void));
21+
22+
function logSomething(thing: void) {}
23+
function printArg<T = void>(arg: T) {}
24+
25+
logAndReturn<void>(undefined);
26+
27+
interface Interface {
28+
lambda: () => void;
29+
prop: void;
30+
}
31+
32+
class MyClass {
33+
private readonly propName: void;
34+
}
35+
```
36+
37+
The following patterns are not considered warnings:
38+
39+
```ts
40+
type NoOp = () => void;
41+
42+
function noop(): void {}
43+
44+
let trulyUndefined = void 0;
45+
46+
async function promiseMeSomething(): Promise<void> {}
47+
```
48+
49+
### Options
50+
51+
```ts
52+
interface Options {
53+
allowInGenericTypeArguments?: boolean | string[];
54+
}
55+
56+
const defaultOptions: Options = {
57+
allowInGenericTypeArguments: true,
58+
};
59+
```
60+
61+
#### `allowInGenericTypeArguments`
62+
63+
This option lets you control if `void` can be used as a valid value for generic type parameters.
64+
65+
Alternatively, you can provide an array of strings which whitelist which types may accept `void` as a generic type parameter.
66+
67+
This option is `true` by default.
68+
69+
The following patterns are considered warnings with `{ allowInGenericTypeArguments: false }`:
70+
71+
```ts
72+
logAndReturn<void>(undefined);
73+
74+
let voidPromise: Promise<void> = new Promise<void>(() => {});
75+
let voidMap: Map<string, void> = new Map<string, void>();
76+
```
77+
78+
The following patterns are considered warnings with `{ allowInGenericTypeArguments: ['Ex.Mx.Tx'] }`:
79+
80+
```ts
81+
logAndReturn<void>(undefined);
82+
83+
type NotAllowedVoid1 = Mx.Tx<void>;
84+
type NotAllowedVoid2 = Tx<void>;
85+
type NotAllowedVoid3 = Promise<void>;
86+
```
87+
88+
The following patterns are not considered warnings with `{ allowInGenericTypeArguments: ['Ex.Mx.Tx'] }`:
89+
90+
```ts
91+
type AllowedVoid = Ex.MX.Tx<void>;
92+
```
93+
94+
## When Not To Use It
95+
96+
If you don't care about if `void` is used with other types,
97+
or in invalid places, then you don't need this rule.
98+
99+
## Compatibility
100+
101+
- TSLint: [invalid-void](https://palantir.github.io/tslint/rules/invalid-void/)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"@typescript-eslint/no-for-in-array": "error",
5151
"@typescript-eslint/no-implied-eval": "error",
5252
"@typescript-eslint/no-inferrable-types": "error",
53+
"@typescript-eslint/no-invalid-void-type": "error",
5354
"no-magic-numbers": "off",
5455
"@typescript-eslint/no-magic-numbers": "error",
5556
"@typescript-eslint/no-misused-new": "error",

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import noFloatingPromises from './no-floating-promises';
4040
import noForInArray from './no-for-in-array';
4141
import noImpliedEval from './no-implied-eval';
4242
import noInferrableTypes from './no-inferrable-types';
43+
import noInvalidVoidType from './no-invalid-void-type';
4344
import noMagicNumbers from './no-magic-numbers';
4445
import noMisusedNew from './no-misused-new';
4546
import noMisusedPromises from './no-misused-promises';
@@ -142,6 +143,7 @@ export default {
142143
'no-for-in-array': noForInArray,
143144
'no-implied-eval': noImpliedEval,
144145
'no-inferrable-types': noInferrableTypes,
146+
'no-invalid-void-type': noInvalidVoidType,
145147
'no-magic-numbers': noMagicNumbers,
146148
'no-misused-new': noMisusedNew,
147149
'no-misused-promises': noMisusedPromises,
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import {
2+
AST_NODE_TYPES,
3+
TSESTree,
4+
} from '@typescript-eslint/experimental-utils';
5+
import * as util from '../util';
6+
7+
interface Options {
8+
allowInGenericTypeArguments: boolean | string[];
9+
}
10+
11+
type MessageIds =
12+
| 'invalidVoidForGeneric'
13+
| 'invalidVoidNotReturnOrGeneric'
14+
| 'invalidVoidNotReturn';
15+
16+
export default util.createRule<[Options], MessageIds>({
17+
name: 'no-invalid-void-type',
18+
meta: {
19+
type: 'problem',
20+
docs: {
21+
description:
22+
'Disallows usage of `void` type outside of generic or return types',
23+
category: 'Best Practices',
24+
recommended: false,
25+
},
26+
messages: {
27+
invalidVoidForGeneric:
28+
'{{ generic }} may not have void as a type variable',
29+
invalidVoidNotReturnOrGeneric:
30+
'void is only valid as a return type or generic type variable',
31+
invalidVoidNotReturn: 'void is only valid as a return type',
32+
},
33+
schema: [
34+
{
35+
type: 'object',
36+
properties: {
37+
allowInGenericTypeArguments: {
38+
oneOf: [
39+
{ type: 'boolean' },
40+
{
41+
type: 'array',
42+
items: { type: 'string' },
43+
minLength: 1,
44+
},
45+
],
46+
},
47+
},
48+
additionalProperties: false,
49+
},
50+
],
51+
},
52+
defaultOptions: [{ allowInGenericTypeArguments: true }],
53+
create(context, [{ allowInGenericTypeArguments }]) {
54+
const validParents: AST_NODE_TYPES[] = [
55+
AST_NODE_TYPES.TSTypeAnnotation, //
56+
];
57+
const invalidGrandParents: AST_NODE_TYPES[] = [
58+
AST_NODE_TYPES.TSPropertySignature,
59+
AST_NODE_TYPES.CallExpression,
60+
AST_NODE_TYPES.ClassProperty,
61+
AST_NODE_TYPES.Identifier,
62+
];
63+
64+
if (allowInGenericTypeArguments === true) {
65+
validParents.push(AST_NODE_TYPES.TSTypeParameterInstantiation);
66+
}
67+
68+
return {
69+
TSVoidKeyword(node: TSESTree.TSVoidKeyword): void {
70+
/* istanbul ignore next */
71+
if (!node.parent?.parent) {
72+
return;
73+
}
74+
75+
if (
76+
validParents.includes(node.parent.type) &&
77+
!invalidGrandParents.includes(node.parent.parent.type)
78+
) {
79+
return;
80+
}
81+
82+
if (
83+
node.parent.type === AST_NODE_TYPES.TSTypeParameterInstantiation &&
84+
node.parent.parent.type === AST_NODE_TYPES.TSTypeReference &&
85+
Array.isArray(allowInGenericTypeArguments)
86+
) {
87+
const sourceCode = context.getSourceCode();
88+
const fullyQualifiedName = sourceCode
89+
.getText(node.parent.parent.typeName)
90+
.replace(/ /gu, '');
91+
92+
if (
93+
!allowInGenericTypeArguments
94+
.map(s => s.replace(/ /gu, ''))
95+
.includes(fullyQualifiedName)
96+
) {
97+
context.report({
98+
messageId: 'invalidVoidForGeneric',
99+
data: { generic: fullyQualifiedName },
100+
node,
101+
});
102+
}
103+
104+
return;
105+
}
106+
107+
context.report({
108+
messageId: allowInGenericTypeArguments
109+
? 'invalidVoidNotReturnOrGeneric'
110+
: 'invalidVoidNotReturn',
111+
node,
112+
});
113+
},
114+
};
115+
},
116+
});

0 commit comments

Comments
 (0)