📄 no-floating-promises¶
📊 Analysis Summary¶
| Metric | Count |
|---|---|
| 🔧 Functions | 12 |
| 📦 Imports | 18 |
| 📊 Variables & Constants | 5 |
| 📑 Type Aliases | 2 |
📚 Table of Contents¶
🛠️ File Location:¶
📂 packages/eslint-plugin/src/rules/no-floating-promises.ts
📤 Default Export¶
| Property | Value |
|---|---|
name |
'no-floating-promises' |
meta.type |
'problem' |
meta.docs.description |
'Require Promise-like statements to be handled appropriately' |
meta.docs.recommended |
'recommended' |
meta.docs.requiresTypeChecking |
true |
meta.hasSuggestions |
true |
meta.messages.floating |
messageBase |
meta.messages.floatingFixAwait |
'Add await operator.' |
meta.messages.floatingFixVoid |
'Add void operator to ignore.' |
meta.messages.floatingPromiseArray |
messagePromiseArray |
meta.messages.floatingPromiseArrayVoid |
messagePromiseArrayVoid |
meta.messages.floatingUselessRejectionHandler |
${messageBase} ${messageRejectionHandler} |
meta.messages.floatingUselessRejectionHandlerVoid |
${messageBaseVoid} ${messageRejectionHandler} |
meta.messages.floatingVoid |
messageBaseVoid |
meta.schema |
[ { type: 'object', additionalProperties: false, properties: { allowForKnownSafeCalls: { ...readonlynessOptionsSchema... |
defaultOptions |
[ { allowForKnownSafeCalls: readonlynessOptionsDefaults.allow, allowForKnownSafePromises: readonlynessOptionsDefaults... |
Entry point: create — documented under Functions.
📦 Imports¶
| Name | Source |
|---|---|
TSESLint |
@typescript-eslint/utils |
TSESTree |
@typescript-eslint/utils |
AST_NODE_TYPES |
@typescript-eslint/utils |
TypeOrValueSpecifier |
../util |
createRule |
../util |
getOperatorPrecedenceForNode |
../util |
getParserServices |
../util |
isBuiltinSymbolLike |
../util |
isParenthesized |
../util |
OperatorPrecedence |
../util |
readonlynessOptionsDefaults |
../util |
readonlynessOptionsSchema |
../util |
skipChainExpression |
../util |
typeMatchesSomeSpecifier |
../util |
valueMatchesSomeSpecifier |
../util |
parseCatchCall |
../util/promiseUtils |
parseFinallyCall |
../util/promiseUtils |
parseThenCall |
../util/promiseUtils |
Variables & Constants¶
| Name | Type | Kind | Value | Exported |
|---|---|---|---|---|
messageBase |
"Promises must be awaited, end with a... |
const | 'Promises must be awaited, end with a call to .catch, or end with a call to .... |
✗ |
messageBaseVoid |
string |
const | 'Promises must be awaited, end with a call to .catch, end with a call to .the... |
✗ |
messageRejectionHandler |
"A rejection handler that is not a fu... |
const | 'A rejection handler that is not a function will be ignored.' |
✗ |
messagePromiseArray |
"An array of Promises may be unintent... |
const | "An array of Promises may be unintentional. Consider handling the promises' f... |
✗ |
messagePromiseArrayVoid |
string |
const | "An array of Promises may be unintentional. Consider handling the promises' f... |
✗ |
Functions¶
create(context: any, [options]: any): { ArrowFunctionExpression(node: any): void; ExpressionState…¶
Parameters:
contextany[options]any
Returns: { ArrowFunctionExpression(node: any): void; ExpressionStatement(node: any): void; }
Calls:
getParserServices (from ../util)services.program.getTypeCheckercheckNodeisAsyncIifeskipChainExpression (from ../util)isKnownSafePromiseCallisUnhandledPromisecontext.reportisParenthesized (from ../util)getOperatorPrecedenceForNode (from ../util)fixer.insertTextBeforefixer.insertTextAfterRangeaddAwaitfixer.replaceTextRangeservices.getTypeAtLocationvalueMatchesSomeSpecifier (from ../util)typeMatchesSomeSpecifier (from ../util)services.program .getTypeChecker() .getTypeAtLocation( services.esTreeNodeToTSNodeMap.get(rejectionHandler), ) .getCallSignaturesnode.expressions .map(item => isUnhandledPromise(checker, item)) .findservices.esTreeNodeToTSNodeMap.getisPromiseArrayisPromiseLikeparseCatchCall (from ../util/promiseUtils)parseThenCall (from ../util/promiseUtils)isValidRejectionHandlerparseFinallyCall (from ../util/promiseUtils)getTypeAtLocationtsutils .unionConstituents(type) .mapchecker.getApparentTypechecker.isArrayTypechecker.getTypeArgumentschecker.isTupleTypechecker.getTypeAtLocationtsutils.unionConstituentstypeParts.someisBuiltinSymbolLike (from ../util)ty.getPropertychecker.getTypeOfSymbolAtLocationhasMatchingSignatureisFunctionParam
Internal Comments:
// TODO: #5439 (x2)
/* eslint-disable @typescript-eslint/no-non-null-assertion */ (x2)
/* eslint-enable @typescript-eslint/no-non-null-assertion */
// First, check expressions whose resulting types may not be promise-like
// Any child in a comma expression could return a potentially unhandled
// promise, so we check them all regardless of whether the final returned
// value is promise-like.
// Similarly, a `void` expression always returns undefined, so we need to
// see what's inside it without checking the type of the overall expression.
// Check the type. At this point it can't be unhandled if it isn't a promise
// or array thereof.
// await expression addresses promises, but not promise arrays.
// you would think this wouldn't be strictly necessary, since we're
// anyway checking the type of the expression, but, unfortunately TS
// reports the result of `await (promise as Promise<number> & number)`
// as `Promise<number> & number` instead of `number`.
// If the outer expression is a call, a `.catch()` or `.then()` with (x2)
// rejection handler handles the promise. (x2)
// All other cases are unhandled.
// We must be getting the promise-like value from one of the branches of the (x2)
// ternary. Check them directly. (x2)
// Anything else is unhandled.
// The highest priority is to allow anything allowlisted
// Otherwise, we always consider the built-in Promise to be Promise-like... (x2)
// ...and only check all Thenables if explicitly told to
// Modified from tsutils.isThenable() to only consider thenables which can be
// rejected/caught via a second parameter. Original source (MIT licensed):
//
// https://github.com/ajafff/tsutils/blob/49d0d31050b44b81e918eae4fbaf1dfe7b7286af/util/type.ts#L95-L125
Code
create(context, [options]) {
const services = getParserServices(context);
const checker = services.program.getTypeChecker();
const { checkThenables } = options;
// TODO: #5439
/* eslint-disable @typescript-eslint/no-non-null-assertion */
const allowForKnownSafePromises = options.allowForKnownSafePromises!;
const allowForKnownSafeCalls = options.allowForKnownSafeCalls!;
/* eslint-enable @typescript-eslint/no-non-null-assertion */
return {
ArrowFunctionExpression(node): void {
if (node.body.type === AST_NODE_TYPES.UnaryExpression) {
checkNode(node.body, node.body);
}
},
ExpressionStatement(node): void {
if (options.ignoreIIFE && isAsyncIife(node)) {
return;
}
const expression = skipChainExpression(node.expression);
checkNode(node, expression);
},
};
function checkNode(
node: TSESTree.Expression | TSESTree.ExpressionStatement,
expression: TSESTree.Expression,
): void {
if (isKnownSafePromiseCall(expression)) {
return;
}
const { isUnhandled, nonFunctionHandler, promiseArray } =
isUnhandledPromise(checker, expression);
if (isUnhandled) {
if (promiseArray) {
context.report({
node,
messageId: options.ignoreVoid
? 'floatingPromiseArrayVoid'
: 'floatingPromiseArray',
});
} else if (options.ignoreVoid) {
context.report({
node,
messageId: nonFunctionHandler
? 'floatingUselessRejectionHandlerVoid'
: 'floatingVoid',
suggest: [
{
messageId: 'floatingFixVoid',
fix(fixer): TSESLint.RuleFix | TSESLint.RuleFix[] {
if (
isParenthesized(expression, context.sourceCode) ||
getOperatorPrecedenceForNode(expression) >
OperatorPrecedence.Unary
) {
return fixer.insertTextBefore(node, 'void ');
}
return [
fixer.insertTextBefore(node, 'void ('),
fixer.insertTextAfterRange(
[expression.range[1], expression.range[1]],
')',
),
];
},
},
{
messageId: 'floatingFixAwait',
fix: (fixer): TSESLint.RuleFix | TSESLint.RuleFix[] =>
addAwait(fixer, expression, node),
},
],
});
} else {
context.report({
node,
messageId: nonFunctionHandler
? 'floatingUselessRejectionHandler'
: 'floating',
suggest: [
{
messageId: 'floatingFixAwait',
fix: (fixer): TSESLint.RuleFix | TSESLint.RuleFix[] =>
addAwait(fixer, expression, node),
},
],
});
}
}
}
function addAwait(
fixer: TSESLint.RuleFixer,
expression: TSESTree.Expression,
node: TSESTree.Expression | TSESTree.ExpressionStatement,
): TSESLint.RuleFix | TSESLint.RuleFix[] {
if (
expression.type === AST_NODE_TYPES.UnaryExpression &&
expression.operator === 'void'
) {
return fixer.replaceTextRange(
[expression.range[0], expression.range[0] + 4],
'await',
);
}
if (
isParenthesized(expression, context.sourceCode) ||
getOperatorPrecedenceForNode(expression) > OperatorPrecedence.Unary
) {
return fixer.insertTextBefore(node, 'await ');
}
return [
fixer.insertTextBefore(node, 'await ('),
fixer.insertTextAfterRange(
[expression.range[1], expression.range[1]],
')',
),
];
}
function isKnownSafePromiseCall(node: TSESTree.Node): boolean {
if (node.type !== AST_NODE_TYPES.CallExpression) {
return false;
}
const type = services.getTypeAtLocation(node.callee);
if (
valueMatchesSomeSpecifier(
node.callee,
allowForKnownSafeCalls,
services.program,
type,
)
) {
return true;
}
return typeMatchesSomeSpecifier(
type,
allowForKnownSafeCalls,
services.program,
);
}
function isAsyncIife(node: TSESTree.ExpressionStatement): boolean {
if (node.expression.type !== AST_NODE_TYPES.CallExpression) {
return false;
}
return (
node.expression.callee.type ===
AST_NODE_TYPES.ArrowFunctionExpression ||
node.expression.callee.type === AST_NODE_TYPES.FunctionExpression
);
}
function isValidRejectionHandler(rejectionHandler: TSESTree.Node): boolean {
return (
services.program
.getTypeChecker()
.getTypeAtLocation(
services.esTreeNodeToTSNodeMap.get(rejectionHandler),
)
.getCallSignatures().length > 0
);
}
function isUnhandledPromise(
checker: ts.TypeChecker,
node: TSESTree.Node,
): {
isUnhandled: boolean;
nonFunctionHandler?: boolean;
promiseArray?: boolean;
} {
if (node.type === AST_NODE_TYPES.AssignmentExpression) {
return { isUnhandled: false };
}
// First, check expressions whose resulting types may not be promise-like
if (node.type === AST_NODE_TYPES.SequenceExpression) {
// Any child in a comma expression could return a potentially unhandled
// promise, so we check them all regardless of whether the final returned
// value is promise-like.
return (
node.expressions
.map(item => isUnhandledPromise(checker, item))
.find(result => result.isUnhandled) ?? { isUnhandled: false }
);
}
if (
!options.ignoreVoid &&
node.type === AST_NODE_TYPES.UnaryExpression &&
node.operator === 'void'
) {
// Similarly, a `void` expression always returns undefined, so we need to
// see what's inside it without checking the type of the overall expression.
return isUnhandledPromise(checker, node.argument);
}
const tsNode = services.esTreeNodeToTSNodeMap.get(node);
// Check the type. At this point it can't be unhandled if it isn't a promise
// or array thereof.
if (isPromiseArray(tsNode)) {
return { isUnhandled: true, promiseArray: true };
}
// await expression addresses promises, but not promise arrays.
if (node.type === AST_NODE_TYPES.AwaitExpression) {
// you would think this wouldn't be strictly necessary, since we're
// anyway checking the type of the expression, but, unfortunately TS
// reports the result of `await (promise as Promise<number> & number)`
// as `Promise<number> & number` instead of `number`.
return { isUnhandled: false };
}
if (!isPromiseLike(tsNode)) {
return { isUnhandled: false };
}
if (node.type === AST_NODE_TYPES.CallExpression) {
// If the outer expression is a call, a `.catch()` or `.then()` with
// rejection handler handles the promise.
const promiseHandlingMethodCall =
parseCatchCall(node, context) ?? parseThenCall(node, context);
if (promiseHandlingMethodCall != null) {
const onRejected = promiseHandlingMethodCall.onRejected;
if (onRejected != null) {
if (isValidRejectionHandler(onRejected)) {
return { isUnhandled: false };
}
return { isUnhandled: true, nonFunctionHandler: true };
}
return { isUnhandled: true };
}
const promiseFinallyCall = parseFinallyCall(node, context);
if (promiseFinallyCall != null) {
return isUnhandledPromise(checker, promiseFinallyCall.object);
}
// All other cases are unhandled.
return { isUnhandled: true };
}
if (node.type === AST_NODE_TYPES.ConditionalExpression) {
// We must be getting the promise-like value from one of the branches of the
// ternary. Check them directly.
const alternateResult = isUnhandledPromise(checker, node.alternate);
if (alternateResult.isUnhandled) {
return alternateResult;
}
return isUnhandledPromise(checker, node.consequent);
}
if (node.type === AST_NODE_TYPES.LogicalExpression) {
const leftResult = isUnhandledPromise(checker, node.left);
if (leftResult.isUnhandled) {
return leftResult;
}
return isUnhandledPromise(checker, node.right);
}
// Anything else is unhandled.
return { isUnhandled: true };
}
function isPromiseArray(node: ts.Node): boolean {
const type = getTypeAtLocation(checker, node);
if (type == null) {
return false;
}
for (const ty of tsutils
.unionConstituents(type)
.map(t => checker.getApparentType(t))) {
if (checker.isArrayType(ty)) {
const arrayType = checker.getTypeArguments(ty)[0];
if (isPromiseLike(node, arrayType)) {
return true;
}
}
if (checker.isTupleType(ty)) {
for (const tupleElementType of checker.getTypeArguments(ty)) {
if (isPromiseLike(node, tupleElementType)) {
return true;
}
}
}
}
return false;
}
function isPromiseLike(node: ts.Node, type?: ts.Type): boolean {
type ??= checker.getTypeAtLocation(node);
// The highest priority is to allow anything allowlisted
if (
typeMatchesSomeSpecifier(
type,
allowForKnownSafePromises,
services.program,
)
) {
return false;
}
// Otherwise, we always consider the built-in Promise to be Promise-like...
const typeParts = tsutils.unionConstituents(
checker.getApparentType(type),
);
if (
typeParts.some(typePart =>
isBuiltinSymbolLike(services.program, typePart, 'Promise'),
)
) {
return true;
}
// ...and only check all Thenables if explicitly told to
if (!checkThenables) {
return false;
}
// Modified from tsutils.isThenable() to only consider thenables which can be
// rejected/caught via a second parameter. Original source (MIT licensed):
//
// https://github.com/ajafff/tsutils/blob/49d0d31050b44b81e918eae4fbaf1dfe7b7286af/util/type.ts#L95-L125
for (const ty of typeParts) {
const then = ty.getProperty('then');
if (then == null) {
continue;
}
const thenType = checker.getTypeOfSymbolAtLocation(then, node);
if (
hasMatchingSignature(
thenType,
signature =>
signature.parameters.length >= 2 &&
isFunctionParam(checker, signature.parameters[0], node) &&
isFunctionParam(checker, signature.parameters[1], node),
)
) {
return true;
}
}
return false;
}
}
hasMatchingSignature(type: ts.Type, matcher: (signature: ts.Signature) => boolean): boolean¶
Parameters:
typets.Typematcher(signature: ts.Signature) => boolean
Returns: boolean
Calls:
tsutils.unionConstituentst.getCallSignatures().some
Code
isFunctionParam(checker: ts.TypeChecker, param: ts.Symbol, node: ts.Node): boolean¶
Parameters:
checkerts.TypeCheckerparamts.Symbolnodets.Node
Returns: boolean
Calls:
checker.getApparentTypechecker.getTypeOfSymbolAtLocationtsutils.unionConstituentst.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 t of tsutils.unionConstituents(type)) {
if (t.getCallSignatures().length !== 0) {
return true;
}
}
return false;
}
getTypeAtLocation(checker: ts.TypeChecker, node: ts.Node): ts.Type | null¶
Parameters:
checkerts.TypeCheckernodets.Node
Returns: ts.Type | null
Calls:
checker.getTypeAtLocation
Internal Comments:
Code
Internal helpers¶
Declared inside another function in this file.
checkNode(node: TSESTree.Expression | TSESTree.Expressi…, expression: TSESTree.Expression): void¶
Parameters:
nodeTSESTree.Expression | TSESTree.ExpressionStatementexpressionTSESTree.Expression
Returns: void
Calls:
isKnownSafePromiseCallisUnhandledPromisecontext.reportisParenthesized (from ../util)getOperatorPrecedenceForNode (from ../util)fixer.insertTextBeforefixer.insertTextAfterRangeaddAwait
Code
function checkNode(
node: TSESTree.Expression | TSESTree.ExpressionStatement,
expression: TSESTree.Expression,
): void {
if (isKnownSafePromiseCall(expression)) {
return;
}
const { isUnhandled, nonFunctionHandler, promiseArray } =
isUnhandledPromise(checker, expression);
if (isUnhandled) {
if (promiseArray) {
context.report({
node,
messageId: options.ignoreVoid
? 'floatingPromiseArrayVoid'
: 'floatingPromiseArray',
});
} else if (options.ignoreVoid) {
context.report({
node,
messageId: nonFunctionHandler
? 'floatingUselessRejectionHandlerVoid'
: 'floatingVoid',
suggest: [
{
messageId: 'floatingFixVoid',
fix(fixer): TSESLint.RuleFix | TSESLint.RuleFix[] {
if (
isParenthesized(expression, context.sourceCode) ||
getOperatorPrecedenceForNode(expression) >
OperatorPrecedence.Unary
) {
return fixer.insertTextBefore(node, 'void ');
}
return [
fixer.insertTextBefore(node, 'void ('),
fixer.insertTextAfterRange(
[expression.range[1], expression.range[1]],
')',
),
];
},
},
{
messageId: 'floatingFixAwait',
fix: (fixer): TSESLint.RuleFix | TSESLint.RuleFix[] =>
addAwait(fixer, expression, node),
},
],
});
} else {
context.report({
node,
messageId: nonFunctionHandler
? 'floatingUselessRejectionHandler'
: 'floating',
suggest: [
{
messageId: 'floatingFixAwait',
fix: (fixer): TSESLint.RuleFix | TSESLint.RuleFix[] =>
addAwait(fixer, expression, node),
},
],
});
}
}
}
addAwait(fixer: TSESLint.RuleFixer, expression: TSESTree.Expression, node: TSESTree.Expression | TSESTree.Expressi…): TSESLint.RuleFix | TSESLint.RuleFix[]¶
Parameters:
fixerTSESLint.RuleFixerexpressionTSESTree.ExpressionnodeTSESTree.Expression | TSESTree.ExpressionStatement
Returns: TSESLint.RuleFix | TSESLint.RuleFix[]
Calls:
fixer.replaceTextRangeisParenthesized (from ../util)getOperatorPrecedenceForNode (from ../util)fixer.insertTextBeforefixer.insertTextAfterRange
Code
function addAwait(
fixer: TSESLint.RuleFixer,
expression: TSESTree.Expression,
node: TSESTree.Expression | TSESTree.ExpressionStatement,
): TSESLint.RuleFix | TSESLint.RuleFix[] {
if (
expression.type === AST_NODE_TYPES.UnaryExpression &&
expression.operator === 'void'
) {
return fixer.replaceTextRange(
[expression.range[0], expression.range[0] + 4],
'await',
);
}
if (
isParenthesized(expression, context.sourceCode) ||
getOperatorPrecedenceForNode(expression) > OperatorPrecedence.Unary
) {
return fixer.insertTextBefore(node, 'await ');
}
return [
fixer.insertTextBefore(node, 'await ('),
fixer.insertTextAfterRange(
[expression.range[1], expression.range[1]],
')',
),
];
}
isKnownSafePromiseCall(node: TSESTree.Node): boolean¶
Parameters:
nodeTSESTree.Node
Returns: boolean
Calls:
services.getTypeAtLocationvalueMatchesSomeSpecifier (from ../util)typeMatchesSomeSpecifier (from ../util)
Code
function isKnownSafePromiseCall(node: TSESTree.Node): boolean {
if (node.type !== AST_NODE_TYPES.CallExpression) {
return false;
}
const type = services.getTypeAtLocation(node.callee);
if (
valueMatchesSomeSpecifier(
node.callee,
allowForKnownSafeCalls,
services.program,
type,
)
) {
return true;
}
return typeMatchesSomeSpecifier(
type,
allowForKnownSafeCalls,
services.program,
);
}
isAsyncIife(node: TSESTree.ExpressionStatement): boolean¶
Parameters:
nodeTSESTree.ExpressionStatement
Returns: boolean
Code
isValidRejectionHandler(rejectionHandler: TSESTree.Node): boolean¶
Parameters:
rejectionHandlerTSESTree.Node
Returns: boolean
Calls:
services.program .getTypeChecker() .getTypeAtLocation( services.esTreeNodeToTSNodeMap.get(rejectionHandler), ) .getCallSignatures
Code
isUnhandledPromise(checker: ts.TypeChecker, node: TSESTree.Node): { isUnhandled: boolean; nonFunctionHandler?: boolean; promi…¶
Parameters:
checkerts.TypeCheckernodeTSESTree.Node
Returns: {
isUnhandled: boolean;
nonFunctionHandler?: boolean;
promiseArray?: boolean;
}
Calls:
node.expressions .map(item => isUnhandledPromise(checker, item)) .findisUnhandledPromiseservices.esTreeNodeToTSNodeMap.getisPromiseArrayisPromiseLikeparseCatchCall (from ../util/promiseUtils)parseThenCall (from ../util/promiseUtils)isValidRejectionHandlerparseFinallyCall (from ../util/promiseUtils)
Internal Comments:
// First, check expressions whose resulting types may not be promise-like
// Any child in a comma expression could return a potentially unhandled
// promise, so we check them all regardless of whether the final returned
// value is promise-like.
// Similarly, a `void` expression always returns undefined, so we need to
// see what's inside it without checking the type of the overall expression.
// Check the type. At this point it can't be unhandled if it isn't a promise
// or array thereof.
// await expression addresses promises, but not promise arrays.
// you would think this wouldn't be strictly necessary, since we're
// anyway checking the type of the expression, but, unfortunately TS
// reports the result of `await (promise as Promise<number> & number)`
// as `Promise<number> & number` instead of `number`.
// If the outer expression is a call, a `.catch()` or `.then()` with (x2)
// rejection handler handles the promise. (x2)
// All other cases are unhandled.
// We must be getting the promise-like value from one of the branches of the (x2)
// ternary. Check them directly. (x2)
// Anything else is unhandled.
Code
function isUnhandledPromise(
checker: ts.TypeChecker,
node: TSESTree.Node,
): {
isUnhandled: boolean;
nonFunctionHandler?: boolean;
promiseArray?: boolean;
} {
if (node.type === AST_NODE_TYPES.AssignmentExpression) {
return { isUnhandled: false };
}
// First, check expressions whose resulting types may not be promise-like
if (node.type === AST_NODE_TYPES.SequenceExpression) {
// Any child in a comma expression could return a potentially unhandled
// promise, so we check them all regardless of whether the final returned
// value is promise-like.
return (
node.expressions
.map(item => isUnhandledPromise(checker, item))
.find(result => result.isUnhandled) ?? { isUnhandled: false }
);
}
if (
!options.ignoreVoid &&
node.type === AST_NODE_TYPES.UnaryExpression &&
node.operator === 'void'
) {
// Similarly, a `void` expression always returns undefined, so we need to
// see what's inside it without checking the type of the overall expression.
return isUnhandledPromise(checker, node.argument);
}
const tsNode = services.esTreeNodeToTSNodeMap.get(node);
// Check the type. At this point it can't be unhandled if it isn't a promise
// or array thereof.
if (isPromiseArray(tsNode)) {
return { isUnhandled: true, promiseArray: true };
}
// await expression addresses promises, but not promise arrays.
if (node.type === AST_NODE_TYPES.AwaitExpression) {
// you would think this wouldn't be strictly necessary, since we're
// anyway checking the type of the expression, but, unfortunately TS
// reports the result of `await (promise as Promise<number> & number)`
// as `Promise<number> & number` instead of `number`.
return { isUnhandled: false };
}
if (!isPromiseLike(tsNode)) {
return { isUnhandled: false };
}
if (node.type === AST_NODE_TYPES.CallExpression) {
// If the outer expression is a call, a `.catch()` or `.then()` with
// rejection handler handles the promise.
const promiseHandlingMethodCall =
parseCatchCall(node, context) ?? parseThenCall(node, context);
if (promiseHandlingMethodCall != null) {
const onRejected = promiseHandlingMethodCall.onRejected;
if (onRejected != null) {
if (isValidRejectionHandler(onRejected)) {
return { isUnhandled: false };
}
return { isUnhandled: true, nonFunctionHandler: true };
}
return { isUnhandled: true };
}
const promiseFinallyCall = parseFinallyCall(node, context);
if (promiseFinallyCall != null) {
return isUnhandledPromise(checker, promiseFinallyCall.object);
}
// All other cases are unhandled.
return { isUnhandled: true };
}
if (node.type === AST_NODE_TYPES.ConditionalExpression) {
// We must be getting the promise-like value from one of the branches of the
// ternary. Check them directly.
const alternateResult = isUnhandledPromise(checker, node.alternate);
if (alternateResult.isUnhandled) {
return alternateResult;
}
return isUnhandledPromise(checker, node.consequent);
}
if (node.type === AST_NODE_TYPES.LogicalExpression) {
const leftResult = isUnhandledPromise(checker, node.left);
if (leftResult.isUnhandled) {
return leftResult;
}
return isUnhandledPromise(checker, node.right);
}
// Anything else is unhandled.
return { isUnhandled: true };
}
isPromiseArray(node: ts.Node): boolean¶
Parameters:
nodets.Node
Returns: boolean
Calls:
getTypeAtLocationtsutils .unionConstituents(type) .mapchecker.getApparentTypechecker.isArrayTypechecker.getTypeArgumentsisPromiseLikechecker.isTupleType
Code
function isPromiseArray(node: ts.Node): boolean {
const type = getTypeAtLocation(checker, node);
if (type == null) {
return false;
}
for (const ty of tsutils
.unionConstituents(type)
.map(t => checker.getApparentType(t))) {
if (checker.isArrayType(ty)) {
const arrayType = checker.getTypeArguments(ty)[0];
if (isPromiseLike(node, arrayType)) {
return true;
}
}
if (checker.isTupleType(ty)) {
for (const tupleElementType of checker.getTypeArguments(ty)) {
if (isPromiseLike(node, tupleElementType)) {
return true;
}
}
}
}
return false;
}
isPromiseLike(node: ts.Node, type: ts.Type): boolean¶
Parameters:
nodets.Nodetypets.Type
Returns: boolean
Calls:
checker.getTypeAtLocationtypeMatchesSomeSpecifier (from ../util)tsutils.unionConstituentschecker.getApparentTypetypeParts.someisBuiltinSymbolLike (from ../util)ty.getPropertychecker.getTypeOfSymbolAtLocationhasMatchingSignatureisFunctionParam
Internal Comments:
// The highest priority is to allow anything allowlisted
// Otherwise, we always consider the built-in Promise to be Promise-like... (x2)
// ...and only check all Thenables if explicitly told to
// Modified from tsutils.isThenable() to only consider thenables which can be
// rejected/caught via a second parameter. Original source (MIT licensed):
//
// https://github.com/ajafff/tsutils/blob/49d0d31050b44b81e918eae4fbaf1dfe7b7286af/util/type.ts#L95-L125
Code
function isPromiseLike(node: ts.Node, type?: ts.Type): boolean {
type ??= checker.getTypeAtLocation(node);
// The highest priority is to allow anything allowlisted
if (
typeMatchesSomeSpecifier(
type,
allowForKnownSafePromises,
services.program,
)
) {
return false;
}
// Otherwise, we always consider the built-in Promise to be Promise-like...
const typeParts = tsutils.unionConstituents(
checker.getApparentType(type),
);
if (
typeParts.some(typePart =>
isBuiltinSymbolLike(services.program, typePart, 'Promise'),
)
) {
return true;
}
// ...and only check all Thenables if explicitly told to
if (!checkThenables) {
return false;
}
// Modified from tsutils.isThenable() to only consider thenables which can be
// rejected/caught via a second parameter. Original source (MIT licensed):
//
// https://github.com/ajafff/tsutils/blob/49d0d31050b44b81e918eae4fbaf1dfe7b7286af/util/type.ts#L95-L125
for (const ty of typeParts) {
const then = ty.getProperty('then');
if (then == null) {
continue;
}
const thenType = checker.getTypeOfSymbolAtLocation(then, node);
if (
hasMatchingSignature(
thenType,
signature =>
signature.parameters.length >= 2 &&
isFunctionParam(checker, signature.parameters[0], node) &&
isFunctionParam(checker, signature.parameters[1], node),
)
) {
return true;
}
}
return false;
}
Type Aliases¶
Options¶
type Options = [
{
allowForKnownSafeCalls?: TypeOrValueSpecifier[];
allowForKnownSafePromises?: TypeOrValueSpecifier[];
checkThenables?: boolean;
ignoreIIFE?: boolean;
ignoreVoid?: boolean;
},
];
MessageId¶
type MessageId = | 'floating'
| 'floatingFixAwait'
| 'floatingFixVoid'
| 'floatingPromiseArray'
| 'floatingPromiseArrayVoid'
| 'floatingUselessRejectionHandler'
| 'floatingUselessRejectionHandlerVoid'
| 'floatingVoid';
Generated by Syntax Scribe