Skip to content

⬅️ Back to Table of Contents

📄 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

export default createRule<Options, MessageId>({ ... })
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:

  • context any
  • [{ checksConditionals, checksSpreads, checksVoidReturn }] any

Returns: any

Calls:

  • getParserServices (from ../util)
  • services.program.getTypeChecker
  • normalizeFlagUnionsOption
  • checkConditional
  • parseChecksVoidReturn
  • node.typeAnnotation.members.some
  • checkedNodes.has
  • checkedNodes.add
  • services.esTreeNodeToTSNodeMap.get
  • isAlwaysThenable
  • context.report
  • isSometimesThenable
  • hasMatchingPromiseTypeArgument
  • parent.arguments.at
  • isArrayMethodCallWithPredicate (from ../util)
  • returnsThenable
  • isPromiseFinallyMethod
  • voidFunctionArguments
  • node.arguments.entries
  • voidArgs.has
  • services.getTypeAtLocation
  • isVoidReturningFunctionType
  • hasWellKnownSymbolWithThenableReturn
  • checker.getTypeAtLocation
  • hasWellKnownSymbolWithVoidReturn
  • isPossiblyFunctionType
  • ts.isPropertyAssignment
  • checker.getContextualType
  • isFunction (from ../util)
  • getFunctionHeadLoc (from ../util)
  • ts.isShorthandPropertyAssignment
  • ts.isMethodDeclaration
  • ts.isComputedPropertyName
  • ts.isObjectLiteralExpression
  • tsutils .unionConstituents(objType) .map(t => checker.getPropertyOfType(t, tsNode.name.getText())) .find
  • checker.getTypeOfSymbolAtLocation
  • complex_call_20907
  • nullThrows (from ../util)
  • parseFinallyCall (from ../util/promiseUtils)
  • isPromiseLike (from ../util)
  • getConstrainedTypeAtLocation (from ../util)
  • getHeritageTypes
  • nodeMember.name?.getText
  • services.tsNodeToESTreeNodeMap.get
  • isStaticMember
  • checkHeritageTypeForMemberReturningVoid
  • getMemberIfExists
  • checker.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:

  • checksVoidReturn boolean | 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:

  • checker ts.TypeChecker
  • node ts.Node

Returns: boolean

Calls:

  • checker.getTypeAtLocation
  • tsutils.unionConstituents
  • checker.getApparentType
  • tsutils.isThenableType
Code
function isSometimesThenable(checker: ts.TypeChecker, node: ts.Node): boolean {
  const type = checker.getTypeAtLocation(node);

  for (const subType of tsutils.unionConstituents(
    checker.getApparentType(type),
  )) {
    if (tsutils.isThenableType(checker, node, subType)) {
      return true;
    }
  }

  return false;
}

isAlwaysThenable(checker: ts.TypeChecker, node: ts.Node): boolean

Parameters:

  • checker ts.TypeChecker
  • node ts.Node

Returns: boolean

Calls:

  • checker.getTypeAtLocation
  • tsutils.unionConstituents
  • checker.getApparentType
  • subType.getProperty
  • checker.getTypeOfSymbolAtLocation
  • subType.getCallSignatures
  • isFunctionParam

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:

  • checker ts.TypeChecker
  • param ts.Symbol
  • node ts.Node

Returns: boolean

Calls:

  • checker.getApparentType
  • checker.getTypeOfSymbolAtLocation
  • tsutils.unionConstituents
  • subType.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:

  • checker ts.TypeChecker
  • node ts.CallExpression | ts.NewExpression
  • type ts.Type
  • index number
  • thenableReturnIndices Set<number>
  • voidReturnIndices Set<number>

Returns: void

Calls:

  • isThenableReturningFunctionType
  • thenableReturnIndices.add
  • isVoidReturningFunctionType
  • thenableReturnIndices.has
  • voidReturnIndices.add
  • checker.getContextualTypeForArgumentAtIndex
  • checkThenableOrVoidArgument

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:

  • checker ts.TypeChecker
  • node ts.CallExpression | ts.NewExpression

Returns: Set<number>

Calls:

  • checker.getTypeAtLocation
  • tsutils.unionConstituents
  • ts.isCallExpression
  • subType.getCallSignatures
  • subType.getConstructSignatures
  • signature.parameters.entries
  • checker.getTypeOfSymbolAtLocation
  • isRestParameterDeclaration (from ../util)
  • checker.isArrayType
  • checker.getTypeArguments
  • checkThenableOrVoidArgument
  • checker.isTupleType
  • voidReturnIndices.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.

Raw JSDoc
/**
 * @returns Whether any call signature of the type has a thenable return type.
 */

Calls:

  • type.getCallSignatures
  • signature.getReturnType
  • tsutils.isThenableType
Code
function anySignatureIsThenableType(
  checker: ts.TypeChecker,
  node: ts.Node,
  type: ts.Type,
): boolean {
  for (const signature of type.getCallSignatures()) {
    const returnType = signature.getReturnType();
    if (tsutils.isThenableType(checker, node, returnType)) {
      return true;
    }
  }

  return false;
}

isThenableReturningFunctionType(checker: ts.TypeChecker, node: ts.Node, type: ts.Type): boolean

Returns: undefined Whether type is a thenable-returning function.

Raw JSDoc
/**
 * @returns Whether type is a thenable-returning function.
 */

Calls:

  • tsutils.unionConstituents
  • anySignatureIsThenableType
Code
function isThenableReturningFunctionType(
  checker: ts.TypeChecker,
  node: ts.Node,
  type: ts.Type,
): boolean {
  for (const subType of tsutils.unionConstituents(type)) {
    if (anySignatureIsThenableType(checker, node, subType)) {
      return true;
    }
  }

  return false;
}

isVoidReturningFunctionType(checker: ts.TypeChecker, node: ts.Node, type: ts.Type): boolean

Returns: undefined Whether type is a void-returning function.

Raw JSDoc
/**
 * @returns Whether type is a void-returning function.
 */

Calls:

  • tsutils.unionConstituents
  • subType.getCallSignatures
  • signature.getReturnType
  • tsutils.isThenableType
  • tsutils.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.

Raw JSDoc
/**
 * @returns Whether expression is a function that returns a thenable.
 */

Calls:

  • checker.getApparentType
  • checker.getTypeAtLocation
  • tsutils .unionConstituents(type) .some
  • anySignatureIsThenableType
Code
function returnsThenable(checker: ts.TypeChecker, node: ts.Node): boolean {
  const type = checker.getApparentType(checker.getTypeAtLocation(node));
  return tsutils
    .unionConstituents(type)
    .some(t => anySignatureIsThenableType(checker, node, t));
}

getHeritageTypes(checker: ts.TypeChecker, tsNode: ts.ClassDeclaration | ts.ClassExpressio…): ts.Type[] | undefined

Parameters:

  • checker ts.TypeChecker
  • tsNode ts.ClassDeclaration | ts.ClassExpression | ts.InterfaceDeclaration

Returns: ts.Type[] | undefined

Calls:

  • tsNode.heritageClauses ?.flatMap(clause => clause.types) .map
  • checker.getTypeAtLocation
Code
function getHeritageTypes(
  checker: ts.TypeChecker,
  tsNode: ts.ClassDeclaration | ts.ClassExpression | ts.InterfaceDeclaration,
): ts.Type[] | undefined {
  return tsNode.heritageClauses
    ?.flatMap(clause => clause.types)
    .map(typeExpression => checker.getTypeAtLocation(typeExpression));
}

getMemberIfExists(type: ts.Type, memberName: string): ts.Symbol | undefined

Returns: undefined The member with the given name in type, if it exists.

Raw JSDoc
/**
 * @returns The member with the given name in `type`, if it exists.
 */

Calls:

  • ts.escapeLeadingUnderscores
  • type.getSymbol()?.members?.get
  • tsutils.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:

  • node TSESTree.Node

Returns: boolean

Code
function isStaticMember(node: TSESTree.Node): boolean {
  return (
    (node.type === AST_NODE_TYPES.MethodDefinition ||
      node.type === AST_NODE_TYPES.PropertyDefinition ||
      node.type === AST_NODE_TYPES.AccessorProperty) &&
    node.static
  );
}

hasWellKnownSymbolWithThenableReturn(checker: ts.TypeChecker, node: ts.Node, type: ts.Type, symbolName: 'asyncDispose' | 'dispose'): boolean

Parameters:

  • checker ts.TypeChecker
  • node ts.Node
  • type ts.Type
  • symbolName 'asyncDispose' | 'dispose'

Returns: boolean

Calls:

  • tsutils .unionConstituents(checker.getApparentType(type)) .some
  • tsutils.getWellKnownSymbolPropertyOfType
  • isThenableReturningFunctionType
  • checker.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:

  • checker ts.TypeChecker
  • node ts.Node
  • type ts.Type
  • symbolName 'asyncDispose' | 'dispose'

Returns: boolean

Calls:

  • tsutils .unionConstituents(checker.getApparentType(type)) .some
  • tsutils.getWellKnownSymbolPropertyOfType
  • isVoidReturningFunctionType
  • checker.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
/**
 * Check that the Promise argument is the same as the rest of the type when it is a Union that contains Promise.
 */

Calls:

  • checker.getTypeAtLocation
  • tsutils.unionConstituents
  • checker.getApparentType
  • unionConstituents.find
  • tsutils.isThenableType
  • unionConstituents.filter
  • checker.getAwaitedType
  • nonPromiseUnionConstituents.every
  • awaitedTypeConstituents.some
  • checker.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:

  • checksConditionals boolean | ChecksConditionalsOptions | undefined

Returns: FlagUnionsOptions

Code
function normalizeFlagUnionsOption(
  checksConditionals: boolean | ChecksConditionalsOptions | undefined,
): FlagUnionsOptions {
  if (!checksConditionals || checksConditionals === true) {
    return 'none';
  }

  return checksConditionals.flagUnions ?? 'none';
}

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
/**
     * 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
     */

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
function checkTestConditional(
      node:
        | TSESTree.ConditionalExpression
        | TSESTree.DoWhileStatement
        | TSESTree.ForStatement
        | TSESTree.IfStatement
        | TSESTree.WhileStatement,
    ): void {
      if (node.test) {
        checkConditional(node.test, true);
      }
    }

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:

  • node any: The AST node to check.
  • isTestExpr any: Whether the node is a descendant of a test expression.
Raw JSDoc
/**
     * 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.
     */

Calls:

  • checkedNodes.has
  • checkedNodes.add
  • checkConditional
  • services.esTreeNodeToTSNodeMap.get
  • isAlwaysThenable
  • context.report
  • isSometimesThenable
  • hasMatchingPromiseTypeArgument

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:

  • node TSESTree.MemberExpression

Returns: void

Calls:

  • parent.arguments.at
  • isArrayMethodCallWithPredicate (from ../util)
  • services.esTreeNodeToTSNodeMap.get
  • returnsThenable
  • context.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:

  • node TSESTree.CallExpression | TSESTree.NewExpression

Returns: void

Calls:

  • isPromiseFinallyMethod
  • services.esTreeNodeToTSNodeMap.get
  • voidFunctionArguments
  • node.arguments.entries
  • voidArgs.has
  • returnsThenable
  • context.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:

  • node TSESTree.AssignmentExpression

Returns: void

Calls:

  • services.esTreeNodeToTSNodeMap.get
  • services.getTypeAtLocation
  • isVoidReturningFunctionType
  • returnsThenable
  • context.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:

  • node TSESTree.VariableDeclarator

Returns: void

Calls:

  • services.esTreeNodeToTSNodeMap.get
  • hasWellKnownSymbolWithThenableReturn
  • checker.getTypeAtLocation
  • context.report
  • services.getTypeAtLocation
  • hasWellKnownSymbolWithVoidReturn
  • isPossiblyFunctionType
  • isVoidReturningFunctionType
  • returnsThenable

Internal Comments:

// syntactically ignore some known-good cases to avoid touching type info

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:

  • node TSESTree.Property

Returns: void

