📄 no-misused-promises¶
📊 Analysis Summary¶
| Metric | Count |
|---|---|
| 🔧 Functions | 32 |
| 📦 Imports | 14 |
| 📐 Interfaces | 2 |
| 📑 Type Aliases | 3 |
📚 Table of Contents¶
🛠️ File Location:¶
📂 packages/eslint-plugin/src/rules/no-misused-promises.ts
📤 Default Export¶
| Property | Value |
|---|---|
name |
'no-misused-promises' |
meta.type |
'problem' |
meta.docs.description |
'Disallow Promises in places not designed to handle them' |
meta.docs.recommended |
'recommended' |
meta.docs.requiresTypeChecking |
true |
meta.messages.conditional |
'Expected non-Promise value in a boolean conditional.' |
meta.messages.predicate |
'Expected a non-Promise value to be returned.' |
meta.messages.spread |
'Expected a non-Promise value to be spread in an object.' |
meta.messages.voidReturnArgument |
'Promise returned in function argument where a void return was expected.' |
meta.messages.voidReturnAttribute |
'Promise-returning function provided to attribute where a void return was expected.' |
meta.messages.voidReturnInheritedMethod |
"Promise-returning method provided where a void return was expected by extended/implemented type '{{ heritageTypeName... |
meta.messages.voidReturnProperty |
'Promise-returning function provided to property where a void return was expected.' |
meta.messages.voidReturnReturnValue |
'Promise-returning function provided to return value where a void return was expected.' |
meta.messages.voidReturnVariable |
'Promise-returning function provided to variable where a void return was expected.' |
meta.schema |
[ { type: 'object', additionalProperties: false, properties: { checksConditionals: { description: 'Whether to warn wh... |
defaultOptions |
[ { checksConditionals: true, checksSpreads: true, checksVoidReturn: true, }, ] |
Entry point: create — documented under Functions.
📦 Imports¶
| Name | Source |
|---|---|
TSESLint |
@typescript-eslint/utils |
TSESTree |
@typescript-eslint/utils |
AST_NODE_TYPES |
@typescript-eslint/utils |
createRule |
../util |
getConstrainedTypeAtLocation |
../util |
getFunctionHeadLoc |
../util |
getParserServices |
../util |
isArrayMethodCallWithPredicate |
../util |
isFunction |
../util |
isPromiseLike |
../util |
isRestParameterDeclaration |
../util |
nullThrows |
../util |
NullThrowsReasons |
../util |
parseFinallyCall |
../util/promiseUtils |
Functions¶
create(context: any, [{ checksConditionals, checksSp…: any): any¶
Parameters:
contextany[{ checksConditionals, checksSpreads, checksVoidReturn }]any
Returns: any
Calls:
getParserServices (from ../util)services.program.getTypeCheckernormalizeFlagUnionsOptioncheckConditionalparseChecksVoidReturnnode.typeAnnotation.members.somecheckedNodes.hascheckedNodes.addservices.esTreeNodeToTSNodeMap.getisAlwaysThenablecontext.reportisSometimesThenablehasMatchingPromiseTypeArgumentparent.arguments.atisArrayMethodCallWithPredicate (from ../util)returnsThenableisPromiseFinallyMethodvoidFunctionArgumentsnode.arguments.entriesvoidArgs.hasservices.getTypeAtLocationisVoidReturningFunctionTypehasWellKnownSymbolWithThenableReturnchecker.getTypeAtLocationhasWellKnownSymbolWithVoidReturnisPossiblyFunctionTypets.isPropertyAssignmentchecker.getContextualTypeisFunction (from ../util)getFunctionHeadLoc (from ../util)ts.isShorthandPropertyAssignmentts.isMethodDeclarationts.isComputedPropertyNamets.isObjectLiteralExpressiontsutils .unionConstituents(objType) .map(t => checker.getPropertyOfType(t, tsNode.name.getText())) .findchecker.getTypeOfSymbolAtLocationcomplex_call_20907nullThrows (from ../util)parseFinallyCall (from ../util/promiseUtils)isPromiseLike (from ../util)getConstrainedTypeAtLocation (from ../util)getHeritageTypesnodeMember.name?.getTextservices.tsNodeToESTreeNodeMap.getisStaticMembercheckHeritageTypeForMemberReturningVoidgetMemberIfExistschecker.typeToString
Internal Comments:
/**
* A syntactic check to see if an annotated type is maybe a function type.
* This is a perf optimization to help avoid requesting types where possible
*/
/**
* This function analyzes the type of a node and checks if it is a Promise in a boolean conditional.
* It uses recursion when checking nested logical operators.
* @param node The AST node to check.
* @param isTestExpr Whether the node is a descendant of a test expression.
*/
// prevent checking the same node multiple times
// ignore the left operand for nullish coalescing expressions not in a context of a test expression
// we ignore the right operand when not in a context of a test expression
// none -> Report `Promise` but not `Promise | ...` (x3)
// (x2)
// all -> Report `Promise` and `Promise | ...`
// strict -> Report `Promise<T> | T` but not `Promise<T> | NotT`
// syntactically ignore some known-good cases to avoid touching type info (x3)
// Below condition isn't satisfied unless something goes wrong,
// but is needed for type checking.
// 'node' does not include class method declaration so 'obj' is
// always an object literal expression, but after converting 'node'
// to TypeScript AST, its type includes MethodDeclaration which
// does include the case of class method declaration.
// Call/construct/index signatures don't have names. TS allows call signatures to mismatch,
// and construct signatures can't be async.
// TODO - Once we're able to use `checker.isTypeAssignableTo` (v8), we can check an index
// signature here against its compatible index signatures in `heritageTypes`
/**
* Checks `heritageType` for a member named `memberName` that returns void; reports the
* 'voidReturnInheritedMethod' message if found.
* @param nodeMember Node member that returns a Promise
* @param heritageType Heritage type to check against
* @param memberName Name of the member to check for
*/
Code
create(context, [{ checksConditionals, checksSpreads, checksVoidReturn }]) {
const services = getParserServices(context);
const checker = services.program.getTypeChecker();
const checkedNodes = new Set<TSESTree.Node>();
const flagUnionsOption = normalizeFlagUnionsOption(checksConditionals);
const conditionalChecks: TSESLint.RuleListener = {
'CallExpression > MemberExpression': checkArrayPredicates,
ConditionalExpression: checkTestConditional,
DoWhileStatement: checkTestConditional,
ForStatement: checkTestConditional,
IfStatement: checkTestConditional,
LogicalExpression: checkConditional,
'UnaryExpression[operator="!"]'(node: TSESTree.UnaryExpression) {
checkConditional(node.argument, true);
},
WhileStatement: checkTestConditional,
};
checksVoidReturn = parseChecksVoidReturn(checksVoidReturn);
const voidReturnChecks: TSESLint.RuleListener = checksVoidReturn
? {
...(checksVoidReturn.arguments && {
CallExpression: checkArguments,
NewExpression: checkArguments,
}),
...(checksVoidReturn.attributes && {
JSXAttribute: checkJSXAttribute,
}),
...(checksVoidReturn.inheritedMethods && {
ClassDeclaration: checkClassLikeOrInterfaceNode,
ClassExpression: checkClassLikeOrInterfaceNode,
TSInterfaceDeclaration: checkClassLikeOrInterfaceNode,
}),
...(checksVoidReturn.properties && {
Property: checkProperty,
}),
...(checksVoidReturn.returns && {
ReturnStatement: checkReturnStatement,
}),
...(checksVoidReturn.variables && {
AssignmentExpression: checkAssignment,
VariableDeclarator: checkVariableDeclaration,
}),
}
: {};
const spreadChecks: TSESLint.RuleListener = {
SpreadElement: checkSpread,
};
/**
* A syntactic check to see if an annotated type is maybe a function type.
* This is a perf optimization to help avoid requesting types where possible
*/
function isPossiblyFunctionType(node: TSESTree.TSTypeAnnotation): boolean {
switch (node.typeAnnotation.type) {
case AST_NODE_TYPES.TSConditionalType:
case AST_NODE_TYPES.TSConstructorType:
case AST_NODE_TYPES.TSFunctionType:
case AST_NODE_TYPES.TSImportType:
case AST_NODE_TYPES.TSIndexedAccessType:
case AST_NODE_TYPES.TSInferType:
case AST_NODE_TYPES.TSIntersectionType:
case AST_NODE_TYPES.TSQualifiedName:
case AST_NODE_TYPES.TSThisType:
case AST_NODE_TYPES.TSTypeOperator:
case AST_NODE_TYPES.TSTypeQuery:
case AST_NODE_TYPES.TSTypeReference:
case AST_NODE_TYPES.TSUnionType:
return true;
case AST_NODE_TYPES.TSTypeLiteral:
return node.typeAnnotation.members.some(
member =>
member.type === AST_NODE_TYPES.TSCallSignatureDeclaration ||
member.type === AST_NODE_TYPES.TSConstructSignatureDeclaration,
);
case AST_NODE_TYPES.TSAbstractKeyword:
case AST_NODE_TYPES.TSAnyKeyword:
case AST_NODE_TYPES.TSArrayType:
case AST_NODE_TYPES.TSAsyncKeyword:
case AST_NODE_TYPES.TSBigIntKeyword:
case AST_NODE_TYPES.TSBooleanKeyword:
case AST_NODE_TYPES.TSDeclareKeyword:
case AST_NODE_TYPES.TSExportKeyword:
case AST_NODE_TYPES.TSIntrinsicKeyword:
case AST_NODE_TYPES.TSLiteralType:
case AST_NODE_TYPES.TSMappedType:
case AST_NODE_TYPES.TSNamedTupleMember:
case AST_NODE_TYPES.TSNeverKeyword:
case AST_NODE_TYPES.TSNullKeyword:
case AST_NODE_TYPES.TSNumberKeyword:
case AST_NODE_TYPES.TSObjectKeyword:
case AST_NODE_TYPES.TSOptionalType:
case AST_NODE_TYPES.TSPrivateKeyword:
case AST_NODE_TYPES.TSProtectedKeyword:
case AST_NODE_TYPES.TSPublicKeyword:
case AST_NODE_TYPES.TSReadonlyKeyword:
case AST_NODE_TYPES.TSRestType:
case AST_NODE_TYPES.TSStaticKeyword:
case AST_NODE_TYPES.TSStringKeyword:
case AST_NODE_TYPES.TSSymbolKeyword:
case AST_NODE_TYPES.TSTemplateLiteralType:
case AST_NODE_TYPES.TSTupleType:
case AST_NODE_TYPES.TSTypePredicate:
case AST_NODE_TYPES.TSUndefinedKeyword:
case AST_NODE_TYPES.TSUnknownKeyword:
case AST_NODE_TYPES.TSVoidKeyword:
return false;
}
}
function checkTestConditional(
node:
| TSESTree.ConditionalExpression
| TSESTree.DoWhileStatement
| TSESTree.ForStatement
| TSESTree.IfStatement
| TSESTree.WhileStatement,
): void {
if (node.test) {
checkConditional(node.test, true);
}
}
/**
* This function analyzes the type of a node and checks if it is a Promise in a boolean conditional.
* It uses recursion when checking nested logical operators.
* @param node The AST node to check.
* @param isTestExpr Whether the node is a descendant of a test expression.
*/
function checkConditional(
node: TSESTree.Expression,
isTestExpr = false,
): void {
// prevent checking the same node multiple times
if (checkedNodes.has(node)) {
return;
}
checkedNodes.add(node);
if (node.type === AST_NODE_TYPES.LogicalExpression) {
// ignore the left operand for nullish coalescing expressions not in a context of a test expression
if (node.operator !== '??' || isTestExpr) {
checkConditional(node.left, isTestExpr);
}
// we ignore the right operand when not in a context of a test expression
if (isTestExpr) {
checkConditional(node.right, isTestExpr);
}
return;
}
const tsNode = services.esTreeNodeToTSNodeMap.get(node);
if (isAlwaysThenable(checker, tsNode)) {
context.report({
node,
messageId: 'conditional',
});
return;
}
if (
// none -> Report `Promise` but not `Promise | ...`
(flagUnionsOption === 'none' && isAlwaysThenable(checker, tsNode)) ||
//
// all -> Report `Promise` and `Promise | ...`
(flagUnionsOption === 'all' && isSometimesThenable(checker, tsNode)) ||
//
// strict -> Report `Promise<T> | T` but not `Promise<T> | NotT`
(flagUnionsOption === 'strict' &&
hasMatchingPromiseTypeArgument(checker, tsNode))
) {
context.report({
node,
messageId: 'conditional',
});
}
}
function checkArrayPredicates(node: TSESTree.MemberExpression): void {
const parent = node.parent;
if (parent.type === AST_NODE_TYPES.CallExpression) {
const callback = parent.arguments.at(0);
if (
callback &&
isArrayMethodCallWithPredicate(context, services, parent)
) {
const type = services.esTreeNodeToTSNodeMap.get(callback);
if (returnsThenable(checker, type)) {
context.report({
node: callback,
messageId: 'predicate',
});
}
}
}
}
function checkArguments(
node: TSESTree.CallExpression | TSESTree.NewExpression,
): void {
if (
node.type === AST_NODE_TYPES.CallExpression &&
isPromiseFinallyMethod(node)
) {
return;
}
const tsNode = services.esTreeNodeToTSNodeMap.get(node);
const voidArgs = voidFunctionArguments(checker, tsNode);
if (voidArgs.size === 0) {
return;
}
for (const [index, argument] of node.arguments.entries()) {
if (!voidArgs.has(index)) {
continue;
}
const tsNode = services.esTreeNodeToTSNodeMap.get(argument);
if (returnsThenable(checker, tsNode)) {
context.report({
node: argument,
messageId: 'voidReturnArgument',
});
}
}
}
function checkAssignment(node: TSESTree.AssignmentExpression): void {
const tsNode = services.esTreeNodeToTSNodeMap.get(node);
const varType = services.getTypeAtLocation(node.left);
if (!isVoidReturningFunctionType(checker, tsNode.left, varType)) {
return;
}
if (returnsThenable(checker, tsNode.right)) {
context.report({
node: node.right,
messageId: 'voidReturnVariable',
});
}
}
function checkVariableDeclaration(node: TSESTree.VariableDeclarator): void {
const tsNode = services.esTreeNodeToTSNodeMap.get(node);
if (tsNode.initializer == null || node.init == null) {
return;
}
if (
node.parent.kind === 'using' &&
hasWellKnownSymbolWithThenableReturn(
checker,
tsNode.initializer,
checker.getTypeAtLocation(tsNode.initializer),
'dispose',
)
) {
context.report({
node: node.init,
messageId: 'voidReturnVariable',
});
}
if (node.id.typeAnnotation == null) {
return;
}
const variableType = services.getTypeAtLocation(node.id);
if (
hasWellKnownSymbolWithVoidReturn(
checker,
tsNode.name,
variableType,
'dispose',
) &&
hasWellKnownSymbolWithThenableReturn(
checker,
tsNode.initializer,
checker.getTypeAtLocation(tsNode.initializer),
'dispose',
)
) {
context.report({
node: node.init,
messageId: 'voidReturnVariable',
});
}
// syntactically ignore some known-good cases to avoid touching type info
if (!isPossiblyFunctionType(node.id.typeAnnotation)) {
return;
}
const varType = services.getTypeAtLocation(node.id);
if (!isVoidReturningFunctionType(checker, tsNode.initializer, varType)) {
return;
}
if (returnsThenable(checker, tsNode.initializer)) {
context.report({
node: node.init,
messageId: 'voidReturnVariable',
});
}
}
function checkProperty(node: TSESTree.Property): void {
const tsNode = services.esTreeNodeToTSNodeMap.get(node);
if (ts.isPropertyAssignment(tsNode)) {
const contextualType = checker.getContextualType(tsNode.initializer);
if (
contextualType != null &&
isVoidReturningFunctionType(
checker,
tsNode.initializer,
contextualType,
) &&
returnsThenable(checker, tsNode.initializer)
) {
if (isFunction(node.value)) {
const functionNode = node.value;
if (functionNode.returnType) {
context.report({
node: functionNode.returnType.typeAnnotation,
messageId: 'voidReturnProperty',
});
} else {
context.report({
loc: getFunctionHeadLoc(functionNode, context.sourceCode),
messageId: 'voidReturnProperty',
});
}
} else {
context.report({
node: node.value,
messageId: 'voidReturnProperty',
});
}
}
} else if (ts.isShorthandPropertyAssignment(tsNode)) {
const contextualType = checker.getContextualType(tsNode.name);
if (
contextualType != null &&
isVoidReturningFunctionType(checker, tsNode.name, contextualType) &&
returnsThenable(checker, tsNode.name)
) {
context.report({
node: node.value,
messageId: 'voidReturnProperty',
});
}
} else if (ts.isMethodDeclaration(tsNode)) {
if (ts.isComputedPropertyName(tsNode.name)) {
return;
}
const obj = tsNode.parent;
// Below condition isn't satisfied unless something goes wrong,
// but is needed for type checking.
// 'node' does not include class method declaration so 'obj' is
// always an object literal expression, but after converting 'node'
// to TypeScript AST, its type includes MethodDeclaration which
// does include the case of class method declaration.
if (!ts.isObjectLiteralExpression(obj)) {
return;
}
if (!returnsThenable(checker, tsNode)) {
return;
}
const objType = checker.getContextualType(obj);
if (objType == null) {
return;
}
const propertySymbol = tsutils
.unionConstituents(objType)
.map(t => checker.getPropertyOfType(t, tsNode.name.getText()))
.find(p => p);
if (propertySymbol == null) {
return;
}
const contextualType = checker.getTypeOfSymbolAtLocation(
propertySymbol,
tsNode.name,
);
if (isVoidReturningFunctionType(checker, tsNode.name, contextualType)) {
const functionNode = node.value as TSESTree.FunctionExpression;
if (functionNode.returnType) {
context.report({
node: functionNode.returnType.typeAnnotation,
messageId: 'voidReturnProperty',
});
} else {
context.report({
loc: getFunctionHeadLoc(functionNode, context.sourceCode),
messageId: 'voidReturnProperty',
});
}
}
return;
}
}
function checkReturnStatement(node: TSESTree.ReturnStatement): void {
const tsNode = services.esTreeNodeToTSNodeMap.get(node);
if (tsNode.expression == null || node.argument == null) {
return;
}
// syntactically ignore some known-good cases to avoid touching type info
const functionNode = (() => {
let current: TSESTree.Node | undefined = node.parent;
while (current && !isFunction(current)) {
current = current.parent;
}
return nullThrows(current, NullThrowsReasons.MissingParent);
})();
if (
functionNode.returnType &&
!isPossiblyFunctionType(functionNode.returnType)
) {
return;
}
const contextualType = checker.getContextualType(tsNode.expression);
if (
contextualType != null &&
isVoidReturningFunctionType(
checker,
tsNode.expression,
contextualType,
) &&
returnsThenable(checker, tsNode.expression)
) {
context.report({
node: node.argument,
messageId: 'voidReturnReturnValue',
});
}
}
function isPromiseFinallyMethod(node: TSESTree.CallExpression): boolean {
const promiseFinallyCall = parseFinallyCall(node, context);
return (
promiseFinallyCall != null &&
isPromiseLike(
services.program,
getConstrainedTypeAtLocation(services, promiseFinallyCall.object),
)
);
}
function checkClassLikeOrInterfaceNode(
node:
| TSESTree.ClassDeclaration
| TSESTree.ClassExpression
| TSESTree.TSInterfaceDeclaration,
): void {
const tsNode = services.esTreeNodeToTSNodeMap.get(node);
const heritageTypes = getHeritageTypes(checker, tsNode);
if (!heritageTypes?.length) {
return;
}
for (const nodeMember of tsNode.members) {
const memberName = nodeMember.name?.getText();
if (memberName == null) {
// Call/construct/index signatures don't have names. TS allows call signatures to mismatch,
// and construct signatures can't be async.
// TODO - Once we're able to use `checker.isTypeAssignableTo` (v8), we can check an index
// signature here against its compatible index signatures in `heritageTypes`
continue;
}
if (!returnsThenable(checker, nodeMember)) {
continue;
}
const node = services.tsNodeToESTreeNodeMap.get(nodeMember);
if (isStaticMember(node)) {
continue;
}
for (const heritageType of heritageTypes) {
checkHeritageTypeForMemberReturningVoid(
nodeMember,
heritageType,
memberName,
);
}
}
}
/**
* Checks `heritageType` for a member named `memberName` that returns void; reports the
* 'voidReturnInheritedMethod' message if found.
* @param nodeMember Node member that returns a Promise
* @param heritageType Heritage type to check against
* @param memberName Name of the member to check for
*/
function checkHeritageTypeForMemberReturningVoid(
nodeMember: ts.Node,
heritageType: ts.Type,
memberName: string,
): void {
const heritageMember = getMemberIfExists(heritageType, memberName);
if (heritageMember == null) {
return;
}
const memberType = checker.getTypeOfSymbolAtLocation(
heritageMember,
nodeMember,
);
if (!isVoidReturningFunctionType(checker, nodeMember, memberType)) {
return;
}
context.report({
node: services.tsNodeToESTreeNodeMap.get(nodeMember),
messageId: 'voidReturnInheritedMethod',
data: { heritageTypeName: checker.typeToString(heritageType) },
});
}
function checkJSXAttribute(node: TSESTree.JSXAttribute): void {
if (node.value?.type !== AST_NODE_TYPES.JSXExpressionContainer) {
return;
}
const expressionContainer = services.esTreeNodeToTSNodeMap.get(
node.value,
);
const expression = services.esTreeNodeToTSNodeMap.get(
node.value.expression,
);
const contextualType = checker.getContextualType(expressionContainer);
if (
contextualType != null &&
isVoidReturningFunctionType(
checker,
expressionContainer,
contextualType,
) &&
returnsThenable(checker, expression)
) {
context.report({
node: node.value,
messageId: 'voidReturnAttribute',
});
}
}
function checkSpread(node: TSESTree.SpreadElement): void {
const tsNode = services.esTreeNodeToTSNodeMap.get(node);
if (isSometimesThenable(checker, tsNode.expression)) {
context.report({
node: node.argument,
messageId: 'spread',
});
}
}
return {
...(checksConditionals ? conditionalChecks : {}),
...(checksVoidReturn ? voidReturnChecks : {}),
...(checksSpreads ? spreadChecks : {}),
};
}
parseChecksVoidReturn(checksVoidReturn: boolean | ChecksVoidReturnOptions | und…): ChecksVoidReturnOptions | false¶
Parameters:
checksVoidReturnboolean | ChecksVoidReturnOptions | undefined
Returns: ChecksVoidReturnOptions | false
Code
function parseChecksVoidReturn(
checksVoidReturn: boolean | ChecksVoidReturnOptions | undefined,
): ChecksVoidReturnOptions | false {
switch (checksVoidReturn) {
case false:
return false;
case true:
case undefined:
return {
arguments: true,
attributes: true,
inheritedMethods: true,
properties: true,
returns: true,
variables: true,
};
default:
return {
arguments: checksVoidReturn.arguments ?? true,
attributes: checksVoidReturn.attributes ?? true,
inheritedMethods: checksVoidReturn.inheritedMethods ?? true,
properties: checksVoidReturn.properties ?? true,
returns: checksVoidReturn.returns ?? true,
variables: checksVoidReturn.variables ?? true,
};
}
}
isSometimesThenable(checker: ts.TypeChecker, node: ts.Node): boolean¶
Parameters:
checkerts.TypeCheckernodets.Node
Returns: boolean
Calls:
checker.getTypeAtLocationtsutils.unionConstituentschecker.getApparentTypetsutils.isThenableType
Code
isAlwaysThenable(checker: ts.TypeChecker, node: ts.Node): boolean¶
Parameters:
checkerts.TypeCheckernodets.Node
Returns: boolean
Calls:
checker.getTypeAtLocationtsutils.unionConstituentschecker.getApparentTypesubType.getPropertychecker.getTypeOfSymbolAtLocationsubType.getCallSignaturesisFunctionParam
Internal Comments:
// If one of the alternates has no then property, it is not thenable in all
// cases.
// We walk through each variation of the then property. Since we know it (x2)
// exists at this point, we just need at least one of the alternates to (x2)
// be of the right form to consider it thenable. (x2)
// We only need to find one variant of the then property that has a
// function signature for it to be thenable.
// If no flavors of the then property are thenable, we don't consider the
// overall type to be thenable
// If all variants are considered thenable (i.e. haven't returned false), we
// consider the overall type thenable
Code
function isAlwaysThenable(checker: ts.TypeChecker, node: ts.Node): boolean {
const type = checker.getTypeAtLocation(node);
for (const subType of tsutils.unionConstituents(
checker.getApparentType(type),
)) {
const thenProp = subType.getProperty('then');
// If one of the alternates has no then property, it is not thenable in all
// cases.
if (thenProp == null) {
return false;
}
// We walk through each variation of the then property. Since we know it
// exists at this point, we just need at least one of the alternates to
// be of the right form to consider it thenable.
const thenType = checker.getTypeOfSymbolAtLocation(thenProp, node);
let hasThenableSignature = false;
for (const subType of tsutils.unionConstituents(thenType)) {
for (const signature of subType.getCallSignatures()) {
if (
signature.parameters.length !== 0 &&
isFunctionParam(checker, signature.parameters[0], node)
) {
hasThenableSignature = true;
break;
}
}
// We only need to find one variant of the then property that has a
// function signature for it to be thenable.
if (hasThenableSignature) {
break;
}
}
// If no flavors of the then property are thenable, we don't consider the
// overall type to be thenable
if (!hasThenableSignature) {
return false;
}
}
// If all variants are considered thenable (i.e. haven't returned false), we
// consider the overall type thenable
return true;
}
isFunctionParam(checker: ts.TypeChecker, param: ts.Symbol, node: ts.Node): boolean¶
Parameters:
checkerts.TypeCheckerparamts.Symbolnodets.Node
Returns: boolean
Calls:
checker.getApparentTypechecker.getTypeOfSymbolAtLocationtsutils.unionConstituentssubType.getCallSignatures
Code
function isFunctionParam(
checker: ts.TypeChecker,
param: ts.Symbol,
node: ts.Node,
): boolean {
const type: ts.Type | undefined = checker.getApparentType(
checker.getTypeOfSymbolAtLocation(param, node),
);
for (const subType of tsutils.unionConstituents(type)) {
if (subType.getCallSignatures().length !== 0) {
return true;
}
}
return false;
}
checkThenableOrVoidArgument(…): void¶
Parameters:
checkerts.TypeCheckernodets.CallExpression | ts.NewExpressiontypets.TypeindexnumberthenableReturnIndicesSet<number>voidReturnIndicesSet<number>
Returns: void
Calls:
isThenableReturningFunctionTypethenableReturnIndices.addisVoidReturningFunctionTypethenableReturnIndices.hasvoidReturnIndices.addchecker.getContextualTypeForArgumentAtIndexcheckThenableOrVoidArgument
Internal Comments:
// If a certain argument accepts both thenable and void returns,
// a promise-returning function is valid
Code
function checkThenableOrVoidArgument(
checker: ts.TypeChecker,
node: ts.CallExpression | ts.NewExpression,
type: ts.Type,
index: number,
thenableReturnIndices: Set<number>,
voidReturnIndices: Set<number>,
): void {
if (isThenableReturningFunctionType(checker, node.expression, type)) {
thenableReturnIndices.add(index);
} else if (
isVoidReturningFunctionType(checker, node.expression, type) &&
// If a certain argument accepts both thenable and void returns,
// a promise-returning function is valid
!thenableReturnIndices.has(index)
) {
voidReturnIndices.add(index);
}
const contextualType = checker.getContextualTypeForArgumentAtIndex(
node,
index,
);
if (contextualType !== type) {
checkThenableOrVoidArgument(
checker,
node,
contextualType,
index,
thenableReturnIndices,
voidReturnIndices,
);
}
}
voidFunctionArguments(checker: ts.TypeChecker, node: ts.CallExpression | ts.NewExpression): Set<number>¶
Parameters:
checkerts.TypeCheckernodets.CallExpression | ts.NewExpression
Returns: Set<number>
Calls:
checker.getTypeAtLocationtsutils.unionConstituentsts.isCallExpressionsubType.getCallSignaturessubType.getConstructSignaturessignature.parameters.entrieschecker.getTypeOfSymbolAtLocationisRestParameterDeclaration (from ../util)checker.isArrayTypechecker.getTypeArgumentscheckThenableOrVoidArgumentchecker.isTupleTypevoidReturnIndices.delete
Internal Comments:
// 'new' can be used without any arguments, as in 'let b = new Object;'
// In this case, there are no argument positions to check, so return early.
// We can't use checker.getResolvedSignature because it prefers an early '() => void' over a later '() => Promise<void>'
// See https://github.com/microsoft/TypeScript/issues/48077
// Standard function calls and `new` have two different types of signatures (x2)
// If this is a array 'rest' parameter, check all of the argument indices
// from the current argument to the end.
// Unwrap 'Array<MaybeVoidFunction>' to 'MaybeVoidFunction', (x3)
// so that we'll handle it in the same way as a non-rest (x3)
// 'param: MaybeVoidFunction' (x3)
// Check each type in the tuple - for example, [boolean, () => void] would (x2)
// add the index of the second tuple parameter to 'voidReturnIndices' (x2)
Code
function voidFunctionArguments(
checker: ts.TypeChecker,
node: ts.CallExpression | ts.NewExpression,
): Set<number> {
// 'new' can be used without any arguments, as in 'let b = new Object;'
// In this case, there are no argument positions to check, so return early.
if (!node.arguments) {
return new Set<number>();
}
const thenableReturnIndices = new Set<number>();
const voidReturnIndices = new Set<number>();
const type = checker.getTypeAtLocation(node.expression);
// We can't use checker.getResolvedSignature because it prefers an early '() => void' over a later '() => Promise<void>'
// See https://github.com/microsoft/TypeScript/issues/48077
for (const subType of tsutils.unionConstituents(type)) {
// Standard function calls and `new` have two different types of signatures
const signatures = ts.isCallExpression(node)
? subType.getCallSignatures()
: subType.getConstructSignatures();
for (const signature of signatures) {
for (const [index, parameter] of signature.parameters.entries()) {
const decl = parameter.valueDeclaration;
let type = checker.getTypeOfSymbolAtLocation(
parameter,
node.expression,
);
// If this is a array 'rest' parameter, check all of the argument indices
// from the current argument to the end.
if (decl && isRestParameterDeclaration(decl)) {
if (checker.isArrayType(type)) {
// Unwrap 'Array<MaybeVoidFunction>' to 'MaybeVoidFunction',
// so that we'll handle it in the same way as a non-rest
// 'param: MaybeVoidFunction'
type = checker.getTypeArguments(type)[0];
for (let i = index; i < node.arguments.length; i++) {
checkThenableOrVoidArgument(
checker,
node,
type,
i,
thenableReturnIndices,
voidReturnIndices,
);
}
} else if (checker.isTupleType(type)) {
// Check each type in the tuple - for example, [boolean, () => void] would
// add the index of the second tuple parameter to 'voidReturnIndices'
const typeArgs = checker.getTypeArguments(type);
for (
let i = index;
i < node.arguments.length && i - index < typeArgs.length;
i++
) {
checkThenableOrVoidArgument(
checker,
node,
typeArgs[i - index],
i,
thenableReturnIndices,
voidReturnIndices,
);
}
}
} else {
checkThenableOrVoidArgument(
checker,
node,
type,
index,
thenableReturnIndices,
voidReturnIndices,
);
}
}
}
}
for (const index of thenableReturnIndices) {
voidReturnIndices.delete(index);
}
return voidReturnIndices;
}
anySignatureIsThenableType(checker: ts.TypeChecker, node: ts.Node, type: ts.Type): boolean¶
Returns: undefined
Whether any call signature of the type has a thenable return type.
Calls:
type.getCallSignaturessignature.getReturnTypetsutils.isThenableType
Code
isThenableReturningFunctionType(checker: ts.TypeChecker, node: ts.Node, type: ts.Type): boolean¶
Returns: undefined
Whether type is a thenable-returning function.
Calls:
tsutils.unionConstituentsanySignatureIsThenableType
Code
isVoidReturningFunctionType(checker: ts.TypeChecker, node: ts.Node, type: ts.Type): boolean¶
Returns: undefined
Whether type is a void-returning function.
Calls:
tsutils.unionConstituentssubType.getCallSignaturessignature.getReturnTypetsutils.isThenableTypetsutils.isTypeFlagSet
Internal Comments:
// If a certain positional argument accepts both thenable and void returns,
// a promise-returning function is valid
Code
function isVoidReturningFunctionType(
checker: ts.TypeChecker,
node: ts.Node,
type: ts.Type,
): boolean {
let hadVoidReturn = false;
for (const subType of tsutils.unionConstituents(type)) {
for (const signature of subType.getCallSignatures()) {
const returnType = signature.getReturnType();
// If a certain positional argument accepts both thenable and void returns,
// a promise-returning function is valid
if (tsutils.isThenableType(checker, node, returnType)) {
return false;
}
hadVoidReturn ||= tsutils.isTypeFlagSet(returnType, ts.TypeFlags.Void);
}
}
return hadVoidReturn;
}
returnsThenable(checker: ts.TypeChecker, node: ts.Node): boolean¶
Returns: undefined
Whether expression is a function that returns a thenable.
Calls:
checker.getApparentTypechecker.getTypeAtLocationtsutils .unionConstituents(type) .someanySignatureIsThenableType
Code
getHeritageTypes(checker: ts.TypeChecker, tsNode: ts.ClassDeclaration | ts.ClassExpressio…): ts.Type[] | undefined¶
Parameters:
checkerts.TypeCheckertsNodets.ClassDeclaration | ts.ClassExpression | ts.InterfaceDeclaration
Returns: ts.Type[] | undefined
Calls:
tsNode.heritageClauses ?.flatMap(clause => clause.types) .mapchecker.getTypeAtLocation
Code
getMemberIfExists(type: ts.Type, memberName: string): ts.Symbol | undefined¶
Returns: undefined
The member with the given name in type, if it exists.
Calls:
ts.escapeLeadingUnderscorestype.getSymbol()?.members?.gettsutils.getPropertyOfType
Code
function getMemberIfExists(
type: ts.Type,
memberName: string,
): ts.Symbol | undefined {
const escapedMemberName = ts.escapeLeadingUnderscores(memberName);
const symbolMemberMatch = type.getSymbol()?.members?.get(escapedMemberName);
return (
symbolMemberMatch ?? tsutils.getPropertyOfType(type, escapedMemberName)
);
}
isStaticMember(node: TSESTree.Node): boolean¶
Parameters:
nodeTSESTree.Node
Returns: boolean
Code
hasWellKnownSymbolWithThenableReturn(checker: ts.TypeChecker, node: ts.Node, type: ts.Type, symbolName: 'asyncDispose' | 'dispose'): boolean¶
Parameters:
checkerts.TypeCheckernodets.Nodetypets.TypesymbolName'asyncDispose' | 'dispose'
Returns: boolean
Calls:
tsutils .unionConstituents(checker.getApparentType(type)) .sometsutils.getWellKnownSymbolPropertyOfTypeisThenableReturningFunctionTypechecker.getTypeOfSymbolAtLocation
Code
function hasWellKnownSymbolWithThenableReturn(
checker: ts.TypeChecker,
node: ts.Node,
type: ts.Type,
symbolName: 'asyncDispose' | 'dispose',
): boolean {
return tsutils
.unionConstituents(checker.getApparentType(type))
.some(typePart => {
const symbol = tsutils.getWellKnownSymbolPropertyOfType(
typePart,
symbolName,
checker,
);
if (symbol == null) {
return false;
}
return isThenableReturningFunctionType(
checker,
node,
checker.getTypeOfSymbolAtLocation(symbol, node),
);
});
}
hasWellKnownSymbolWithVoidReturn(checker: ts.TypeChecker, node: ts.Node, type: ts.Type, symbolName: 'asyncDispose' | 'dispose'): boolean¶
Parameters:
checkerts.TypeCheckernodets.Nodetypets.TypesymbolName'asyncDispose' | 'dispose'
Returns: boolean
Calls:
tsutils .unionConstituents(checker.getApparentType(type)) .sometsutils.getWellKnownSymbolPropertyOfTypeisVoidReturningFunctionTypechecker.getTypeOfSymbolAtLocation
Code
function hasWellKnownSymbolWithVoidReturn(
checker: ts.TypeChecker,
node: ts.Node,
type: ts.Type,
symbolName: 'asyncDispose' | 'dispose',
): boolean {
return tsutils
.unionConstituents(checker.getApparentType(type))
.some(typePart => {
const symbol = tsutils.getWellKnownSymbolPropertyOfType(
typePart,
symbolName,
checker,
);
if (symbol == null) {
return false;
}
return isVoidReturningFunctionType(
checker,
node,
checker.getTypeOfSymbolAtLocation(symbol, node),
);
});
}
hasMatchingPromiseTypeArgument(checker: ts.TypeChecker, node: ts.Node): any¶
Check that the Promise argument is the same as the rest of the type when it is a Union that contains Promise.
Raw JSDoc
Calls:
checker.getTypeAtLocationtsutils.unionConstituentschecker.getApparentTypeunionConstituents.findtsutils.isThenableTypeunionConstituents.filterchecker.getAwaitedTypenonPromiseUnionConstituents.everyawaitedTypeConstituents.somechecker.isTypeAssignableTo
Code
function hasMatchingPromiseTypeArgument(
checker: ts.TypeChecker,
node: ts.Node,
) {
const type = checker.getTypeAtLocation(node);
const unionConstituents = tsutils.unionConstituents(
checker.getApparentType(type),
);
const promiseType = unionConstituents.find(type =>
tsutils.isThenableType(checker, node, type),
);
if (!promiseType) {
return false;
}
const nonPromiseUnionConstituents = unionConstituents.filter(
type => type !== promiseType,
);
const awaitedType = checker.getAwaitedType(promiseType);
if (!awaitedType) {
return false;
}
const awaitedTypeConstituents = tsutils.unionConstituents(awaitedType);
return (
nonPromiseUnionConstituents.length === awaitedTypeConstituents.length &&
nonPromiseUnionConstituents.every(type =>
awaitedTypeConstituents.some(
awaited =>
checker.isTypeAssignableTo(type, awaited) &&
checker.isTypeAssignableTo(awaited, type),
),
)
);
}
normalizeFlagUnionsOption(checksConditionals: boolean | ChecksConditionalsOptions | u…): FlagUnionsOptions¶
Parameters:
checksConditionalsboolean | ChecksConditionalsOptions | undefined
Returns: FlagUnionsOptions
Code
Internal helpers¶
Declared inside another function in this file.
isPossiblyFunctionType(node: TSESTree.TSTypeAnnotation): boolean¶
A syntactic check to see if an annotated type is maybe a function type. This is a perf optimization to help avoid requesting types where possible
Raw JSDoc
Calls:
node.typeAnnotation.members.some
Code
function isPossiblyFunctionType(node: TSESTree.TSTypeAnnotation): boolean {
switch (node.typeAnnotation.type) {
case AST_NODE_TYPES.TSConditionalType:
case AST_NODE_TYPES.TSConstructorType:
case AST_NODE_TYPES.TSFunctionType:
case AST_NODE_TYPES.TSImportType:
case AST_NODE_TYPES.TSIndexedAccessType:
case AST_NODE_TYPES.TSInferType:
case AST_NODE_TYPES.TSIntersectionType:
case AST_NODE_TYPES.TSQualifiedName:
case AST_NODE_TYPES.TSThisType:
case AST_NODE_TYPES.TSTypeOperator:
case AST_NODE_TYPES.TSTypeQuery:
case AST_NODE_TYPES.TSTypeReference:
case AST_NODE_TYPES.TSUnionType:
return true;
case AST_NODE_TYPES.TSTypeLiteral:
return node.typeAnnotation.members.some(
member =>
member.type === AST_NODE_TYPES.TSCallSignatureDeclaration ||
member.type === AST_NODE_TYPES.TSConstructSignatureDeclaration,
);
case AST_NODE_TYPES.TSAbstractKeyword:
case AST_NODE_TYPES.TSAnyKeyword:
case AST_NODE_TYPES.TSArrayType:
case AST_NODE_TYPES.TSAsyncKeyword:
case AST_NODE_TYPES.TSBigIntKeyword:
case AST_NODE_TYPES.TSBooleanKeyword:
case AST_NODE_TYPES.TSDeclareKeyword:
case AST_NODE_TYPES.TSExportKeyword:
case AST_NODE_TYPES.TSIntrinsicKeyword:
case AST_NODE_TYPES.TSLiteralType:
case AST_NODE_TYPES.TSMappedType:
case AST_NODE_TYPES.TSNamedTupleMember:
case AST_NODE_TYPES.TSNeverKeyword:
case AST_NODE_TYPES.TSNullKeyword:
case AST_NODE_TYPES.TSNumberKeyword:
case AST_NODE_TYPES.TSObjectKeyword:
case AST_NODE_TYPES.TSOptionalType:
case AST_NODE_TYPES.TSPrivateKeyword:
case AST_NODE_TYPES.TSProtectedKeyword:
case AST_NODE_TYPES.TSPublicKeyword:
case AST_NODE_TYPES.TSReadonlyKeyword:
case AST_NODE_TYPES.TSRestType:
case AST_NODE_TYPES.TSStaticKeyword:
case AST_NODE_TYPES.TSStringKeyword:
case AST_NODE_TYPES.TSSymbolKeyword:
case AST_NODE_TYPES.TSTemplateLiteralType:
case AST_NODE_TYPES.TSTupleType:
case AST_NODE_TYPES.TSTypePredicate:
case AST_NODE_TYPES.TSUndefinedKeyword:
case AST_NODE_TYPES.TSUnknownKeyword:
case AST_NODE_TYPES.TSVoidKeyword:
return false;
}
}
checkTestConditional(node: | TSESTree.ConditionalExpression | TSES…): void¶
Parameters:
node| TSESTree.ConditionalExpression | TSESTree.DoWhileStatement | TSESTree.ForStatement | TSESTree.IfStatement | TSESTree.WhileStatement
Returns: void
Calls:
checkConditional
Code
checkConditional(node: TSESTree.Expression, isTestExpr: boolean): void¶
This function analyzes the type of a node and checks if it is a Promise in a boolean conditional. It uses recursion when checking nested logical operators.
Parameters:
nodeany: The AST node to check.isTestExprany: Whether the node is a descendant of a test expression.
Raw JSDoc
Calls:
checkedNodes.hascheckedNodes.addcheckConditionalservices.esTreeNodeToTSNodeMap.getisAlwaysThenablecontext.reportisSometimesThenablehasMatchingPromiseTypeArgument
Internal Comments:
// prevent checking the same node multiple times
// ignore the left operand for nullish coalescing expressions not in a context of a test expression
// we ignore the right operand when not in a context of a test expression
// none -> Report `Promise` but not `Promise | ...` (x3)
// (x2)
// all -> Report `Promise` and `Promise | ...`
// strict -> Report `Promise<T> | T` but not `Promise<T> | NotT`
Code
function checkConditional(
node: TSESTree.Expression,
isTestExpr = false,
): void {
// prevent checking the same node multiple times
if (checkedNodes.has(node)) {
return;
}
checkedNodes.add(node);
if (node.type === AST_NODE_TYPES.LogicalExpression) {
// ignore the left operand for nullish coalescing expressions not in a context of a test expression
if (node.operator !== '??' || isTestExpr) {
checkConditional(node.left, isTestExpr);
}
// we ignore the right operand when not in a context of a test expression
if (isTestExpr) {
checkConditional(node.right, isTestExpr);
}
return;
}
const tsNode = services.esTreeNodeToTSNodeMap.get(node);
if (isAlwaysThenable(checker, tsNode)) {
context.report({
node,
messageId: 'conditional',
});
return;
}
if (
// none -> Report `Promise` but not `Promise | ...`
(flagUnionsOption === 'none' && isAlwaysThenable(checker, tsNode)) ||
//
// all -> Report `Promise` and `Promise | ...`
(flagUnionsOption === 'all' && isSometimesThenable(checker, tsNode)) ||
//
// strict -> Report `Promise<T> | T` but not `Promise<T> | NotT`
(flagUnionsOption === 'strict' &&
hasMatchingPromiseTypeArgument(checker, tsNode))
) {
context.report({
node,
messageId: 'conditional',
});
}
}
checkArrayPredicates(node: TSESTree.MemberExpression): void¶
Parameters:
nodeTSESTree.MemberExpression
Returns: void
Calls:
parent.arguments.atisArrayMethodCallWithPredicate (from ../util)services.esTreeNodeToTSNodeMap.getreturnsThenablecontext.report
Code
function checkArrayPredicates(node: TSESTree.MemberExpression): void {
const parent = node.parent;
if (parent.type === AST_NODE_TYPES.CallExpression) {
const callback = parent.arguments.at(0);
if (
callback &&
isArrayMethodCallWithPredicate(context, services, parent)
) {
const type = services.esTreeNodeToTSNodeMap.get(callback);
if (returnsThenable(checker, type)) {
context.report({
node: callback,
messageId: 'predicate',
});
}
}
}
}
checkArguments(node: TSESTree.CallExpression | TSESTree.NewE…): void¶
Parameters:
nodeTSESTree.CallExpression | TSESTree.NewExpression
Returns: void
Calls:
isPromiseFinallyMethodservices.esTreeNodeToTSNodeMap.getvoidFunctionArgumentsnode.arguments.entriesvoidArgs.hasreturnsThenablecontext.report
Code
function checkArguments(
node: TSESTree.CallExpression | TSESTree.NewExpression,
): void {
if (
node.type === AST_NODE_TYPES.CallExpression &&
isPromiseFinallyMethod(node)
) {
return;
}
const tsNode = services.esTreeNodeToTSNodeMap.get(node);
const voidArgs = voidFunctionArguments(checker, tsNode);
if (voidArgs.size === 0) {
return;
}
for (const [index, argument] of node.arguments.entries()) {
if (!voidArgs.has(index)) {
continue;
}
const tsNode = services.esTreeNodeToTSNodeMap.get(argument);
if (returnsThenable(checker, tsNode)) {
context.report({
node: argument,
messageId: 'voidReturnArgument',
});
}
}
}
checkAssignment(node: TSESTree.AssignmentExpression): void¶
Parameters:
nodeTSESTree.AssignmentExpression
Returns: void
Calls:
services.esTreeNodeToTSNodeMap.getservices.getTypeAtLocationisVoidReturningFunctionTypereturnsThenablecontext.report
Code
function checkAssignment(node: TSESTree.AssignmentExpression): void {
const tsNode = services.esTreeNodeToTSNodeMap.get(node);
const varType = services.getTypeAtLocation(node.left);
if (!isVoidReturningFunctionType(checker, tsNode.left, varType)) {
return;
}
if (returnsThenable(checker, tsNode.right)) {
context.report({
node: node.right,
messageId: 'voidReturnVariable',
});
}
}
checkVariableDeclaration(node: TSESTree.VariableDeclarator): void¶
Parameters:
nodeTSESTree.VariableDeclarator
Returns: void
Calls:
services.esTreeNodeToTSNodeMap.gethasWellKnownSymbolWithThenableReturnchecker.getTypeAtLocationcontext.reportservices.getTypeAtLocationhasWellKnownSymbolWithVoidReturnisPossiblyFunctionTypeisVoidReturningFunctionTypereturnsThenable
Internal Comments:
Code
function checkVariableDeclaration(node: TSESTree.VariableDeclarator): void {
const tsNode = services.esTreeNodeToTSNodeMap.get(node);
if (tsNode.initializer == null || node.init == null) {
return;
}
if (
node.parent.kind === 'using' &&
hasWellKnownSymbolWithThenableReturn(
checker,
tsNode.initializer,
checker.getTypeAtLocation(tsNode.initializer),
'dispose',
)
) {
context.report({
node: node.init,
messageId: 'voidReturnVariable',
});
}
if (node.id.typeAnnotation == null) {
return;
}
const variableType = services.getTypeAtLocation(node.id);
if (
hasWellKnownSymbolWithVoidReturn(
checker,
tsNode.name,
variableType,
'dispose',
) &&
hasWellKnownSymbolWithThenableReturn(
checker,
tsNode.initializer,
checker.getTypeAtLocation(tsNode.initializer),
'dispose',
)
) {
context.report({
node: node.init,
messageId: 'voidReturnVariable',
});
}
// syntactically ignore some known-good cases to avoid touching type info
if (!isPossiblyFunctionType(node.id.typeAnnotation)) {
return;
}
const varType = services.getTypeAtLocation(node.id);
if (!isVoidReturningFunctionType(checker, tsNode.initializer, varType)) {
return;
}
if (returnsThenable(checker, tsNode.initializer)) {
context.report({
node: node.init,
messageId: 'voidReturnVariable',
});
}
}
checkProperty(node: TSESTree.Property): void¶
Parameters:
nodeTSESTree.Property
Returns: void
Calls:
services.esTreeNodeToTSNodeMap.getts.isPropertyAssignmentchecker.getContextualTypeisVoidReturningFunctionTypereturnsThenableisFunction (from ../util)context.reportgetFunctionHeadLoc (from ../util)ts.isShorthandPropertyAssignmentts.isMethodDeclarationts.isComputedPropertyNamets.isObjectLiteralExpressiontsutils .unionConstituents(objType) .map(t => checker.getPropertyOfType(t, tsNode.name.getText())) .findchecker.getTypeOfSymbolAtLocation
Internal Comments:
// Below condition isn't satisfied unless something goes wrong,
// but is needed for type checking.
// 'node' does not include class method declaration so 'obj' is
// always an object literal expression, but after converting 'node'
// to TypeScript AST, its type includes MethodDeclaration which
// does include the case of class method declaration.
Code
function checkProperty(node: TSESTree.Property): void {
const tsNode = services.esTreeNodeToTSNodeMap.get(node);
if (ts.isPropertyAssignment(tsNode)) {
const contextualType = checker.getContextualType(tsNode.initializer);
if (
contextualType != null &&
isVoidReturningFunctionType(
checker,
tsNode.initializer,
contextualType,
) &&
returnsThenable(checker, tsNode.initializer)
) {
if (isFunction(node.value)) {
const functionNode = node.value;
if (functionNode.returnType) {
context.report({
node: functionNode.returnType.typeAnnotation,
messageId: 'voidReturnProperty',
});
} else {
context.report({
loc: getFunctionHeadLoc(functionNode, context.sourceCode),
messageId: 'voidReturnProperty',
});
}
} else {
context.report({
node: node.value,
messageId: 'voidReturnProperty',
});
}
}
} else if (ts.isShorthandPropertyAssignment(tsNode)) {
const contextualType = checker.getContextualType(tsNode.name);
if (
contextualType != null &&
isVoidReturningFunctionType(checker, tsNode.name, contextualType) &&
returnsThenable(checker, tsNode.name)
) {
context.report({
node: node.value,
messageId: 'voidReturnProperty',
});
}
} else if (ts.isMethodDeclaration(tsNode)) {
if (ts.isComputedPropertyName(tsNode.name)) {
return;
}
const obj = tsNode.parent;
// Below condition isn't satisfied unless something goes wrong,
// but is needed for type checking.
// 'node' does not include class method declaration so 'obj' is
// always an object literal expression, but after converting 'node'
// to TypeScript AST, its type includes MethodDeclaration which
// does include the case of class method declaration.
if (!ts.isObjectLiteralExpression(obj)) {
return;
}
if (!returnsThenable(checker, tsNode)) {
return;
}
const objType = checker.getContextualType(obj);
if (objType == null) {
return;
}
const propertySymbol = tsutils
.unionConstituents(objType)
.map(t => checker.getPropertyOfType(t, tsNode.name.getText()))
.find(p => p);
if (propertySymbol == null) {
return;
}
const contextualType = checker.getTypeOfSymbolAtLocation(
propertySymbol,
tsNode.name,
);
if (isVoidReturningFunctionType(checker, tsNode.name, contextualType)) {
const functionNode = node.value as TSESTree.FunctionExpression;
if (functionNode.returnType) {
context.report({
node: functionNode.returnType.typeAnnotation,
messageId: 'voidReturnProperty',
});
} else {
context.report({
loc: getFunctionHeadLoc(functionNode, context.sourceCode),
messageId: 'voidReturnProperty',
});
}
}
return;
}
}
checkReturnStatement(node: TSESTree.ReturnStatement): void¶
Parameters:
nodeTSESTree.ReturnStatement
Returns: void
Calls:
services.esTreeNodeToTSNodeMap.getcomplex_call_20907isFunction (from ../util)nullThrows (from ../util)isPossiblyFunctionTypechecker.getContextualTypeisVoidReturningFunctionTypereturnsThenablecontext.report
Internal Comments:
Code
function checkReturnStatement(node: TSESTree.ReturnStatement): void {
const tsNode = services.esTreeNodeToTSNodeMap.get(node);
if (tsNode.expression == null || node.argument == null) {
return;
}
// syntactically ignore some known-good cases to avoid touching type info
const functionNode = (() => {
let current: TSESTree.Node | undefined = node.parent;
while (current && !isFunction(current)) {
current = current.parent;
}
return nullThrows(current, NullThrowsReasons.MissingParent);
})();
if (
functionNode.returnType &&
!isPossiblyFunctionType(functionNode.returnType)
) {
return;
}
const contextualType = checker.getContextualType(tsNode.expression);
if (
contextualType != null &&
isVoidReturningFunctionType(
checker,
tsNode.expression,
contextualType,
) &&
returnsThenable(checker, tsNode.expression)
) {
context.report({
node: node.argument,
messageId: 'voidReturnReturnValue',
});
}
}
isPromiseFinallyMethod(node: TSESTree.CallExpression): boolean¶
Parameters:
nodeTSESTree.CallExpression
Returns: boolean
Calls:
parseFinallyCall (from ../util/promiseUtils)isPromiseLike (from ../util)getConstrainedTypeAtLocation (from ../util)
Code
checkClassLikeOrInterfaceNode(node: | TSESTree.ClassDeclaration | TSESTree.…): void¶
Parameters:
node| TSESTree.ClassDeclaration | TSESTree.ClassExpression | TSESTree.TSInterfaceDeclaration
Returns: void
Calls:
services.esTreeNodeToTSNodeMap.getgetHeritageTypesnodeMember.name?.getTextreturnsThenableservices.tsNodeToESTreeNodeMap.getisStaticMembercheckHeritageTypeForMemberReturningVoid
Internal Comments:
// Call/construct/index signatures don't have names. TS allows call signatures to mismatch,
// and construct signatures can't be async.
// TODO - Once we're able to use `checker.isTypeAssignableTo` (v8), we can check an index
// signature here against its compatible index signatures in `heritageTypes`
Code
function checkClassLikeOrInterfaceNode(
node:
| TSESTree.ClassDeclaration
| TSESTree.ClassExpression
| TSESTree.TSInterfaceDeclaration,
): void {
const tsNode = services.esTreeNodeToTSNodeMap.get(node);
const heritageTypes = getHeritageTypes(checker, tsNode);
if (!heritageTypes?.length) {
return;
}
for (const nodeMember of tsNode.members) {
const memberName = nodeMember.name?.getText();
if (memberName == null) {
// Call/construct/index signatures don't have names. TS allows call signatures to mismatch,
// and construct signatures can't be async.
// TODO - Once we're able to use `checker.isTypeAssignableTo` (v8), we can check an index
// signature here against its compatible index signatures in `heritageTypes`
continue;
}
if (!returnsThenable(checker, nodeMember)) {
continue;
}
const node = services.tsNodeToESTreeNodeMap.get(nodeMember);
if (isStaticMember(node)) {
continue;
}
for (const heritageType of heritageTypes) {
checkHeritageTypeForMemberReturningVoid(
nodeMember,
heritageType,
memberName,
);
}
}
}
checkHeritageTypeForMemberReturningVoid(nodeMember: ts.Node, heritageType: ts.Type, memberName: string): void¶
Checks heritageType for a member named memberName that returns void; reports the
'voidReturnInheritedMethod' message if found.
Parameters:
nodeMemberany: Node member that returns a PromiseheritageTypeany: Heritage type to check againstmemberNameany: Name of the member to check for
Raw JSDoc
/**
* Checks `heritageType` for a member named `memberName` that returns void; reports the
* 'voidReturnInheritedMethod' message if found.
* @param nodeMember Node member that returns a Promise
* @param heritageType Heritage type to check against
* @param memberName Name of the member to check for
*/
Calls:
getMemberIfExistschecker.getTypeOfSymbolAtLocationisVoidReturningFunctionTypecontext.reportservices.tsNodeToESTreeNodeMap.getchecker.typeToString
Code
function checkHeritageTypeForMemberReturningVoid(
nodeMember: ts.Node,
heritageType: ts.Type,
memberName: string,
): void {
const heritageMember = getMemberIfExists(heritageType, memberName);
if (heritageMember == null) {
return;
}
const memberType = checker.getTypeOfSymbolAtLocation(
heritageMember,
nodeMember,
);
if (!isVoidReturningFunctionType(checker, nodeMember, memberType)) {
return;
}
context.report({
node: services.tsNodeToESTreeNodeMap.get(nodeMember),
messageId: 'voidReturnInheritedMethod',
data: { heritageTypeName: checker.typeToString(heritageType) },
});
}
checkJSXAttribute(node: TSESTree.JSXAttribute): void¶
Parameters:
nodeTSESTree.JSXAttribute
Returns: void
Calls:
services.esTreeNodeToTSNodeMap.getchecker.getContextualTypeisVoidReturningFunctionTypereturnsThenablecontext.report
Code
function checkJSXAttribute(node: TSESTree.JSXAttribute): void {
if (node.value?.type !== AST_NODE_TYPES.JSXExpressionContainer) {
return;
}
const expressionContainer = services.esTreeNodeToTSNodeMap.get(
node.value,
);
const expression = services.esTreeNodeToTSNodeMap.get(
node.value.expression,
);
const contextualType = checker.getContextualType(expressionContainer);
if (
contextualType != null &&
isVoidReturningFunctionType(
checker,
expressionContainer,
contextualType,
) &&
returnsThenable(checker, expression)
) {
context.report({
node: node.value,
messageId: 'voidReturnAttribute',
});
}
}
checkSpread(node: TSESTree.SpreadElement): void¶
Parameters:
nodeTSESTree.SpreadElement
Returns: void
Calls:
services.esTreeNodeToTSNodeMap.getisSometimesThenablecontext.report
Code
Interfaces¶
ChecksConditionalsOptions¶
Properties¶
| Name | Type | Optional | Description |
|---|---|---|---|
flagUnions |
FlagUnionsOptions |
✓ | not shown |
ChecksVoidReturnOptions¶
Interface Code
Properties¶
| Name | Type | Optional | Description |
|---|---|---|---|
arguments |
boolean |
✓ | not shown |
attributes |
boolean |
✓ | not shown |
inheritedMethods |
boolean |
✓ | not shown |
properties |
boolean |
✓ | not shown |
returns |
boolean |
✓ | not shown |
variables |
boolean |
✓ | not shown |
Type Aliases¶
Options¶
type Options = [
{
checksConditionals?: boolean | ChecksConditionalsOptions;
checksSpreads?: boolean;
checksVoidReturn?: boolean | ChecksVoidReturnOptions;
},
];
FlagUnionsOptions¶
MessageId¶
type MessageId = | 'conditional'
| 'predicate'
| 'spread'
| 'voidReturnArgument'
| 'voidReturnAttribute'
| 'voidReturnInheritedMethod'
| 'voidReturnProperty'
| 'voidReturnReturnValue'
| 'voidReturnVariable';
Generated by Syntax Scribe