📄 no-confusing-void-expression.ts
¶
📊 Analysis Summary¶
Metric | Count |
---|---|
🔧 Functions | 7 |
📦 Imports | 13 |
📊 Variables & Constants | 9 |
📑 Type Aliases | 4 |
📚 Table of Contents¶
🛠️ File Location:¶
📂 packages/eslint-plugin/src/rules/no-confusing-void-expression.ts
📦 Imports¶
Name | Source |
---|---|
TSESLint |
@typescript-eslint/utils |
TSESTree |
@typescript-eslint/utils |
AST_NODE_TYPES |
@typescript-eslint/utils |
MakeRequired |
../util |
createRule |
../util |
getConstrainedTypeAtLocation |
../util |
getParserServices |
../util |
isClosingParenToken |
../util |
isOpeningParenToken |
../util |
isParenthesized |
../util |
nullThrows |
../util |
NullThrowsReasons |
../util |
getParentFunctionNode |
../util/getParentFunctionNode |
Variables & Constants¶
Name | Type | Kind | Value | Exported |
---|---|---|---|---|
newNodeText |
string |
const | void ${nodeText} |
✗ |
arrowFunction |
MakeRequired<TSESTree.ReturnStatement, "argument"> |
const | invalidAncestor |
✗ |
arrowBody |
TSESTree.ReturnStatement |
const | arrowFunction.body |
✗ |
newArrowBodyText |
string |
const | { ${arrowBodyText}; } |
✗ |
returnValue |
any |
const | invalidAncestor.argument |
✗ |
newReturnStmtText |
string |
let/var | ${returnValueText}; |
✗ |
returnValue |
any |
const | invalidAncestor.argument |
✗ |
newReturnStmtText |
string |
let/var | ${returnValueText}; return; |
✗ |
targetNode |
any |
const | `node.type === AST_NODE_TYPES.ReturnStatement | |
? node.argument | ||||
: node.body` | ✗ |
Functions¶
wrapVoidFix(fixer: TSESLint.RuleFixer): TSESLint.RuleFix
¶
Code
- Parameters:
fixer: TSESLint.RuleFixer
- Return Type:
TSESLint.RuleFix
- Calls:
context.sourceCode.getText
fixer.replaceText
findInvalidAncestor(node: TSESTree.Node): InvalidAncestor | null
¶
Code
function findInvalidAncestor(node: TSESTree.Node): InvalidAncestor | null {
const parent = nullThrows(node.parent, NullThrowsReasons.MissingParent);
if (
parent.type === AST_NODE_TYPES.SequenceExpression &&
node !== parent.expressions[parent.expressions.length - 1]
) {
return null;
}
if (parent.type === AST_NODE_TYPES.ExpressionStatement) {
// e.g. `{ console.log("foo"); }`
// this is always valid
return null;
}
if (
parent.type === AST_NODE_TYPES.LogicalExpression &&
parent.right === node
) {
// e.g. `x && console.log(x)`
// this is valid only if the next ancestor is valid
return findInvalidAncestor(parent);
}
if (
parent.type === AST_NODE_TYPES.ConditionalExpression &&
(parent.consequent === node || parent.alternate === node)
) {
// e.g. `cond ? console.log(true) : console.log(false)`
// this is valid only if the next ancestor is valid
return findInvalidAncestor(parent);
}
if (
parent.type === AST_NODE_TYPES.ArrowFunctionExpression &&
// e.g. `() => console.log("foo")`
// this is valid with an appropriate option
options.ignoreArrowShorthand
) {
return null;
}
if (
parent.type === AST_NODE_TYPES.UnaryExpression &&
parent.operator === 'void' &&
// e.g. `void console.log("foo")`
// this is valid with an appropriate option
options.ignoreVoidOperator
) {
return null;
}
if (parent.type === AST_NODE_TYPES.ChainExpression) {
// e.g. `console?.log('foo')`
return findInvalidAncestor(parent);
}
// Any other parent is invalid.
// We can assume a return statement will have an argument.
return parent as InvalidAncestor;
}
-
JSDoc:
/** * Inspects the void expression's ancestors and finds closest invalid one. * By default anything other than an ExpressionStatement is invalid. * Parent expressions which can be used for their short-circuiting behavior * are ignored and their parents are checked instead. * @param node The void expression node to check. * @returns Invalid ancestor node if it was found. `null` otherwise. */
-
Parameters:
node: TSESTree.Node
- Return Type:
InvalidAncestor | null
- Calls:
nullThrows (from ../util)
findInvalidAncestor
- Internal Comments:
// e.g. `{ console.log("foo"); }` // this is always valid // e.g. `x && console.log(x)` // this is valid only if the next ancestor is valid (x2) // e.g. `cond ? console.log(true) : console.log(false)` // e.g. `() => console.log("foo")` (x2) // this is valid with an appropriate option (x4) // e.g. `void console.log("foo")` (x2) // e.g. `console?.log('foo')` // Any other parent is invalid. // We can assume a return statement will have an argument.
isFinalReturn(node: TSESTree.ReturnStatement): boolean
¶
Code
function isFinalReturn(node: TSESTree.ReturnStatement): boolean {
// the parent must be a block
const block = nullThrows(node.parent, NullThrowsReasons.MissingParent);
if (block.type !== AST_NODE_TYPES.BlockStatement) {
// e.g. `if (cond) return;` (not in a block)
return false;
}
// the block's parent must be a function
const blockParent = nullThrows(
block.parent,
NullThrowsReasons.MissingParent,
);
if (
![
AST_NODE_TYPES.ArrowFunctionExpression,
AST_NODE_TYPES.FunctionDeclaration,
AST_NODE_TYPES.FunctionExpression,
].includes(blockParent.type)
) {
// e.g. `if (cond) { return; }`
// not in a top-level function block
return false;
}
// must be the last child of the block
if (block.body.indexOf(node) < block.body.length - 1) {
// not the last statement in the block
return false;
}
return true;
}
-
JSDoc:
-
Parameters:
node: TSESTree.ReturnStatement
- Return Type:
boolean
- Calls:
nullThrows (from ../util)
[ AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionDeclaration, AST_NODE_TYPES.FunctionExpression, ].includes
block.body.indexOf
- Internal Comments:
isPreventingASI(node: TSESTree.Expression): boolean
¶
Code
-
JSDoc:
-
Parameters:
node: TSESTree.Expression
- Return Type:
boolean
- Calls:
nullThrows (from ../util)
context.sourceCode.getFirstToken
NullThrowsReasons.MissingToken
['(', '[', '
'].includes`
canFix(node: ReturnStatementWithArgument | TSESTree.ArrowFunctionExpression): boolean
¶
Code
function canFix(
node: ReturnStatementWithArgument | TSESTree.ArrowFunctionExpression,
): boolean {
const targetNode =
node.type === AST_NODE_TYPES.ReturnStatement
? node.argument
: node.body;
const type = getConstrainedTypeAtLocation(services, targetNode);
return tsutils.isTypeFlagSet(type, ts.TypeFlags.VoidLike);
}
- Parameters:
node: ReturnStatementWithArgument | TSESTree.ArrowFunctionExpression
- Return Type:
boolean
- Calls:
getConstrainedTypeAtLocation (from ../util)
tsutils.isTypeFlagSet
isFunctionReturnTypeIncludesVoid(functionType: ts.Type): boolean
¶
Code
function isFunctionReturnTypeIncludesVoid(functionType: ts.Type): boolean {
const callSignatures = tsutils.getCallSignaturesOfType(functionType);
return callSignatures.some(signature => {
const returnType = signature.getReturnType();
return tsutils
.unionConstituents(returnType)
.some(tsutils.isIntrinsicVoidType);
});
}
- Parameters:
functionType: ts.Type
- Return Type:
boolean
- Calls:
tsutils.getCallSignaturesOfType
callSignatures.some
signature.getReturnType
tsutils .unionConstituents(returnType) .some
`isVoidReturningFunctionNode(functionNode: | TSESTree.ArrowFunctionExpression¶
| TSESTree.FunctionDeclaration
| TSESTree.FunctionExpression): boolean`
Code
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
.unionConstituents(returnType)
.some(tsutils.isIntrinsicVoidType);
}
if (ts.isExpression(functionTSNode)) {
const functionType = checker.getContextualType(functionTSNode);
if (functionType) {
return tsutils
.unionConstituents(functionType)
.some(isFunctionReturnTypeIncludesVoid);
}
}
return false;
}
- Parameters:
functionNode: | TSESTree.ArrowFunctionExpression | TSESTree.FunctionDeclaration | TSESTree.FunctionExpression
- Return Type:
boolean
- Calls:
services.esTreeNodeToTSNodeMap.get
checker.getTypeFromTypeNode
tsutils .unionConstituents(returnType) .some
ts.isExpression
checker.getContextualType
tsutils .unionConstituents(functionType) .some
- Internal Comments:
// Game plan: (x2) // - If the function node has a type annotation, check if it includes `void`. (x2) // - If it does then the function is safe to return `void` expressions in. (x2) // - Otherwise, check if the function is a function-expression or an arrow-function. (x2) // - If it is, get its contextual type and bail if we cannot. (x2) // - Return based on whether the contextual type includes `void` or not (x2)
Type Aliases¶
Options
¶
type Options = [
{
ignoreArrowShorthand?: boolean;
ignoreVoidOperator?: boolean;
ignoreVoidReturningFunctions?: boolean;
},
];
MessageId
¶
type MessageId = | 'invalidVoidExpr'
| 'invalidVoidExprArrow'
| 'invalidVoidExprArrowWrapVoid'
| 'invalidVoidExprReturn'
| 'invalidVoidExprReturnLast'
| 'invalidVoidExprReturnWrapVoid'
| 'invalidVoidExprWrapVoid'
| 'voidExprWrapVoid';
ReturnStatementWithArgument
¶
InvalidAncestor
¶
type InvalidAncestor = | Exclude<TSESTree.Node, TSESTree.ReturnStatement>
| ReturnStatementWithArgument;