Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commite2e9ffc

Browse files
feat(eslint-plugin): [no-confusing-void-expression] add an option to ignore void<->void (typescript-eslint#10067)
* initial implementation* enable rule* improvements and fixes* use checker.getContextualType over getContextualType* more fixes* improve implementation* add docs* update snapshots* enable rule with option* rename function names to be a bit less confusing* reword game-plan* move rule def to be under its proper title* remove invalid tests* add more tests* add more tests* add more tests* refactor tests* refactor tests* wording* Update packages/eslint-plugin/docs/rules/no-confusing-void-expression.mdxCo-authored-by: Josh Goldberg ✨ <git@joshuakgoldberg.com>* Update packages/eslint-plugin/docs/rules/no-confusing-void-expression.mdxCo-authored-by: Josh Goldberg ✨ <git@joshuakgoldberg.com>* Update packages/eslint-plugin/src/rules/no-confusing-void-expression.tsCo-authored-by: Josh Goldberg ✨ <git@joshuakgoldberg.com>* format* format* add missing test* remove code to handle multiple call signatures* figure out the case of multiple call signatures in a function expression---------Co-authored-by: Josh Goldberg ✨ <git@joshuakgoldberg.com>
1 parentac1f632 commite2e9ffc

File tree

9 files changed

+869
-33
lines changed

9 files changed

+869
-33
lines changed

‎eslint.config.mjs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,14 @@ export default tseslint.config(
105105
},
106106
linterOptions:{reportUnusedDisableDirectives:'error'},
107107
rules:{
108-
// TODO: https://github.com/typescript-eslint/typescript-eslint/issues/8538
109-
'@typescript-eslint/no-confusing-void-expression':'off',
110-
111108
//
112109
// our plugin :D
113110
//
114111

112+
'@typescript-eslint/no-confusing-void-expression':[
113+
'error',
114+
{ignoreVoidReturningFunctions:true},
115+
],
115116
'@typescript-eslint/ban-ts-comment':[
116117
'error',
117118
{

‎packages/eslint-plugin/docs/rules/no-confusing-void-expression.mdx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,28 @@ function doSomething() {
123123
console.log(voidalert('Hello, world!'));
124124
```
125125

126+
###`ignoreVoidReturningFunctions`
127+
128+
Whether to ignore returns from functions with`void` return types when inside a function with a`void` return type.
129+
130+
Some projects prefer allowing functions that explicitly return`void` to return`void` expressions. Doing so allows more writing more succinct functions.
131+
132+
:::note
133+
This is technically risky as the`void`-returning function might actually be returning a value not seen by the type system.
134+
:::
135+
136+
```ts option='{ "ignoreVoidReturningFunctions": true }' showPlaygroundButton
137+
function foo():void {
138+
returnconsole.log();
139+
}
140+
141+
function onError(callback: ()=>void):void {
142+
callback();
143+
}
144+
145+
onError(()=>console.log('oops'));
146+
```
147+
126148
##When Not To Use It
127149

128150
The return type of a function can be inspected by going to its definition or hovering over it in an IDE.

‎packages/eslint-plugin/src/rules/no-confusing-void-expression.ts

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@ import {
1616
nullThrows,
1717
NullThrowsReasons,
1818
}from'../util';
19+
import{getParentFunctionNode}from'../util/getParentFunctionNode';
1920

2021
exporttypeOptions=[
2122
{
2223
ignoreArrowShorthand?:boolean;
2324
ignoreVoidOperator?:boolean;
25+
ignoreVoidReturningFunctions?:boolean;
2426
},
2527
];
2628

@@ -86,21 +88,28 @@ export default createRule<Options, MessageId>({
8688
description:
8789
'Whether to ignore returns that start with the `void` operator.',
8890
},
91+
ignoreVoidReturningFunctions:{
92+
type:'boolean',
93+
description:
94+
'Whether to ignore returns from functions with explicit `void` return types and functions with contextual `void` return types.',
95+
},
8996
},
9097
},
9198
],
9299
},
93100
defaultOptions:[{ignoreArrowShorthand:false,ignoreVoidOperator:false}],
94101

95102
create(context,[options]){
103+
constservices=getParserServices(context);
104+
constchecker=services.program.getTypeChecker();
105+
96106
return{
97107
'AwaitExpression, CallExpression, TaggedTemplateExpression'(
98108
node:
99109
|TSESTree.AwaitExpression
100110
|TSESTree.CallExpression
101111
|TSESTree.TaggedTemplateExpression,
102112
):void{
103-
constservices=getParserServices(context);
104113
consttype=getConstrainedTypeAtLocation(services,node);
105114
if(!tsutils.isTypeFlagSet(type,ts.TypeFlags.VoidLike)){
106115
// not a void expression
@@ -122,6 +131,14 @@ export default createRule<Options, MessageId>({
122131
if(invalidAncestor.type===AST_NODE_TYPES.ArrowFunctionExpression){
123132
// handle arrow function shorthand
124133

134+
if(options.ignoreVoidReturningFunctions){
135+
constreturnsVoid=isVoidReturningFunctionNode(invalidAncestor);
136+
137+
if(returnsVoid){
138+
return;
139+
}
140+
}
141+
125142
if(options.ignoreVoidOperator){
126143
// handle wrapping with `void`
127144
returncontext.report({
@@ -177,6 +194,18 @@ export default createRule<Options, MessageId>({
177194
if(invalidAncestor.type===AST_NODE_TYPES.ReturnStatement){
178195
// handle return statement
179196

197+
if(options.ignoreVoidReturningFunctions){
198+
constfunctionNode=getParentFunctionNode(invalidAncestor);
199+
200+
if(functionNode){
201+
constreturnsVoid=isVoidReturningFunctionNode(functionNode);
202+
203+
if(returnsVoid){
204+
return;
205+
}
206+
}
207+
}
208+
180209
if(options.ignoreVoidOperator){
181210
// handle wrapping with `void`
182211
returncontext.report({
@@ -380,8 +409,6 @@ export default createRule<Options, MessageId>({
380409
functioncanFix(
381410
node:ReturnStatementWithArgument|TSESTree.ArrowFunctionExpression,
382411
):boolean{
383-
constservices=getParserServices(context);
384-
385412
consttargetNode=
386413
node.type===AST_NODE_TYPES.ReturnStatement
387414
?node.argument
@@ -390,5 +417,53 @@ export default createRule<Options, MessageId>({
390417
consttype=getConstrainedTypeAtLocation(services,targetNode);
391418
returntsutils.isTypeFlagSet(type,ts.TypeFlags.VoidLike);
392419
}
420+
421+
functionisFunctionReturnTypeIncludesVoid(functionType:ts.Type):boolean{
422+
constcallSignatures=tsutils.getCallSignaturesOfType(functionType);
423+
424+
returncallSignatures.some(signature=>{
425+
constreturnType=signature.getReturnType();
426+
427+
returntsutils
428+
.unionTypeParts(returnType)
429+
.some(tsutils.isIntrinsicVoidType);
430+
});
431+
}
432+
433+
functionisVoidReturningFunctionNode(
434+
functionNode:
435+
|TSESTree.ArrowFunctionExpression
436+
|TSESTree.FunctionDeclaration
437+
|TSESTree.FunctionExpression,
438+
):boolean{
439+
// Game plan:
440+
// - If the function node has a type annotation, check if it includes `void`.
441+
// - If it does then the function is safe to return `void` expressions in.
442+
// - Otherwise, check if the function is a function-expression or an arrow-function.
443+
// - If it is, get its contextual type and bail if we cannot.
444+
// - Return based on whether the contextual type includes `void` or not
445+
446+
constfunctionTSNode=services.esTreeNodeToTSNodeMap.get(functionNode);
447+
448+
if(functionTSNode.type){
449+
constreturnType=checker.getTypeFromTypeNode(functionTSNode.type);
450+
451+
returntsutils
452+
.unionTypeParts(returnType)
453+
.some(tsutils.isIntrinsicVoidType);
454+
}
455+
456+
if(ts.isExpression(functionTSNode)){
457+
constfunctionType=checker.getContextualType(functionTSNode);
458+
459+
if(functionType){
460+
returntsutils
461+
.unionTypeParts(functionType)
462+
.some(isFunctionReturnTypeIncludesVoid);
463+
}
464+
}
465+
466+
returnfalse;
467+
}
393468
},
394469
});

‎packages/eslint-plugin/src/rules/no-unsafe-return.ts

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
importtype{TSESTree}from'@typescript-eslint/utils';
22

3-
import{AST_NODE_TYPES}from'@typescript-eslint/utils';
43
import*astsutilsfrom'ts-api-utils';
54
import*astsfrom'typescript';
65

@@ -18,6 +17,7 @@ import {
1817
isTypeUnknownType,
1918
isUnsafeAssignment,
2019
}from'../util';
20+
import{getParentFunctionNode}from'../util/getParentFunctionNode';
2121

2222
exportdefaultcreateRule({
2323
name:'no-unsafe-return',
@@ -49,31 +49,6 @@ export default createRule({
4949
'noImplicitThis',
5050
);
5151

52-
functiongetParentFunctionNode(
53-
node:TSESTree.Node,
54-
):
55-
|TSESTree.ArrowFunctionExpression
56-
|TSESTree.FunctionDeclaration
57-
|TSESTree.FunctionExpression
58-
|null{
59-
letcurrent=node.parent;
60-
while(current){
61-
if(
62-
current.type===AST_NODE_TYPES.ArrowFunctionExpression||
63-
current.type===AST_NODE_TYPES.FunctionDeclaration||
64-
current.type===AST_NODE_TYPES.FunctionExpression
65-
){
66-
returncurrent;
67-
}
68-
69-
current=current.parent;
70-
}
71-
72-
// this shouldn't happen in correct code, but someone may attempt to parse bad code
73-
// the parser won't error, so we shouldn't throw here
74-
/* istanbul ignore next */returnnull;
75-
}
76-
7752
functioncheckReturn(
7853
returnNode:TSESTree.Node,
7954
reportingNode:TSESTree.Node=returnNode,
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
importtype{TSESTree}from'@typescript-eslint/utils';
2+
3+
import{AST_NODE_TYPES}from'@typescript-eslint/utils';
4+
5+
exportfunctiongetParentFunctionNode(
6+
node:TSESTree.Node,
7+
):
8+
|TSESTree.ArrowFunctionExpression
9+
|TSESTree.FunctionDeclaration
10+
|TSESTree.FunctionExpression
11+
|null{
12+
letcurrent=node.parent;
13+
while(current){
14+
if(
15+
current.type===AST_NODE_TYPES.ArrowFunctionExpression||
16+
current.type===AST_NODE_TYPES.FunctionDeclaration||
17+
current.type===AST_NODE_TYPES.FunctionExpression
18+
){
19+
returncurrent;
20+
}
21+
22+
current=current.parent;
23+
}
24+
25+
// this shouldn't happen in correct code, but someone may attempt to parse bad code
26+
// the parser won't error, so we shouldn't throw here
27+
/* istanbul ignore next */returnnull;
28+
}

‎packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-confusing-void-expression.shot

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp