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