blob: 5c39fe506f2894082dd1a17057bf280603c7d6e9 [file] [log] [blame]
/**
* @fileoverview A Tsetse rule that checks that all promises in async function
* blocks are awaited or used.
*/
import * as tsutils from 'tsutils';
import * as ts from 'typescript';
import {Checker} from '../checker';
import {ErrorCode} from '../error_code';
import {AbstractRule} from '../rule';
const FAILURE_STRING =
'All Promises in async functions must either be awaited or used in an expression.' +
'\n\tSee http://tsetse.info/must-use-promises';
export class Rule extends AbstractRule {
readonly ruleName = 'must-use-promises';
readonly code = ErrorCode.MUST_USE_PROMISES;
register(checker: Checker) {
checker.on(ts.SyntaxKind.CallExpression, checkCallExpression, this.code);
}
}
function checkCallExpression(checker: Checker, node: ts.CallExpression) {
// Short-circuit before using the typechecker if possible, as its expensive.
// Workaround for https://github.com/Microsoft/TypeScript/issues/27997
if (tsutils.isExpressionValueUsed(node) || !inAsyncFunction(node)) {
return;
}
const signature = checker.typeChecker.getResolvedSignature(node);
if (signature === undefined) {
return;
}
const returnType = checker.typeChecker.getReturnTypeOfSignature(signature);
if (!!(returnType.flags & ts.TypeFlags.Void)) {
return;
}
if (tsutils.isThenableType(checker.typeChecker, node)) {
checker.addFailureAtNode(node, FAILURE_STRING);
}
}
function inAsyncFunction(node: ts.Node): boolean {
const isFunction = tsutils.isFunctionDeclaration(node) ||
tsutils.isArrowFunction(node) || tsutils.isMethodDeclaration(node) ||
tsutils.isFunctionExpression(node);
if (isFunction) {
return tsutils.hasModifier(node.modifiers, ts.SyntaxKind.AsyncKeyword);
}
if (node.parent) {
return inAsyncFunction(node.parent);
}
return false;
}