Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork2.8k
feat(eslint-plugin): [no-confusing-void-expression] add an option to ignore void<->void#10067
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
27126ca
98830b9
3830a24
5b9655d
e47395e
4d0bf7c
117d244
b4be799
1c49780
5dc7600
2c26323
6800dba
5f7833f
37201b8
5e235c8
a8ca492
60dc04c
3b66043
b788954
d735247
c5f5e12
b61ab1f
af9bf87
7ac4a37
dcbb392
1a52c22
f190b15
8b85c14
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -16,11 +16,13 @@ import { | ||
nullThrows, | ||
NullThrowsReasons, | ||
} from '../util'; | ||
import { getParentFunctionNode } from '../util/getParentFunctionNode'; | ||
export type Options = [ | ||
{ | ||
ignoreArrowShorthand?: boolean; | ||
ignoreVoidOperator?: boolean; | ||
ignoreVoidReturningFunctions?: boolean; | ||
}, | ||
]; | ||
@@ -86,21 +88,28 @@ export default createRule<Options, MessageId>({ | ||
description: | ||
'Whether to ignore returns that start with the `void` operator.', | ||
}, | ||
ignoreVoidReturningFunctions: { | ||
type: 'boolean', | ||
description: | ||
'Whether to ignore returns from functions with explicit `void` return types and functions with contextual `void` return types.', | ||
}, | ||
}, | ||
}, | ||
], | ||
}, | ||
defaultOptions: [{ ignoreArrowShorthand: false, ignoreVoidOperator: false }], | ||
create(context, [options]) { | ||
const services = getParserServices(context); | ||
const checker = services.program.getTypeChecker(); | ||
return { | ||
'AwaitExpression, CallExpression, TaggedTemplateExpression'( | ||
node: | ||
| TSESTree.AwaitExpression | ||
| TSESTree.CallExpression | ||
| TSESTree.TaggedTemplateExpression, | ||
): void { | ||
const type = getConstrainedTypeAtLocation(services, node); | ||
if (!tsutils.isTypeFlagSet(type, ts.TypeFlags.VoidLike)) { | ||
// not a void expression | ||
@@ -122,6 +131,14 @@ export default createRule<Options, MessageId>({ | ||
if (invalidAncestor.type === AST_NODE_TYPES.ArrowFunctionExpression) { | ||
// handle arrow function shorthand | ||
if (options.ignoreVoidReturningFunctions) { | ||
const returnsVoid = isVoidReturningFunctionNode(invalidAncestor); | ||
if (returnsVoid) { | ||
return; | ||
} | ||
} | ||
if (options.ignoreVoidOperator) { | ||
// handle wrapping with `void` | ||
return context.report({ | ||
@@ -177,6 +194,18 @@ export default createRule<Options, MessageId>({ | ||
if (invalidAncestor.type === AST_NODE_TYPES.ReturnStatement) { | ||
// handle return statement | ||
if (options.ignoreVoidReturningFunctions) { | ||
const functionNode = getParentFunctionNode(invalidAncestor); | ||
if (functionNode) { | ||
const returnsVoid = isVoidReturningFunctionNode(functionNode); | ||
if (returnsVoid) { | ||
return; | ||
} | ||
} | ||
} | ||
if (options.ignoreVoidOperator) { | ||
// handle wrapping with `void` | ||
return context.report({ | ||
@@ -380,8 +409,6 @@ export default createRule<Options, MessageId>({ | ||
function canFix( | ||
node: ReturnStatementWithArgument | TSESTree.ArrowFunctionExpression, | ||
): boolean { | ||
const targetNode = | ||
node.type === AST_NODE_TYPES.ReturnStatement | ||
? node.argument | ||
@@ -390,5 +417,53 @@ export default createRule<Options, MessageId>({ | ||
const type = getConstrainedTypeAtLocation(services, targetNode); | ||
return tsutils.isTypeFlagSet(type, ts.TypeFlags.VoidLike); | ||
} | ||
function isFunctionReturnTypeIncludesVoid(functionType: ts.Type): boolean { | ||
const callSignatures = tsutils.getCallSignaturesOfType(functionType); | ||
return callSignatures.some(signature => { | ||
JoshuaKGoldberg marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
const returnType = signature.getReturnType(); | ||
return tsutils | ||
.unionTypeParts(returnType) | ||
.some(tsutils.isIntrinsicVoidType); | ||
}); | ||
} | ||
function isVoidReturningFunctionNode( | ||
functionNode: | ||
| TSESTree.ArrowFunctionExpression | ||
| TSESTree.FunctionDeclaration | ||
| TSESTree.FunctionExpression, | ||
): boolean { | ||
// Game plan: | ||
// - If the function node has a type annotation, check if it includes `void`. | ||
// - If it does then the function is safe to return `void` expressions in. | ||
// - Otherwise, check if the function is a function-expression or an arrow-function. | ||
// - If it is, get its contextual type and bail if we cannot. | ||
// - Return based on whether the contextual type includes `void` or not | ||
const functionTSNode = services.esTreeNodeToTSNodeMap.get(functionNode); | ||
if (functionTSNode.type) { | ||
const returnType = checker.getTypeFromTypeNode(functionTSNode.type); | ||
return tsutils | ||
.unionTypeParts(returnType) | ||
.some(tsutils.isIntrinsicVoidType); | ||
JoshuaKGoldberg marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
} | ||
if (ts.isExpression(functionTSNode)) { | ||
const functionType = checker.getContextualType(functionTSNode); | ||
if (functionType) { | ||
return tsutils | ||
.unionTypeParts(functionType) | ||
.some(isFunctionReturnTypeIncludesVoid); | ||
} | ||
} | ||
return false; | ||
} | ||
}, | ||
}); |
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. [Praise] I like the reuse here 🙂 nice. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import type { TSESTree } from '@typescript-eslint/utils'; | ||
import { AST_NODE_TYPES } from '@typescript-eslint/utils'; | ||
export function getParentFunctionNode( | ||
node: TSESTree.Node, | ||
): | ||
| TSESTree.ArrowFunctionExpression | ||
| TSESTree.FunctionDeclaration | ||
| TSESTree.FunctionExpression | ||
| null { | ||
let current = node.parent; | ||
while (current) { | ||
if ( | ||
current.type === AST_NODE_TYPES.ArrowFunctionExpression || | ||
current.type === AST_NODE_TYPES.FunctionDeclaration || | ||
current.type === AST_NODE_TYPES.FunctionExpression | ||
) { | ||
return current; | ||
} | ||
current = current.parent; | ||
} | ||
// this shouldn't happen in correct code, but someone may attempt to parse bad code | ||
// the parser won't error, so we shouldn't throw here | ||
/* istanbul ignore next */ return null; | ||
} |
Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.