-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
feat(eslint-plugin): [no-confusing-void-expression] add ignoreVoidInVoid option to void in void situation #8632
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3492cb6
7bc38a4
8c4b72f
028aa93
7babaf9
0030a7b
3ed4122
b76656a
27d8d5f
39c2080
0f88a30
0d547c9
a7e7de7
d72be7b
48eb394
8a7a8e7
f9c30dc
024993f
f392b39
ea12937
b04a9be
9ed6061
896a85c
6cc46ad
09846c0
7957082
369f4e7
5428e4b
a522d26
edbfc36
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,11 +14,16 @@ import { | |
nullThrows, | ||
NullThrowsReasons, | ||
} from '../util'; | ||
import { | ||
ancestorHasReturnType, | ||
isValidFunctionExpressionReturnType, | ||
} from '../util/explicitReturnTypeUtils'; | ||
|
||
export type Options = [ | ||
{ | ||
ignoreArrowShorthand?: boolean; | ||
ignoreVoidOperator?: boolean; | ||
ignoreVoidInVoid?: boolean; | ||
}, | ||
]; | ||
|
||
|
@@ -32,6 +37,26 @@ export type MessageId = | |
| 'invalidVoidExprWrapVoid' | ||
| 'voidExprWrapVoid'; | ||
|
||
function findFunction( | ||
node: TSESTree.Node | undefined, | ||
): | ||
| TSESTree.FunctionExpression | ||
| TSESTree.ArrowFunctionExpression | ||
| TSESTree.FunctionDeclaration | ||
| null { | ||
if (!node) { | ||
return null; | ||
} | ||
if ( | ||
node.type === AST_NODE_TYPES.FunctionExpression || | ||
node.type === AST_NODE_TYPES.ArrowFunctionExpression || | ||
node.type === AST_NODE_TYPES.FunctionDeclaration | ||
) { | ||
return node; | ||
} | ||
return findFunction(node.parent); | ||
} | ||
|
||
export default createRule<Options, MessageId>({ | ||
name: 'no-confusing-void-expression', | ||
meta: { | ||
|
@@ -72,6 +97,7 @@ export default createRule<Options, MessageId>({ | |
properties: { | ||
ignoreArrowShorthand: { type: 'boolean' }, | ||
ignoreVoidOperator: { type: 'boolean' }, | ||
ignoreVoidInVoid: { type: 'boolean' }, | ||
}, | ||
additionalProperties: false, | ||
}, | ||
|
@@ -80,8 +106,13 @@ export default createRule<Options, MessageId>({ | |
fixable: 'code', | ||
hasSuggestions: true, | ||
}, | ||
defaultOptions: [{ ignoreArrowShorthand: false, ignoreVoidOperator: false }], | ||
|
||
defaultOptions: [ | ||
{ | ||
ignoreArrowShorthand: false, | ||
ignoreVoidOperator: false, | ||
ignoreVoidInVoid: false, | ||
}, | ||
], | ||
create(context, [options]) { | ||
return { | ||
'AwaitExpression, CallExpression, TaggedTemplateExpression'( | ||
|
@@ -98,6 +129,7 @@ export default createRule<Options, MessageId>({ | |
} | ||
|
||
const invalidAncestor = findInvalidAncestor(node); | ||
|
||
if (invalidAncestor == null) { | ||
// void expression is in valid position | ||
return; | ||
|
@@ -112,6 +144,12 @@ export default createRule<Options, MessageId>({ | |
if (invalidAncestor.type === AST_NODE_TYPES.ArrowFunctionExpression) { | ||
// handle arrow function shorthand | ||
|
||
if (options.ignoreVoidInVoid) { | ||
if (hasValidReturnType(invalidAncestor)) { | ||
return; | ||
} | ||
} | ||
|
||
if (options.ignoreVoidOperator) { | ||
// handle wrapping with `void` | ||
return context.report({ | ||
|
@@ -167,6 +205,14 @@ export default createRule<Options, MessageId>({ | |
if (invalidAncestor.type === AST_NODE_TYPES.ReturnStatement) { | ||
// handle return statement | ||
|
||
if (options.ignoreVoidInVoid) { | ||
const functionNode = findFunction(invalidAncestor); | ||
|
||
if (hasValidReturnType(functionNode)) { | ||
return; | ||
} | ||
} | ||
|
||
if (options.ignoreVoidOperator) { | ||
// handle wrapping with `void` | ||
return context.report({ | ||
|
@@ -376,5 +422,44 @@ export default createRule<Options, MessageId>({ | |
const type = getConstrainedTypeAtLocation(services, targetNode); | ||
return tsutils.isTypeFlagSet(type, ts.TypeFlags.VoidLike); | ||
} | ||
|
||
function hasValidReturnType( | ||
node: | ||
| TSESTree.FunctionExpression | ||
| TSESTree.ArrowFunctionExpression | ||
| TSESTree.FunctionDeclaration | ||
| null, | ||
): boolean { | ||
const services = getParserServices(context); | ||
|
||
if (node != null) { | ||
const functionTSNode = services.esTreeNodeToTSNodeMap.get(node); | ||
const functionType = services.getTypeAtLocation(node); | ||
|
||
if (functionTSNode.type) { | ||
const signatures = tsutils.getCallSignaturesOfType(functionType); | ||
|
||
return !signatures.every(signature => | ||
tsutils.isTypeFlagSet( | ||
signature.getReturnType(), | ||
ts.TypeFlags.Any | ts.TypeFlags.Unknown, | ||
), | ||
); | ||
} | ||
|
||
if ( | ||
((node.type === AST_NODE_TYPES.FunctionExpression || | ||
node.type === AST_NODE_TYPES.ArrowFunctionExpression) && | ||
isValidFunctionExpressionReturnType(node, { | ||
allowTypedFunctionExpressions: true, | ||
})) || | ||
ancestorHasReturnType(node) | ||
) { | ||
return true; | ||
} | ||
Comment on lines
+450
to
+459
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we check that function signature don't contain type HigherOrderType = () => any;
(): HigherOrderType => () => console.log();
const x: HigherOrderType = () => console.log(); |
||
} | ||
|
||
return false; | ||
} | ||
}, | ||
}); |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Bug] The following code won't be reported, because
findFunction
doesn't stop on function declarations:playground