Calls:

  • services.esTreeNodeToTSNodeMap.get
  • ts.isPropertyAssignment
  • checker.getContextualType
  • isVoidReturningFunctionType
  • returnsThenable
  • isFunction (from ../util)
  • context.report
  • getFunctionHeadLoc (from ../util)
  • ts.isShorthandPropertyAssignment
  • ts.isMethodDeclaration
  • ts.isComputedPropertyName
  • ts.isObjectLiteralExpression
  • tsutils .unionConstituents(objType) .map(t => checker.getPropertyOfType(t, tsNode.name.getText())) .find
  • checker.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:

  • node TSESTree.ReturnStatement

Returns: void

Calls:

  • services.esTreeNodeToTSNodeMap.get
  • complex_call_20907
  • isFunction (from ../util)
  • nullThrows (from ../util)
  • isPossiblyFunctionType
  • checker.getContextualType
  • isVoidReturningFunctionType
  • returnsThenable
  • context.report

Internal Comments:

// syntactically ignore some known-good cases to avoid touching type info (x2)

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:

  • node TSESTree.CallExpression

Returns: boolean

Calls:

  • parseFinallyCall (from ../util/promiseUtils)
  • isPromiseLike (from ../util)
  • getConstrainedTypeAtLocation (from ../util)
Code
function isPromiseFinallyMethod(node: TSESTree.CallExpression): boolean {
      const promiseFinallyCall = parseFinallyCall(node, context);

      return (
        promiseFinallyCall != null &&
        isPromiseLike(
          services.program,
          getConstrainedTypeAtLocation(services, promiseFinallyCall.object),
        )
      );
    }

checkClassLikeOrInterfaceNode(node: | TSESTree.ClassDeclaration | TSESTree.…): void

Parameters:

  • node | TSESTree.ClassDeclaration | TSESTree.ClassExpression | TSESTree.TSInterfaceDeclaration

Returns: void

Calls:

  • services.esTreeNodeToTSNodeMap.get
  • getHeritageTypes
  • nodeMember.name?.getText
  • returnsThenable
  • services.tsNodeToESTreeNodeMap.get
  • isStaticMember
  • checkHeritageTypeForMemberReturningVoid

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:

  • nodeMember any: Node member that returns a Promise
  • heritageType any: Heritage type to check against
  • memberName any: 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:

  • getMemberIfExists
  • checker.getTypeOfSymbolAtLocation
  • isVoidReturningFunctionType
  • context.report
  • services.tsNodeToESTreeNodeMap.get
  • checker.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:

  • node TSESTree.JSXAttribute

Returns: void

Calls:

  • services.esTreeNodeToTSNodeMap.get
  • checker.getContextualType
  • isVoidReturningFunctionType
  • returnsThenable
  • context.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:

  • node TSESTree.SpreadElement

Returns: void

Calls:

  • services.esTreeNodeToTSNodeMap.get
  • isSometimesThenable
  • context.report
Code
function checkSpread(node: TSESTree.SpreadElement): void {
      const tsNode = services.esTreeNodeToTSNodeMap.get(node);

      if (isSometimesThenable(checker, tsNode.expression)) {
        context.report({
          node: node.argument,
          messageId: 'spread',
        });
      }
    }

Interfaces

ChecksConditionalsOptions

Interface Code
export interface ChecksConditionalsOptions {
  flagUnions?: FlagUnionsOptions;
}

Properties

Name Type Optional Description
flagUnions FlagUnionsOptions ✓ not shown

ChecksVoidReturnOptions

Interface Code
export interface ChecksVoidReturnOptions {
  arguments?: boolean;
  attributes?: boolean;
  inheritedMethods?: boolean;
  properties?: boolean;
  returns?: boolean;
  variables?: boolean;
}

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

type FlagUnionsOptions = 'all' | 'none' | 'strict';

MessageId

type MessageId = | 'conditional'
  | 'predicate'
  | 'spread'
  | 'voidReturnArgument'
  | 'voidReturnAttribute'
  | 'voidReturnInheritedMethod'
  | 'voidReturnProperty'
  | 'voidReturnReturnValue'
  | 'voidReturnVariable';

Generated by Syntax Scribe