Skip to content

⬅️ Back to Table of Contents

📄 restrict-plus-operands.ts

📊 Analysis Summary

Metric Count
🔧 Functions 4
📦 Imports 7
📊 Variables & Constants 2
📑 Type Aliases 2

📚 Table of Contents

🛠️ File Location:

📂 packages/eslint-plugin/src/rules/restrict-plus-operands.ts

📦 Imports

Name Source
TSESTree @typescript-eslint/utils
createRule ../util
getConstrainedTypeAtLocation ../util
getParserServices ../util
getTypeName ../util
isTypeAnyType ../util
isTypeFlagSet ../util

Variables & Constants

Name Type Kind Value Exported
stringLike string const `stringLikes.length
? stringLikes.length === 1
? string, allowing a string + ${stringLikes[0]}
: string, allowing a string + any of: ${stringLikes.join(', ')}
: 'string'`
hadIndividualComplaint boolean let/var false

Functions

getTypeConstrained(node: TSESTree.Node): ts.Type

Code
function getTypeConstrained(node: TSESTree.Node): ts.Type {
      return typeChecker.getBaseTypeOfLiteralType(
        getConstrainedTypeAtLocation(services, node),
      );
    }
  • Parameters:
  • node: TSESTree.Node
  • Return Type: ts.Type
  • Calls:
  • typeChecker.getBaseTypeOfLiteralType
  • getConstrainedTypeAtLocation (from ../util)

checkPlusOperands(node: TSESTree.AssignmentExpression | TSESTree.BinaryExpression): void

Code
function checkPlusOperands(
      node: TSESTree.AssignmentExpression | TSESTree.BinaryExpression,
    ): void {
      const leftType = getTypeConstrained(node.left);
      const rightType = getTypeConstrained(node.right);

      if (
        leftType === rightType &&
        tsutils.isTypeFlagSet(
          leftType,
          ts.TypeFlags.BigIntLike |
            ts.TypeFlags.NumberLike |
            ts.TypeFlags.StringLike,
        )
      ) {
        return;
      }

      let hadIndividualComplaint = false;

      for (const [baseNode, baseType, otherType] of [
        [node.left, leftType, rightType],
        [node.right, rightType, leftType],
      ] as const) {
        if (
          isTypeFlagSetInUnion(
            baseType,
            ts.TypeFlags.ESSymbolLike |
              ts.TypeFlags.Never |
              ts.TypeFlags.Unknown,
          ) ||
          (!allowAny && isTypeFlagSetInUnion(baseType, ts.TypeFlags.Any)) ||
          (!allowBoolean &&
            isTypeFlagSetInUnion(baseType, ts.TypeFlags.BooleanLike)) ||
          (!allowNullish &&
            isTypeFlagSet(baseType, ts.TypeFlags.Null | ts.TypeFlags.Undefined))
        ) {
          context.report({
            node: baseNode,
            messageId: 'invalid',
            data: {
              type: typeChecker.typeToString(baseType),
              stringLike,
            },
          });
          hadIndividualComplaint = true;
          continue;
        }

        // RegExps also contain ts.TypeFlags.Any & ts.TypeFlags.Object
        for (const subBaseType of tsutils.unionConstituents(baseType)) {
          const typeName = getTypeName(typeChecker, subBaseType);
          if (
            typeName === 'RegExp'
              ? !allowRegExp ||
                tsutils.isTypeFlagSet(otherType, ts.TypeFlags.NumberLike)
              : (!allowAny && isTypeAnyType(subBaseType)) ||
                isDeeplyObjectType(subBaseType)
          ) {
            context.report({
              node: baseNode,
              messageId: 'invalid',
              data: {
                type: typeChecker.typeToString(subBaseType),
                stringLike,
              },
            });
            hadIndividualComplaint = true;
            continue;
          }
        }
      }

      if (hadIndividualComplaint) {
        return;
      }

      for (const [baseType, otherType] of [
        [leftType, rightType],
        [rightType, leftType],
      ] as const) {
        if (
          !allowNumberAndString &&
          isTypeFlagSetInUnion(baseType, ts.TypeFlags.StringLike) &&
          isTypeFlagSetInUnion(
            otherType,
            ts.TypeFlags.NumberLike | ts.TypeFlags.BigIntLike,
          )
        ) {
          return context.report({
            node,
            messageId: 'mismatched',
            data: {
              left: typeChecker.typeToString(leftType),
              right: typeChecker.typeToString(rightType),
              stringLike,
            },
          });
        }

        if (
          isTypeFlagSetInUnion(baseType, ts.TypeFlags.NumberLike) &&
          isTypeFlagSetInUnion(otherType, ts.TypeFlags.BigIntLike)
        ) {
          return context.report({
            node,
            messageId: 'bigintAndNumber',
            data: {
              left: typeChecker.typeToString(leftType),
              right: typeChecker.typeToString(rightType),
            },
          });
        }
      }
    }
  • Parameters:
  • node: TSESTree.AssignmentExpression | TSESTree.BinaryExpression
  • Return Type: void
  • Calls:
  • getTypeConstrained
  • tsutils.isTypeFlagSet
  • isTypeFlagSetInUnion
  • isTypeFlagSet (from ../util)
  • context.report
  • typeChecker.typeToString
  • tsutils.unionConstituents
  • getTypeName (from ../util)
  • isTypeAnyType (from ../util)
  • isDeeplyObjectType
  • Internal Comments:
    // RegExps also contain ts.TypeFlags.Any & ts.TypeFlags.Object
    

isDeeplyObjectType(type: ts.Type): boolean

Code
function isDeeplyObjectType(type: ts.Type): boolean {
  return type.isIntersection()
    ? tsutils.intersectionConstituents(type).every(tsutils.isObjectType)
    : tsutils.unionConstituents(type).every(tsutils.isObjectType);
}
  • Parameters:
  • type: ts.Type
  • Return Type: boolean
  • Calls:
  • type.isIntersection
  • tsutils.intersectionConstituents(type).every
  • tsutils.unionConstituents(type).every

isTypeFlagSetInUnion(type: ts.Type, flag: ts.TypeFlags): boolean

Code
function isTypeFlagSetInUnion(type: ts.Type, flag: ts.TypeFlags): boolean {
  return tsutils
    .unionConstituents(type)
    .some(subType => tsutils.isTypeFlagSet(subType, flag));
}
  • Parameters:
  • type: ts.Type
  • flag: ts.TypeFlags
  • Return Type: boolean
  • Calls:
  • tsutils .unionConstituents(type) .some
  • tsutils.isTypeFlagSet

Type Aliases

Options

type Options = [
  {
    allowAny?: boolean;
    allowBoolean?: boolean;
    allowNullish?: boolean;
    allowNumberAndString?: boolean;
    allowRegExp?: boolean;
    skipCompoundAssignments?: boolean;
  },
];

MessageIds

type MessageIds = 'bigintAndNumber' | 'invalid' | 'mismatched';