📄 await-thenable¶
📊 Analysis Summary¶
| Metric | Count |
|---|---|
| 🔧 Functions | 6 |
| 📦 Imports | 14 |
| 📑 Type Aliases | 1 |
📚 Table of Contents¶
🛠️ File Location:¶
📂 packages/eslint-plugin/src/rules/await-thenable.ts
📤 Default Export¶
| Property | Value |
|---|---|
name |
'await-thenable' |
meta.type |
'problem' |
meta.docs.description |
'Disallow awaiting a value that is not a Thenable' |
meta.docs.recommended |
'recommended' |
meta.docs.requiresTypeChecking |
true |
meta.hasSuggestions |
true |
meta.messages.await |
'Unexpected await of a non-Promise (non-"Thenable") value.' |
meta.messages.awaitUsingOfNonAsyncDisposable |
'Unexpected await using of a value that is not async disposable.' |
meta.messages.convertToOrdinaryFor |
'Convert to an ordinary for...of loop.' |
meta.messages.forAwaitOfNonAsyncIterable |
'Unexpected for await...of of a value that is not async iterable.' |
meta.messages.invalidPromiseAggregatorInput |
'Unexpected iterable of non-Promise (non-"Thenable") values passed to promise aggregator.' |
meta.messages.removeAwait |
'Remove unnecessary await.' |
meta.schema |
[] |
defaultOptions |
[] |
Entry point: create — documented under Functions.
📦 Imports¶
| Name | Source |
|---|---|
TSESLint |
@typescript-eslint/utils |
TSESTree |
@typescript-eslint/utils |
Awaitable |
../util |
createRule |
../util |
getConstrainedTypeAtLocation |
../util |
getFixOrSuggest |
../util |
getParserServices |
../util |
isAwaitKeyword |
../util |
isTypeAnyType |
../util |
needsToBeAwaited |
../util |
nullThrows |
../util |
NullThrowsReasons |
../util |
getForStatementHeadLoc |
../util/getForStatementHeadLoc |
isPromiseAggregatorMethod |
../util/isPromiseAggregatorMethod |
Functions¶
create(context: any): { AwaitExpression(node: any): void; CallExpression(node: TS…¶
Parameters:
contextany
Returns: { AwaitExpression(node: any): void; CallExpression(node: TSESTree.CallExpression): void; 'ForOfStatement[await=true]'(node: TSESTree.ForOfStatement): void; 'VariableDeclaration[kind="await using"]'(node: TSESTree.VariableDeclaration): void; }
Calls:
getParserServices (from ../util)services.program.getTypeCheckerservices.getTypeAtLocationservices.esTreeNodeToTSNodeMap.getneedsToBeAwaited (from ../util)context.reportnullThrows (from ../util)context.sourceCode.getFirstTokenNullThrowsReasons.MissingTokenfixer.removeisPromiseAggregatorMethod (from ../util/isPromiseAggregatorMethod)node.arguments.atgetConstrainedTypeAtLocation (from ../util)isAlwaysNonAwaitableTypeisInvalidPromiseAggregatorInputisTypeAnyType (from ../util)tsutils .unionConstituents(type) .sometsutils.getWellKnownSymbolPropertyOfTypegetForStatementHeadLoc (from ../util/getForStatementHeadLoc)tsutils .unionConstituents(type) .somegetFixOrSuggest (from ../util)
Internal Comments:
// Note that this suggestion causes broken code for sync iterables
// of promises, since the loop variable is not awaited.
// let the user figure out what to do if there's
// await using a = b, c = d, e = f;
// it's rare and not worth the complexity to handle.
Code
create(context) {
const services = getParserServices(context);
const checker = services.program.getTypeChecker();
return {
AwaitExpression(node): void {
const awaitArgumentEsNode = node.argument;
const awaitArgumentType =
services.getTypeAtLocation(awaitArgumentEsNode);
const awaitArgumentTsNode =
services.esTreeNodeToTSNodeMap.get(awaitArgumentEsNode);
const certainty = needsToBeAwaited(
checker,
awaitArgumentTsNode,
awaitArgumentType,
);
if (certainty === Awaitable.Never) {
context.report({
node,
messageId: 'await',
suggest: [
{
messageId: 'removeAwait',
fix(fixer): TSESLint.RuleFix {
const awaitKeyword = nullThrows(
context.sourceCode.getFirstToken(node, isAwaitKeyword),
NullThrowsReasons.MissingToken('await', 'await expression'),
);
return fixer.remove(awaitKeyword);
},
},
],
});
}
},
CallExpression(node: TSESTree.CallExpression): void {
if (!isPromiseAggregatorMethod(context, services, node)) {
return;
}
const argument = node.arguments.at(0);
if (argument == null) {
return;
}
if (argument.type === TSESTree.AST_NODE_TYPES.ArrayExpression) {
for (const element of argument.elements) {
if (element == null) {
continue;
}
const type = getConstrainedTypeAtLocation(services, element);
const tsNode = services.esTreeNodeToTSNodeMap.get(element);
if (isAlwaysNonAwaitableType(type, tsNode, checker)) {
context.report({
node: element,
messageId: 'invalidPromiseAggregatorInput',
});
}
}
return;
}
const type = getConstrainedTypeAtLocation(services, argument);
if (
isInvalidPromiseAggregatorInput(
checker,
services.esTreeNodeToTSNodeMap.get(argument),
type,
)
) {
context.report({
node: argument,
messageId: 'invalidPromiseAggregatorInput',
});
}
},
'ForOfStatement[await=true]'(node: TSESTree.ForOfStatement): void {
const type = services.getTypeAtLocation(node.right);
if (isTypeAnyType(type)) {
return;
}
const hasAsyncIteratorSymbol = tsutils
.unionConstituents(type)
.some(
typePart =>
tsutils.getWellKnownSymbolPropertyOfType(
typePart,
'asyncIterator',
checker,
) != null,
);
if (!hasAsyncIteratorSymbol) {
context.report({
loc: getForStatementHeadLoc(context.sourceCode, node),
messageId: 'forAwaitOfNonAsyncIterable',
suggest: [
// Note that this suggestion causes broken code for sync iterables
// of promises, since the loop variable is not awaited.
{
messageId: 'convertToOrdinaryFor',
fix(fixer): TSESLint.RuleFix {
const awaitToken = nullThrows(
context.sourceCode.getFirstToken(node, isAwaitKeyword),
NullThrowsReasons.MissingToken('await', 'for await loop'),
);
return fixer.remove(awaitToken);
},
},
],
});
}
},
'VariableDeclaration[kind="await using"]'(
node: TSESTree.VariableDeclaration,
): void {
for (const declarator of node.declarations) {
const init = declarator.init;
if (init == null) {
continue;
}
const type = services.getTypeAtLocation(init);
if (isTypeAnyType(type)) {
continue;
}
const hasAsyncDisposeSymbol = tsutils
.unionConstituents(type)
.some(
typePart =>
tsutils.getWellKnownSymbolPropertyOfType(
typePart,
'asyncDispose',
checker,
) != null,
);
if (!hasAsyncDisposeSymbol) {
context.report({
node: init,
messageId: 'awaitUsingOfNonAsyncDisposable',
// let the user figure out what to do if there's
// await using a = b, c = d, e = f;
// it's rare and not worth the complexity to handle.
...getFixOrSuggest({
fixOrSuggest:
node.declarations.length === 1 ? 'suggest' : 'none',
suggestion: {
messageId: 'removeAwait',
fix(fixer): TSESLint.RuleFix {
const awaitToken = nullThrows(
context.sourceCode.getFirstToken(node, isAwaitKeyword),
NullThrowsReasons.MissingToken('await', 'await using'),
);
return fixer.remove(awaitToken);
},
},
}),
});
}
}
},
};
}
isInvalidPromiseAggregatorInput(checker: ts.TypeChecker, node: ts.Node, type: ts.Type): boolean¶
Parameters:
checkerts.TypeCheckernodets.Nodetypets.Type
Returns: boolean
Calls:
isIterabletsutils.unionConstituentsgetValueTypesOfArrayLikecontainsNonAwaitableType
Internal Comments:
Code
function isInvalidPromiseAggregatorInput(
checker: ts.TypeChecker,
node: ts.Node,
type: ts.Type,
): boolean {
// non array/tuple/iterable types already show up as a type error
if (!isIterable(type, checker)) {
return false;
}
for (const part of tsutils.unionConstituents(type)) {
const valueTypes = getValueTypesOfArrayLike(part, checker);
if (valueTypes != null) {
for (const typeArgument of valueTypes) {
if (containsNonAwaitableType(typeArgument, node, checker)) {
return true;
}
}
}
}
return false;
}
getValueTypesOfArrayLike(type: ts.Type, checker: ts.TypeChecker): readonly ts.Type[] | null¶
Parameters:
typets.Typecheckerts.TypeChecker
Returns: readonly ts.Type[] | null
Calls:
checker.isTupleTypechecker.getTypeArgumentschecker.isArrayLikeTypenullThrows (from ../util)type.getNumberIndexTypetsutils.isTypeReferencechecker.getTypeArguments(type).slice
Internal Comments:
Code
function getValueTypesOfArrayLike(
type: ts.Type,
checker: ts.TypeChecker,
): readonly ts.Type[] | null {
if (checker.isTupleType(type)) {
return checker.getTypeArguments(type);
}
if (checker.isArrayLikeType(type)) {
return [
nullThrows(
type.getNumberIndexType(),
'number index type should exist on an array-like',
),
];
}
// `Iterable<...>`
if (tsutils.isTypeReference(type)) {
return checker.getTypeArguments(type).slice(0, 1);
}
return null;
}
isAlwaysNonAwaitableType(type: ts.Type, node: ts.Node, checker: ts.TypeChecker): boolean¶
Parameters:
typets.Typenodets.Nodecheckerts.TypeChecker
Returns: boolean
Calls:
tsutils .unionConstituents(type) .everyneedsToBeAwaited (from ../util)
Code
containsNonAwaitableType(type: ts.Type, node: ts.Node, checker: ts.TypeChecker): boolean¶
Parameters:
typets.Typenodets.Nodecheckerts.TypeChecker
Returns: boolean
Calls:
tsutils .unionConstituents(type) .someneedsToBeAwaited (from ../util)
Code
isIterable(type: ts.Type, checker: ts.TypeChecker): boolean¶
Parameters:
typets.Typecheckerts.TypeChecker
Returns: boolean
Calls:
tsutils .unionConstituents(type) .everytsutils.getWellKnownSymbolPropertyOfType
Code
Type Aliases¶
MessageId¶
type MessageId = | 'await'
| 'awaitUsingOfNonAsyncDisposable'
| 'convertToOrdinaryFor'
| 'forAwaitOfNonAsyncIterable'
| 'invalidPromiseAggregatorInput'
| 'removeAwait';
Generated by Syntax Scribe