Skip to content

⬅️ Back to Table of Contents

πŸ“„ no-unnecessary-type-assertion

πŸ“Š Analysis Summary

Metric Count
πŸ”§ Functions 38
πŸ“¦ Imports 18
πŸ“‘ Type Aliases 2

πŸ“š Table of Contents

πŸ› οΈ File Location:

πŸ“‚ packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts

πŸ“€ Default Export

export default createRule<Options, MessageIds>({ ... })
Property Value
name 'no-unnecessary-type-assertion'
meta.type 'suggestion'
meta.docs.description 'Disallow type assertions that do not change the type of an expression'
meta.docs.recommended 'recommended'
meta.docs.requiresTypeChecking true
meta.fixable 'code'
meta.messages.contextuallyUnnecessary 'This assertion is unnecessary since the receiver accepts the original type of the expression.'
meta.messages.unnecessaryAssertion 'This assertion is unnecessary since it does not change the type of the expression.'
meta.schema [ { type: 'object', additionalProperties: false, properties: { checkLiteralConstAssertions: { type: 'boolean', descri...
defaultOptions [{}]

Entry point: create β€” documented under Functions.


πŸ“¦ Imports

Name Source
Scope @typescript-eslint/scope-manager
TSESTree @typescript-eslint/utils
ReportFixFunction @typescript-eslint/utils/ts-eslint
RuleFix @typescript-eslint/utils/ts-eslint
AST_NODE_TYPES @typescript-eslint/utils
AST_TOKEN_TYPES @typescript-eslint/utils
createRule ../util
getConstrainedTypeAtLocation ../util
getContextualType ../util
getDeclaration ../util
getModifiers ../util
getParserServices ../util
isNullableType ../util
isStartOfArrowFunctionBody ../util
isStartOfExpressionStatement ../util
isTypeFlagSet ../util
nullThrows ../util
NullThrowsReasons ../util

Functions

create(context: any, [options]: any): { 'TSAsExpression, TSTypeAssertion'(node: TSESTree.TSAsExpr…

Parameters:

  • context any
  • [options] any

Returns: { 'TSAsExpression, TSTypeAssertion'(node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion): void; TSNonNullExpression(node: any): void; }

Calls:

  • getParserServices (from ../util)
  • services.program.getTypeChecker
  • services.program.getCompilerOptions
  • getDeclaration (from ../util)
  • tsutils.isStrictCompilerOptionEnabled
  • ts.isVariableDeclaration
  • ts.isVariableDeclarationList
  • services.tsNodeToESTreeNodeMap.has
  • services.tsNodeToESTreeNodeMap.get
  • context.sourceCode.getScope
  • checker.getTypeFromTypeNode
  • getConstrainedTypeAtLocation (from ../util)
  • ts.isVariableStatement
  • tsutils.includesModifier
  • getModifiers (from ../util)
  • isTemplateLiteralWithExpressions
  • containsTypeVariable
  • isTypeFlagSet (from ../util)
  • tsutils.isCompilerOptionEnabled
  • areUnionPartsEquivalentIgnoringUndefined
  • hasIndexSignature
  • containsAny
  • isConceptuallyLiteral
  • cast .getProperties() .some
  • isTypeLiteral
  • checker.getTypeOfSymbol
  • cast.isIntersection
  • uncast.isIntersection
  • castParts.find
  • tsutils.isTypeParameter
  • castParts.some
  • isEmptyObjectType
  • checker.getBaseConstraintOfType
  • isNullableType (from ../util)
  • hasSameProperties
  • haveSameTypeArguments
  • areMutuallyAssignable
  • type.isLiteral
  • tsutils.isBooleanLiteralType
  • tsutils .unionConstituents(type) .some
  • checker.getIndexInfosOfType
  • tsutils.isTypeReference
  • checker.getTypeArguments
  • seen.has
  • seen.add
  • predicate
  • type.isUnionOrIntersection
  • type.types.some
  • typeContains
  • getTypeArguments
  • type .getCallSignatures() .flatMap
  • sig.getReturnType
  • sig.getParameters().map
  • nestedTypes.some
  • sig.getTypeParameters
  • contextual.getProperties().some
  • checker.getSignaturesOfType
  • contextualSigs.some
  • uncast.getProperty
  • prop.getEscapedName
  • checker .getSignaturesOfType( checker.getTypeOfSymbol(uncastProp), ts.SignatureKind.Call, ) .some
  • uncast.getProperties
  • cast.getProperties
  • castProps.map
  • p.getEscapedName
  • uncastProps.every
  • castPropNames.has
  • tsutils.isPropertyReadonlyInType
  • uncastArgs.every
  • checker.isTypeAssignableTo
  • tsutils .unionConstituents(uncast) .filter
  • tsutils.unionConstituents(cast).filter
  • castParts.every
  • uncastPartsSet.has
  • getOriginalExpression
  • services.getTypeAtLocation
  • isTypeUnchanged
  • CONCEPTUALLY_LITERAL_TYPES.has
  • type.getProperties
  • type.getCallSignatures
  • type.getConstructSignatures
  • type.getStringIndexType
  • type.getNumberIndexType
  • type.getCallSignatures().some
  • parent.arguments.includes
  • services .getTypeAtLocation(parent.callee) .getNonNullableType
  • calleeType.getCallSignatures
  • parent.arguments.indexOf
  • signatures.map
  • sig.getParameters
  • ts.isParameter
  • paramTypes.some
  • definedParamTypes.every
  • services.esTreeNodeToTSNodeMap.get
  • checker.getContextualType(objectTsNode)?.isUnion
  • checker.getContextualType
  • checker.getNonNullableType
  • nonNullableContextualType.isUnion
  • current.arguments.includes
  • checker.getTypeAtLocation
  • hasGenericCallSignature
  • isInGenericContext
  • hasPhantomTypeArguments
  • SKIP_PARENT_TYPES.has
  • isInDestructuringDeclaration
  • isPropertyInProblematicContext
  • isAssignmentInNonStatementContext
  • isRightHandSideOfLogicalAssignment
  • isArgumentToOverloadedFunction
  • isIIFE
  • functionType.getCallSignatures
  • checker.getReturnTypeOfSignature
  • checker.getVoidType
  • nullThrows (from ../util)
  • context.sourceCode.getTokenBefore
  • NullThrowsReasons.MissingToken
  • context.sourceCode.getTokenAfter
  • ['{', 'function', 'class'].includes
  • isStartOfExpressionStatement (from ../util)
  • isStartOfArrowFunctionBody (from ../util)
  • fixes.push
  • fixer.insertTextBefore
  • fixer.removeRange
  • fixer.insertTextAfter
  • isDoubleAssertionUnnecessary
  • context.report
  • context.sourceCode.getText
  • fixer.replaceText
  • options.typesToIgnore?.includes
  • isConstAssertion
  • getUncastType
  • isImplicitlyNarrowedLiteralDeclaration
  • createAssertionFixer
  • shouldSkipContextualTypeFallback
  • node.parent.arguments.includes
  • castType.isUnion
  • hasPhantomTypeArgumentMismatch
  • genericsMismatch
  • reportDoubleAssertionIfUnnecessary
  • context.sourceCode.getLastToken
  • isPossiblyUsedBeforeAssigned
  • getContextualType (from ../util)

Internal Comments:

/**
     * Returns true if there's a chance the variable has been used before a value has been assigned to it
     */
// don't know what the declaration is for some reason, so just assume the worst
// non-strict mode doesn't care about used before assigned errors (x4)
// ignore class properties as they are compile time guarded (x3)
// also ignore function arguments as they can't be used before defined (x3)
// For var declarations, we need to check whether the node
// is actually in a descendant of its declaration or not. If not,
// it may be used before defined.
// eg
// if (Math.random() < 0.5) {
//     var x: number  = 2;
// } else {
//     x!.toFixed();
// }
// var (x4)
// If they are not in the same file it will not exist. (x4)
// This situation must not occur using before defined. (x4)
// is it `const x!: number` (x5)
// check if the defined variable type has changed since assignment (x2)
// `declare`s are never narrowed, so never skip them
// possibly used before assigned, so just skip it
// better to false negative and skip it, than false positive and fix to compile erroring code
//
// no better way to figure this out right now
// https://github.com/Microsoft/TypeScript/issues/31124
/**
       * Even on `const` variable declarations, template literals with expressions can sometimes be widened without a type assertion.
       * @see https://github.com/typescript-eslint/typescript-eslint/issues/8737
       */
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion (x2)
// Workaround for https://github.com/typescript-eslint/typescript-eslint/issues/12705
// An optional-chained callee (`foo?.bar(...)`) types as `<method> | undefined`, (x2)
// and a union exposes no call signatures β€” strip the nullability first. (x2)
/**
       * Interpolated template literals can be widened to `string` while contextual
       * typing still accepts them, so the assertion may be required.
       * @see https://github.com/typescript-eslint/typescript-eslint/issues/12276
       */
// Special handling for IIFE: extract the function's return type
// If the function has no explicit return type annotation and returns undefined,
// treat it as void (TypeScript infers () => {} as () => undefined, but it should be void)
// Removing the angle brackets leaves the asserted operand at the (x2)
// assertion's position, so its first token leads whatever the (x2)
// assertion led. A leading `{`/`function`/`class` at the start of an (x2)
// expression statement is parsed as a block / function or class (x2)
// declaration, and a leading `{` at the start of a concise arrow body (x2)
// is parsed as a block body. In those positions the operand must be (x2)
// wrapped in parentheses to stay an expression. (x2)
// for all other = assignments we ignore non-null checks
// this is because non-null assertions can change the type-flow of the code
// so whilst they might be unnecessary for the assignment - they are necessary
// for following code
// Check both the constrained type and the actual type. (x2)
// If either is nullable, we should not report the assertion as unnecessary. (x2)
// This handles cases like generic constraints with `any` where the (x2)
// constrained type is `any` (nullable) but the actual type might be (x2)
// a type parameter that TypeScript treats nominally. (x2)
// See: https://github.com/typescript-eslint/typescript-eslint/issues/11559 (x3)
// we know it's a nullable type
// so figure out if the variable is used in a place that accepts nullable types
// If the constrained type differs from the actual type (e.g., when dealing
// with unresolved generic type parameters), we should not report the assertion
// as contextually unnecessary. TypeScript may still require the assertion
// even if the constraint is nullable (like `any`).
// in strict mode you can't assign null to undefined, so we have to make sure that (x2)
// the two types share a nullable type (x2)
// make sure that the parent accepts the same types (x2)
// i.e. assigning `string | null | undefined` to `string | undefined` is invalid (x2)

Code
create(context, [options]) {
    const services = getParserServices(context);
    const checker = services.program.getTypeChecker();
    const compilerOptions = services.program.getCompilerOptions();

    /**
     * Returns true if there's a chance the variable has been used before a value has been assigned to it
     */
    function isPossiblyUsedBeforeAssigned(node: TSESTree.Expression): boolean {
      const declaration = getDeclaration(services, node);
      if (!declaration) {
        // don't know what the declaration is for some reason, so just assume the worst
        return true;
      }

      if (
        // non-strict mode doesn't care about used before assigned errors
        tsutils.isStrictCompilerOptionEnabled(
          compilerOptions,
          'strictNullChecks',
        ) &&
        // ignore class properties as they are compile time guarded
        // also ignore function arguments as they can't be used before defined
        ts.isVariableDeclaration(declaration)
      ) {
        // For var declarations, we need to check whether the node
        // is actually in a descendant of its declaration or not. If not,
        // it may be used before defined.

        // eg
        // if (Math.random() < 0.5) {
        //     var x: number  = 2;
        // } else {
        //     x!.toFixed();
        // }
        if (
          ts.isVariableDeclarationList(declaration.parent) &&
          // var
          declaration.parent.flags === ts.NodeFlags.None &&
          // If they are not in the same file it will not exist.
          // This situation must not occur using before defined.
          services.tsNodeToESTreeNodeMap.has(declaration)
        ) {
          const declaratorNode: TSESTree.VariableDeclaration =
            services.tsNodeToESTreeNodeMap.get(declaration);
          const scope = context.sourceCode.getScope(node);
          const declaratorScope = context.sourceCode.getScope(declaratorNode);
          let parentScope: Scope | null = declaratorScope;
          while ((parentScope = parentScope.upper)) {
            if (parentScope === scope) {
              return true;
            }
          }
        }

        if (
          // is it `const x!: number`
          declaration.initializer == null &&
          declaration.exclamationToken == null &&
          declaration.type != null
        ) {
          // check if the defined variable type has changed since assignment
          const declarationType = checker.getTypeFromTypeNode(declaration.type);
          const type = getConstrainedTypeAtLocation(services, node);
          if (
            declarationType === type &&
            // `declare`s are never narrowed, so never skip them
            !(
              ts.isVariableDeclarationList(declaration.parent) &&
              ts.isVariableStatement(declaration.parent.parent) &&
              tsutils.includesModifier(
                getModifiers(declaration.parent.parent),
                ts.SyntaxKind.DeclareKeyword,
              )
            )
          ) {
            // possibly used before assigned, so just skip it
            // better to false negative and skip it, than false positive and fix to compile erroring code
            //
            // no better way to figure this out right now
            // https://github.com/Microsoft/TypeScript/issues/31124
            return true;
          }
        }
      }
      return false;
    }

    function isConstAssertion(node: TSESTree.TypeNode): boolean {
      return (
        node.type === AST_NODE_TYPES.TSTypeReference &&
        node.typeName.type === AST_NODE_TYPES.Identifier &&
        node.typeName.name === 'const'
      );
    }

    function isTemplateLiteralWithExpressions(expression: TSESTree.Expression) {
      return (
        expression.type === AST_NODE_TYPES.TemplateLiteral &&
        expression.expressions.length !== 0
      );
    }

    function isImplicitlyNarrowedLiteralDeclaration({
      expression,
      parent,
    }: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion): boolean {
      /**
       * Even on `const` variable declarations, template literals with expressions can sometimes be widened without a type assertion.
       * @see https://github.com/typescript-eslint/typescript-eslint/issues/8737
       */
      if (isTemplateLiteralWithExpressions(expression)) {
        return false;
      }

      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
      const maybeDeclarationNode = parent.parent!;

      return (
        (maybeDeclarationNode.type === AST_NODE_TYPES.VariableDeclaration &&
          maybeDeclarationNode.kind === 'const') ||
        (parent.type === AST_NODE_TYPES.PropertyDefinition && parent.readonly)
      );
    }

    function isTypeUnchanged(
      node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
      expression: TSESTree.Expression,
      uncast: ts.Type,
      cast: ts.Type,
    ): boolean {
      if (uncast === cast) {
        return true;
      }

      if (
        node.typeAnnotation.type === AST_NODE_TYPES.TSIntersectionType &&
        containsTypeVariable(cast)
      ) {
        return false;
      }

      if (
        isTypeFlagSet(uncast, ts.TypeFlags.Undefined) &&
        isTypeFlagSet(cast, ts.TypeFlags.Undefined) &&
        tsutils.isCompilerOptionEnabled(
          compilerOptions,
          'exactOptionalPropertyTypes',
        )
      ) {
        return areUnionPartsEquivalentIgnoringUndefined(uncast, cast);
      }

      if (
        (isTypeFlagSet(uncast, ts.TypeFlags.NonPrimitive) &&
          !isTypeFlagSet(cast, ts.TypeFlags.NonPrimitive)) ||
        (hasIndexSignature(uncast) && !hasIndexSignature(cast)) ||
        containsAny(uncast) ||
        containsAny(cast) ||
        (containsTypeVariable(cast) && !containsTypeVariable(uncast))
      ) {
        return false;
      }

      if (
        isConceptuallyLiteral(expression) &&
        (expression.type !== AST_NODE_TYPES.ObjectExpression ||
          expression.properties.length === 0 ||
          cast
            .getProperties()
            .some(p => isTypeLiteral(checker.getTypeOfSymbol(p))))
      ) {
        return false;
      }

      if (cast.isIntersection() && !uncast.isIntersection()) {
        const castParts = cast.types;
        const otherPart = castParts.find(part => part !== uncast);
        if (
          tsutils.isTypeParameter(uncast) &&
          castParts.length === 2 &&
          castParts.some(part => part === uncast) &&
          otherPart != null &&
          isEmptyObjectType(otherPart) &&
          !containsTypeVariable(otherPart)
        ) {
          const constraint = checker.getBaseConstraintOfType(uncast);
          if (constraint && !isNullableType(constraint)) {
            return true;
          }
        }
        return false;
      }

      if (
        !hasSameProperties(uncast, cast) ||
        !haveSameTypeArguments(uncast, cast)
      ) {
        return false;
      }

      return areMutuallyAssignable(uncast, cast);
    }

    function isTypeLiteral(type: ts.Type): boolean {
      return type.isLiteral() || tsutils.isBooleanLiteralType(type);
    }

    function hasIndexSignature(type: ts.Type): boolean {
      return tsutils
        .unionConstituents(type)
        .some(part => checker.getIndexInfosOfType(part).length > 0);
    }

    function getTypeArguments(type: ts.Type): readonly ts.Type[] {
      return (
        type.aliasTypeArguments ??
        (tsutils.isTypeReference(type) ? checker.getTypeArguments(type) : [])
      );
    }

    function typeContains(
      type: ts.Type,
      predicate: (type: ts.Type) => boolean,
      seen = new Set<ts.Type>(),
    ): boolean {
      if (seen.has(type)) {
        return false;
      }
      seen.add(type);
      if (predicate(type)) {
        return true;
      }
      if (type.isUnionOrIntersection()) {
        return type.types.some(t => typeContains(t, predicate, seen));
      }
      const nestedTypes = [
        ...getTypeArguments(type),
        ...type
          .getCallSignatures()
          .flatMap(sig => [
            sig.getReturnType(),
            ...sig.getParameters().map(p => checker.getTypeOfSymbol(p)),
          ]),
      ];
      return nestedTypes.some(t => typeContains(t, predicate, seen));
    }

    function containsAny(type: ts.Type): boolean {
      try {
        return typeContains(type, t => isTypeFlagSet(t, ts.TypeFlags.Any));
      } catch (error) {
        // Workaround for https://github.com/typescript-eslint/typescript-eslint/issues/12705
        if (
          error instanceof RangeError &&
          error.message === 'Maximum call stack size exceeded'
        ) {
          return false;
        }
        throw error;
      }
    }

    function containsTypeVariable(type: ts.Type): boolean {
      return typeContains(type, t =>
        isTypeFlagSet(t, ts.TypeFlags.TypeVariable | ts.TypeFlags.Index),
      );
    }

    function hasPhantomTypeArguments(type: ts.Type): boolean {
      return isEmptyObjectType(type) && getTypeArguments(type).length > 0;
    }

    function hasTypeParams(sig: ts.Signature): boolean {
      return (sig.getTypeParameters()?.length ?? 0) > 0;
    }

    function genericsMismatch(uncast: ts.Type, contextual: ts.Type): boolean {
      return contextual.getProperties().some(prop => {
        const contextualSigs = checker.getSignaturesOfType(
          checker.getTypeOfSymbol(prop),
          ts.SignatureKind.Call,
        );
        if (!contextualSigs.some(hasTypeParams)) {
          return false;
        }
        const uncastProp = uncast.getProperty(prop.getEscapedName() as string);
        if (!uncastProp) {
          return true;
        }
        return !checker
          .getSignaturesOfType(
            checker.getTypeOfSymbol(uncastProp),
            ts.SignatureKind.Call,
          )
          .some(hasTypeParams);
      });
    }

    function hasSameProperties(uncast: ts.Type, cast: ts.Type): boolean {
      const uncastProps = uncast.getProperties();
      const castProps = cast.getProperties();
      if (uncastProps.length !== castProps.length) {
        return false;
      }
      const castPropNames = new Set(castProps.map(p => p.getEscapedName()));
      return uncastProps.every(prop => {
        const name = prop.getEscapedName();
        return (
          castPropNames.has(name) &&
          tsutils.isPropertyReadonlyInType(uncast, name, checker) ===
            tsutils.isPropertyReadonlyInType(cast, name, checker)
        );
      });
    }

    function haveSameTypeArguments(uncast: ts.Type, cast: ts.Type): boolean {
      const uncastArgs = getTypeArguments(uncast);
      const castArgs = getTypeArguments(cast);
      return (
        uncastArgs.length === castArgs.length &&
        uncastArgs.every((arg, i) => arg === castArgs[i])
      );
    }

    function areMutuallyAssignable(a: ts.Type, b: ts.Type): boolean {
      return (
        checker.isTypeAssignableTo(a, b) && checker.isTypeAssignableTo(b, a)
      );
    }

    function areUnionPartsEquivalentIgnoringUndefined(
      uncast: ts.Type,
      cast: ts.Type,
    ): boolean {
      const filterUndefined = (part: ts.Type): boolean =>
        !isTypeFlagSet(part, ts.TypeFlags.Undefined);
      const uncastParts = tsutils
        .unionConstituents(uncast)
        .filter(filterUndefined);
      const castParts = tsutils.unionConstituents(cast).filter(filterUndefined);
      if (uncastParts.length !== castParts.length) {
        return false;
      }
      const uncastPartsSet = new Set(uncastParts);
      return castParts.every(part => uncastPartsSet.has(part));
    }

    function getOriginalExpression(
      node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
    ): TSESTree.Expression {
      let current = node.expression;
      while (
        current.type === AST_NODE_TYPES.TSAsExpression ||
        current.type === AST_NODE_TYPES.TSTypeAssertion
      ) {
        current = current.expression;
      }
      return current;
    }

    function isDoubleAssertionUnnecessary(
      node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
      contextualType: ts.Type | undefined,
    ): false | MessageIds {
      const innerExpression = node.expression;
      if (
        innerExpression.type !== AST_NODE_TYPES.TSAsExpression &&
        innerExpression.type !== AST_NODE_TYPES.TSTypeAssertion
      ) {
        return false;
      }

      const originalExpr = getOriginalExpression(node);
      const originalType = services.getTypeAtLocation(originalExpr);
      const castType = services.getTypeAtLocation(node);

      if (
        isTypeUnchanged(node, innerExpression, originalType, castType) &&
        !isTypeFlagSet(castType, ts.TypeFlags.Any)
      ) {
        return 'unnecessaryAssertion';
      }

      if (contextualType) {
        const intermediateType = services.getTypeAtLocation(innerExpression);
        if (
          (isTypeFlagSet(intermediateType, ts.TypeFlags.Any) ||
            isTypeFlagSet(intermediateType, ts.TypeFlags.Unknown)) &&
          checker.isTypeAssignableTo(originalType, contextualType)
        ) {
          return 'contextuallyUnnecessary';
        }
      }

      return false;
    }

    const CONCEPTUALLY_LITERAL_TYPES = new Set([
      AST_NODE_TYPES.Literal,
      AST_NODE_TYPES.ArrayExpression,
      AST_NODE_TYPES.ObjectExpression,
      AST_NODE_TYPES.TemplateLiteral,
      AST_NODE_TYPES.ClassExpression,
      AST_NODE_TYPES.FunctionExpression,
      AST_NODE_TYPES.ArrowFunctionExpression,
      AST_NODE_TYPES.JSXElement,
      AST_NODE_TYPES.JSXFragment,
    ]);

    function isConceptuallyLiteral(node: TSESTree.Node): boolean {
      return CONCEPTUALLY_LITERAL_TYPES.has(node.type);
    }

    function isIIFE(
      expression: TSESTree.Expression,
    ): expression is TSESTree.CallExpression & {
      callee: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression;
    } {
      return (
        expression.type === AST_NODE_TYPES.CallExpression &&
        (expression.callee.type === AST_NODE_TYPES.ArrowFunctionExpression ||
          expression.callee.type === AST_NODE_TYPES.FunctionExpression)
      );
    }

    function isEmptyObjectType(type: ts.Type): boolean {
      return (
        isTypeFlagSet(type, ts.TypeFlags.NonPrimitive) ||
        (type.getProperties().length === 0 &&
          !type.getCallSignatures().length &&
          !type.getConstructSignatures().length &&
          !type.getStringIndexType() &&
          !type.getNumberIndexType())
      );
    }

    function hasGenericCallSignature(type: ts.Type): boolean {
      return type.getCallSignatures().some(hasTypeParams);
    }

    function isArgumentToOverloadedFunction(
      node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
    ): boolean {
      const { parent } = node;
      if (
        (parent.type !== AST_NODE_TYPES.CallExpression &&
          parent.type !== AST_NODE_TYPES.NewExpression) ||
        !parent.arguments.includes(node)
      ) {
        return false;
      }
      // An optional-chained callee (`foo?.bar(...)`) types as `<method> | undefined`,
      // and a union exposes no call signatures β€” strip the nullability first.
      const calleeType = services
        .getTypeAtLocation(parent.callee)
        .getNonNullableType();
      const signatures = calleeType.getCallSignatures();
      if (signatures.length <= 1) {
        return false;
      }

      const argIndex = parent.arguments.indexOf(node);
      const paramTypes = signatures.map(sig => {
        const params = sig.getParameters();
        if (argIndex >= params.length) {
          return undefined;
        }
        const param = params[argIndex];
        let paramType = checker.getTypeOfSymbol(param);
        if (
          param.valueDeclaration &&
          ts.isParameter(param.valueDeclaration) &&
          param.valueDeclaration.dotDotDotToken
        ) {
          const typeArgs = getTypeArguments(paramType);
          if (typeArgs.length > 0) {
            paramType = typeArgs[0];
          }
        }
        return paramType;
      });

      if (paramTypes.some(type => type == null)) {
        return true;
      }

      const definedParamTypes = paramTypes as ts.Type[];
      const firstParamType = definedParamTypes[0];
      if (definedParamTypes.every(type => type === firstParamType)) {
        return false;
      }

      const uncastType = services.getTypeAtLocation(node.expression);
      return !definedParamTypes.every(type =>
        checker.isTypeAssignableTo(uncastType, type),
      );
    }

    function isInDestructuringDeclaration(
      node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
    ): boolean {
      const { parent } = node;
      return (
        parent.type === AST_NODE_TYPES.VariableDeclarator &&
        parent.init === node &&
        (parent.id.type === AST_NODE_TYPES.ObjectPattern ||
          parent.id.type === AST_NODE_TYPES.ArrayPattern)
      );
    }

    function isPropertyInProblematicContext(
      node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
    ): boolean {
      const { parent } = node;
      if (parent.type !== AST_NODE_TYPES.Property || parent.value !== node) {
        return false;
      }
      const objectExpr = parent.parent;
      if (objectExpr.type !== AST_NODE_TYPES.ObjectExpression) {
        return false;
      }
      const objectTsNode = services.esTreeNodeToTSNodeMap.get(objectExpr);
      if (checker.getContextualType(objectTsNode)?.isUnion()) {
        const nodeTsNode = services.esTreeNodeToTSNodeMap.get(node);
        const propContextualType = checker.getContextualType(nodeTsNode);
        if (propContextualType == null) {
          return true;
        }
        const nonNullableContextualType =
          checker.getNonNullableType(propContextualType);
        if (nonNullableContextualType.isUnion()) {
          return true;
        }
        const uncastType = services.getTypeAtLocation(node.expression);
        return !checker.isTypeAssignableTo(
          uncastType,
          nonNullableContextualType,
        );
      }
      const objectParent = objectExpr.parent;
      return (
        objectParent.type === AST_NODE_TYPES.TSSatisfiesExpression ||
        (objectParent.type === AST_NODE_TYPES.CallExpression &&
          objectParent.parent.type === AST_NODE_TYPES.TSSatisfiesExpression)
      );
    }

    function isAssignmentInNonStatementContext(
      node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
    ): boolean {
      const { parent } = node;
      if (
        parent.type !== AST_NODE_TYPES.AssignmentExpression ||
        parent.right !== node
      ) {
        return false;
      }
      const assignmentParent = parent.parent;
      return assignmentParent.type !== AST_NODE_TYPES.ExpressionStatement;
    }

    function isRightHandSideOfLogicalAssignment(
      node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
    ): boolean {
      const { parent } = node;
      return (
        parent.type === AST_NODE_TYPES.AssignmentExpression &&
        parent.right === node &&
        (parent.operator === '&&=' ||
          parent.operator === '||=' ||
          parent.operator === '??=')
      );
    }

    function isInGenericContext(
      node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
    ): boolean {
      let seenFunction = false;
      for (
        let current: TSESTree.Node | undefined = node.parent;
        current;
        current = current.parent
      ) {
        if (current.type === AST_NODE_TYPES.FunctionDeclaration) {
          return false;
        }
        if (
          current.type === AST_NODE_TYPES.FunctionExpression ||
          current.type === AST_NODE_TYPES.ArrowFunctionExpression
        ) {
          if (current.body.type === AST_NODE_TYPES.BlockStatement) {
            return false;
          }
          if (seenFunction) {
            return false;
          }
          seenFunction = true;
        }
        if (
          current.type === AST_NODE_TYPES.CallExpression ||
          current.type === AST_NODE_TYPES.NewExpression
        ) {
          if (current.typeArguments != null) {
            continue;
          }
          if (
            current.type === AST_NODE_TYPES.CallExpression &&
            current.callee.type === AST_NODE_TYPES.MemberExpression &&
            current.arguments.includes(node)
          ) {
            continue;
          }
          const calleeType = checker.getTypeAtLocation(
            services.esTreeNodeToTSNodeMap.get(current.callee),
          );
          if (hasGenericCallSignature(calleeType)) {
            return true;
          }
        }
      }
      return false;
    }

    function hasPhantomTypeArgumentMismatch(
      node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
      uncastType: ts.Type,
      contextualType: ts.Type,
    ): boolean {
      return (
        isInGenericContext(node) &&
        (hasPhantomTypeArguments(uncastType) ||
          hasPhantomTypeArguments(contextualType)) &&
        !haveSameTypeArguments(uncastType, contextualType)
      );
    }

    const SKIP_PARENT_TYPES = new Set([
      AST_NODE_TYPES.TSAsExpression,
      AST_NODE_TYPES.TSTypeAssertion,
      AST_NODE_TYPES.SpreadElement,
      AST_NODE_TYPES.TSSatisfiesExpression,
    ]);

    function shouldSkipContextualTypeFallback(
      node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
      castIsAny: boolean,
    ): boolean {
      if (castIsAny) {
        return (
          node.parent.type === AST_NODE_TYPES.LogicalExpression ||
          isInGenericContext(node)
        );
      }

      /**
       * Interpolated template literals can be widened to `string` while contextual
       * typing still accepts them, so the assertion may be required.
       * @see https://github.com/typescript-eslint/typescript-eslint/issues/12276
       */
      if (isTemplateLiteralWithExpressions(node.expression)) {
        return true;
      }

      if (
        SKIP_PARENT_TYPES.has(node.parent.type) ||
        node.expression.type === AST_NODE_TYPES.ArrayExpression ||
        isInDestructuringDeclaration(node) ||
        isPropertyInProblematicContext(node) ||
        isAssignmentInNonStatementContext(node) ||
        isRightHandSideOfLogicalAssignment(node) ||
        isArgumentToOverloadedFunction(node)
      ) {
        return true;
      }

      if (isInGenericContext(node)) {
        const originalExpr = getOriginalExpression(node);
        return (
          !isConceptuallyLiteral(originalExpr) &&
          node.parent.type !== AST_NODE_TYPES.Property
        );
      }

      return false;
    }

    function getUncastType(
      node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
    ): ts.Type {
      // Special handling for IIFE: extract the function's return type
      if (isIIFE(node.expression)) {
        const callee = node.expression.callee;
        const functionType = services.getTypeAtLocation(callee);
        const signatures = functionType.getCallSignatures();

        if (signatures.length > 0) {
          const returnType = checker.getReturnTypeOfSignature(signatures[0]);

          // If the function has no explicit return type annotation and returns undefined,
          // treat it as void (TypeScript infers () => {} as () => undefined, but it should be void)
          if (
            callee.returnType == null &&
            isTypeFlagSet(returnType, ts.TypeFlags.Undefined)
          ) {
            return checker.getVoidType();
          }

          return returnType;
        }
      }

      return services.getTypeAtLocation(node.expression);
    }

    function createAssertionFixer(
      node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
    ): ReportFixFunction {
      return fixer => {
        if (node.type === AST_NODE_TYPES.TSTypeAssertion) {
          const openingAngleBracket = nullThrows(
            context.sourceCode.getTokenBefore(
              node.typeAnnotation,
              token =>
                token.type === AST_TOKEN_TYPES.Punctuator &&
                token.value === '<',
            ),
            NullThrowsReasons.MissingToken('<', 'type annotation'),
          );
          const closingAngleBracket = nullThrows(
            context.sourceCode.getTokenAfter(
              node.typeAnnotation,
              token =>
                token.type === AST_TOKEN_TYPES.Punctuator &&
                token.value === '>',
            ),
            NullThrowsReasons.MissingToken('>', 'type annotation'),
          );
          // Removing the angle brackets leaves the asserted operand at the
          // assertion's position, so its first token leads whatever the
          // assertion led. A leading `{`/`function`/`class` at the start of an
          // expression statement is parsed as a block / function or class
          // declaration, and a leading `{` at the start of a concise arrow body
          // is parsed as a block body. In those positions the operand must be
          // wrapped in parentheses to stay an expression.
          const firstOperandToken = nullThrows(
            context.sourceCode.getTokenAfter(closingAngleBracket),
            NullThrowsReasons.MissingToken('operand', 'type assertion'),
          );
          const breaksExpressionStatement =
            ['{', 'function', 'class'].includes(firstOperandToken.value) &&
            isStartOfExpressionStatement(node);
          const breaksArrowFunctionBody =
            firstOperandToken.value === '{' &&
            isStartOfArrowFunctionBody(node, context.sourceCode);
          const needsParens =
            breaksExpressionStatement || breaksArrowFunctionBody;

          const fixes: RuleFix[] = [];
          if (needsParens) {
            fixes.push(fixer.insertTextBefore(node, '('));
          }
          fixes.push(
            fixer.removeRange([
              openingAngleBracket.range[0],
              closingAngleBracket.range[1],
            ]),
          );
          if (needsParens) {
            fixes.push(fixer.insertTextAfter(node, ')'));
          }
          return fixes;
        }
        const asToken = nullThrows(
          context.sourceCode.getTokenAfter(
            node.expression,
            token =>
              token.type === AST_TOKEN_TYPES.Identifier && token.value === 'as',
          ),
          NullThrowsReasons.MissingToken('>', 'type annotation'),
        );
        const tokenBeforeAs = nullThrows(
          context.sourceCode.getTokenBefore(asToken, {
            includeComments: true,
          }),
          NullThrowsReasons.MissingToken('comment', 'as'),
        );
        return fixer.removeRange([tokenBeforeAs.range[1], node.range[1]]);
      };
    }

    function reportDoubleAssertionIfUnnecessary(
      node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
      contextualType: ts.Type | undefined,
    ): void {
      const doubleAssertionResult = isDoubleAssertionUnnecessary(
        node,
        contextualType,
      );
      if (doubleAssertionResult) {
        context.report({
          node,
          messageId: doubleAssertionResult,
          fix(fixer) {
            const originalExpr = getOriginalExpression(node);
            let text = context.sourceCode.getText(originalExpr);
            if (
              originalExpr.type === AST_NODE_TYPES.ObjectExpression &&
              node.parent.type === AST_NODE_TYPES.ArrowFunctionExpression &&
              node.parent.body === node
            ) {
              text = `(${text})`;
            }
            return fixer.replaceText(node, text);
          },
        });
      }
    }

    return {
      'TSAsExpression, TSTypeAssertion'(
        node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
      ): void {
        if (
          options.typesToIgnore?.includes(
            context.sourceCode.getText(node.typeAnnotation),
          )
        ) {
          return;
        }

        const castType = services.getTypeAtLocation(node);
        const castTypeIsLiteral = isTypeLiteral(castType);
        const typeAnnotationIsConstAssertion = isConstAssertion(
          node.typeAnnotation,
        );

        if (
          !options.checkLiteralConstAssertions &&
          castTypeIsLiteral &&
          typeAnnotationIsConstAssertion
        ) {
          return;
        }

        const uncastType = getUncastType(node);
        const typeIsUnchanged = isTypeUnchanged(
          node,
          node.expression,
          uncastType,
          castType,
        );
        const wouldSameTypeBeInferred = castTypeIsLiteral
          ? isImplicitlyNarrowedLiteralDeclaration(node)
          : !typeAnnotationIsConstAssertion;

        if (typeIsUnchanged && wouldSameTypeBeInferred) {
          context.report({
            node,
            messageId: 'unnecessaryAssertion',
            fix: createAssertionFixer(node),
          });
          return;
        }

        const originalNode = services.esTreeNodeToTSNodeMap.get(node);

        const castIsAny =
          isTypeFlagSet(castType, ts.TypeFlags.Any) &&
          !SKIP_PARENT_TYPES.has(node.parent.type);

        const contextualType = shouldSkipContextualTypeFallback(node, castIsAny)
          ? undefined
          : checker.getContextualType(originalNode);

        if (contextualType) {
          const contextualTypeIsAny = isTypeFlagSet(
            contextualType,
            ts.TypeFlags.Any,
          );

          const isCallArgument =
            (node.parent.type === AST_NODE_TYPES.CallExpression ||
              node.parent.type === AST_NODE_TYPES.NewExpression) &&
            node.parent.arguments.includes(node);

          const anyInvolvedInContextualCheck = contextualTypeIsAny
            ? isCallArgument && !containsAny(castType)
            : !containsAny(contextualType);

          const isNullishLiteralToUnion =
            castType.isUnion() &&
            ((node.expression.type === AST_NODE_TYPES.Literal &&
              node.expression.value == null) ||
              (node.expression.type === AST_NODE_TYPES.Identifier &&
                node.expression.name === 'undefined'));

          const isContextuallyUnnecessary =
            !typeAnnotationIsConstAssertion &&
            !containsAny(uncastType) &&
            anyInvolvedInContextualCheck &&
            !hasPhantomTypeArgumentMismatch(node, uncastType, contextualType) &&
            (castIsAny || !genericsMismatch(uncastType, contextualType)) &&
            (contextualTypeIsAny ||
              checker.isTypeAssignableTo(uncastType, contextualType)) &&
            !isNullishLiteralToUnion;

          if (isContextuallyUnnecessary) {
            context.report({
              node,
              messageId: 'contextuallyUnnecessary',
              fix: createAssertionFixer(node),
            });
            return;
          }
        }

        reportDoubleAssertionIfUnnecessary(node, contextualType);
      },
      TSNonNullExpression(node): void {
        const removeExclamationFix: ReportFixFunction = fixer => {
          const exclamationToken = nullThrows(
            context.sourceCode.getLastToken(node, token => token.value === '!'),
            NullThrowsReasons.MissingToken(
              'exclamation mark',
              'non-null assertion',
            ),
          );

          return fixer.removeRange(exclamationToken.range);
        };

        if (
          node.parent.type === AST_NODE_TYPES.AssignmentExpression &&
          node.parent.operator === '='
        ) {
          if (node.parent.left === node) {
            context.report({
              node,
              messageId: 'contextuallyUnnecessary',
              fix: removeExclamationFix,
            });
          }
          // for all other = assignments we ignore non-null checks
          // this is because non-null assertions can change the type-flow of the code
          // so whilst they might be unnecessary for the assignment - they are necessary
          // for following code
          return;
        }

        const originalNode = services.esTreeNodeToTSNodeMap.get(node);

        const constrainedType = getConstrainedTypeAtLocation(
          services,
          node.expression,
        );
        const actualType = services.getTypeAtLocation(node.expression);

        // Check both the constrained type and the actual type.
        // If either is nullable, we should not report the assertion as unnecessary.
        // This handles cases like generic constraints with `any` where the
        // constrained type is `any` (nullable) but the actual type might be
        // a type parameter that TypeScript treats nominally.
        // See: https://github.com/typescript-eslint/typescript-eslint/issues/11559
        const constrainedTypeIsNullable = isNullableType(constrainedType);
        const actualTypeIsNullable = isNullableType(actualType);

        if (!constrainedTypeIsNullable && !actualTypeIsNullable) {
          if (
            node.expression.type === AST_NODE_TYPES.Identifier &&
            isPossiblyUsedBeforeAssigned(node.expression)
          ) {
            return;
          }

          context.report({
            node,
            messageId: 'unnecessaryAssertion',
            fix: removeExclamationFix,
          });
        } else {
          // we know it's a nullable type
          // so figure out if the variable is used in a place that accepts nullable types

          // If the constrained type differs from the actual type (e.g., when dealing
          // with unresolved generic type parameters), we should not report the assertion
          // as contextually unnecessary. TypeScript may still require the assertion
          // even if the constraint is nullable (like `any`).
          // See: https://github.com/typescript-eslint/typescript-eslint/issues/11559
          if (constrainedType !== actualType) {
            return;
          }

          const contextualType = getContextualType(checker, originalNode);
          if (contextualType) {
            if (
              isTypeFlagSet(constrainedType, ts.TypeFlags.Unknown) &&
              !isTypeFlagSet(contextualType, ts.TypeFlags.Unknown)
            ) {
              return;
            }

            // in strict mode you can't assign null to undefined, so we have to make sure that
            // the two types share a nullable type
            const typeIncludesUndefined = isTypeFlagSet(
              constrainedType,
              ts.TypeFlags.Undefined,
            );
            const typeIncludesNull = isTypeFlagSet(
              constrainedType,
              ts.TypeFlags.Null,
            );
            const typeIncludesVoid = isTypeFlagSet(
              constrainedType,
              ts.TypeFlags.Void,
            );

            const contextualTypeIncludesUndefined = isTypeFlagSet(
              contextualType,
              ts.TypeFlags.Undefined,
            );
            const contextualTypeIncludesNull = isTypeFlagSet(
              contextualType,
              ts.TypeFlags.Null,
            );
            const contextualTypeIncludesVoid = isTypeFlagSet(
              contextualType,
              ts.TypeFlags.Void,
            );

            // make sure that the parent accepts the same types
            // i.e. assigning `string | null | undefined` to `string | undefined` is invalid
            const isValidUndefined = typeIncludesUndefined
              ? contextualTypeIncludesUndefined
              : true;
            const isValidNull = typeIncludesNull
              ? contextualTypeIncludesNull
              : true;
            const isValidVoid = typeIncludesVoid
              ? contextualTypeIncludesVoid
              : true;

            if (isValidUndefined && isValidNull && isValidVoid) {
              context.report({
                node,
                messageId: 'contextuallyUnnecessary',
                fix: removeExclamationFix,
              });
            }
          }
        }
      },
    };
  }

Internal helpers

Declared inside another function in this file.

isPossiblyUsedBeforeAssigned(node: TSESTree.Expression): boolean

Returns true if there's a chance the variable has been used before a value has been assigned to it

Raw JSDoc
/**
     * Returns true if there's a chance the variable has been used before a value has been assigned to it
     */

Calls:

  • getDeclaration (from ../util)
  • tsutils.isStrictCompilerOptionEnabled
  • ts.isVariableDeclaration
  • ts.isVariableDeclarationList
  • services.tsNodeToESTreeNodeMap.has
  • services.tsNodeToESTreeNodeMap.get
  • context.sourceCode.getScope
  • checker.getTypeFromTypeNode
  • getConstrainedTypeAtLocation (from ../util)
  • ts.isVariableStatement
  • tsutils.includesModifier
  • getModifiers (from ../util)

Internal Comments:

// don't know what the declaration is for some reason, so just assume the worst
// non-strict mode doesn't care about used before assigned errors (x4)
// ignore class properties as they are compile time guarded (x3)
// also ignore function arguments as they can't be used before defined (x3)
// For var declarations, we need to check whether the node
// is actually in a descendant of its declaration or not. If not,
// it may be used before defined.
// eg
// if (Math.random() < 0.5) {
//     var x: number  = 2;
// } else {
//     x!.toFixed();
// }
// var (x4)
// If they are not in the same file it will not exist. (x4)
// This situation must not occur using before defined. (x4)
// is it `const x!: number` (x5)
// check if the defined variable type has changed since assignment (x2)
// `declare`s are never narrowed, so never skip them
// possibly used before assigned, so just skip it
// better to false negative and skip it, than false positive and fix to compile erroring code
//
// no better way to figure this out right now
// https://github.com/Microsoft/TypeScript/issues/31124

Code
function isPossiblyUsedBeforeAssigned(node: TSESTree.Expression): boolean {
      const declaration = getDeclaration(services, node);
      if (!declaration) {
        // don't know what the declaration is for some reason, so just assume the worst
        return true;
      }

      if (
        // non-strict mode doesn't care about used before assigned errors
        tsutils.isStrictCompilerOptionEnabled(
          compilerOptions,
          'strictNullChecks',
        ) &&
        // ignore class properties as they are compile time guarded
        // also ignore function arguments as they can't be used before defined
        ts.isVariableDeclaration(declaration)
      ) {
        // For var declarations, we need to check whether the node
        // is actually in a descendant of its declaration or not. If not,
        // it may be used before defined.

        // eg
        // if (Math.random() < 0.5) {
        //     var x: number  = 2;
        // } else {
        //     x!.toFixed();
        // }
        if (
          ts.isVariableDeclarationList(declaration.parent) &&
          // var
          declaration.parent.flags === ts.NodeFlags.None &&
          // If they are not in the same file it will not exist.
          // This situation must not occur using before defined.
          services.tsNodeToESTreeNodeMap.has(declaration)
        ) {
          const declaratorNode: TSESTree.VariableDeclaration =
            services.tsNodeToESTreeNodeMap.get(declaration);
          const scope = context.sourceCode.getScope(node);
          const declaratorScope = context.sourceCode.getScope(declaratorNode);
          let parentScope: Scope | null = declaratorScope;
          while ((parentScope = parentScope.upper)) {
            if (parentScope === scope) {
              return true;
            }
          }
        }

        if (
          // is it `const x!: number`
          declaration.initializer == null &&
          declaration.exclamationToken == null &&
          declaration.type != null
        ) {
          // check if the defined variable type has changed since assignment
          const declarationType = checker.getTypeFromTypeNode(declaration.type);
          const type = getConstrainedTypeAtLocation(services, node);
          if (
            declarationType === type &&
            // `declare`s are never narrowed, so never skip them
            !(
              ts.isVariableDeclarationList(declaration.parent) &&
              ts.isVariableStatement(declaration.parent.parent) &&
              tsutils.includesModifier(
                getModifiers(declaration.parent.parent),
                ts.SyntaxKind.DeclareKeyword,
              )
            )
          ) {
            // possibly used before assigned, so just skip it
            // better to false negative and skip it, than false positive and fix to compile erroring code
            //
            // no better way to figure this out right now
            // https://github.com/Microsoft/TypeScript/issues/31124
            return true;
          }
        }
      }
      return false;
    }

isConstAssertion(node: TSESTree.TypeNode): boolean

Parameters:

  • node TSESTree.TypeNode

Returns: boolean

Code
function isConstAssertion(node: TSESTree.TypeNode): boolean {
      return (
        node.type === AST_NODE_TYPES.TSTypeReference &&
        node.typeName.type === AST_NODE_TYPES.Identifier &&
        node.typeName.name === 'const'
      );
    }

isTemplateLiteralWithExpressions(expression: TSESTree.Expression): boolean

Parameters:

  • expression TSESTree.Expression

Returns: boolean

Code
function isTemplateLiteralWithExpressions(expression: TSESTree.Expression) {
      return (
        expression.type === AST_NODE_TYPES.TemplateLiteral &&
        expression.expressions.length !== 0
      );
    }

isImplicitlyNarrowedLiteralDeclaration({ expression, parent, }: TSESTree.TSAsExpression | TSESTree.TSTy…): boolean

Parameters:

  • { expression, parent, } TSESTree.TSAsExpression | TSESTree.TSTypeAssertion

Returns: boolean

Calls:

  • isTemplateLiteralWithExpressions

Internal Comments:

/**
       * Even on `const` variable declarations, template literals with expressions can sometimes be widened without a type assertion.
       * @see https://github.com/typescript-eslint/typescript-eslint/issues/8737
       */
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion (x2)

Code
function isImplicitlyNarrowedLiteralDeclaration({
      expression,
      parent,
    }: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion): boolean {
      /**
       * Even on `const` variable declarations, template literals with expressions can sometimes be widened without a type assertion.
       * @see https://github.com/typescript-eslint/typescript-eslint/issues/8737
       */
      if (isTemplateLiteralWithExpressions(expression)) {
        return false;
      }

      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
      const maybeDeclarationNode = parent.parent!;

      return (
        (maybeDeclarationNode.type === AST_NODE_TYPES.VariableDeclaration &&
          maybeDeclarationNode.kind === 'const') ||
        (parent.type === AST_NODE_TYPES.PropertyDefinition && parent.readonly)
      );
    }

isTypeUnchanged(node: TSESTree.TSAsExpression | TSESTree.TSTy…, expression: TSESTree.Expression, uncast: ts.Type, cast: ts.Type): boolean

Parameters:

  • node TSESTree.TSAsExpression | TSESTree.TSTypeAssertion
  • expression TSESTree.Expression
  • uncast ts.Type
  • cast ts.Type

Returns: boolean

Calls:

  • containsTypeVariable
  • isTypeFlagSet (from ../util)
  • tsutils.isCompilerOptionEnabled
  • areUnionPartsEquivalentIgnoringUndefined
  • hasIndexSignature
  • containsAny
  • isConceptuallyLiteral
  • cast .getProperties() .some
  • isTypeLiteral
  • checker.getTypeOfSymbol
  • cast.isIntersection
  • uncast.isIntersection
  • castParts.find
  • tsutils.isTypeParameter
  • castParts.some
  • isEmptyObjectType
  • checker.getBaseConstraintOfType
  • isNullableType (from ../util)
  • hasSameProperties
  • haveSameTypeArguments
  • areMutuallyAssignable
Code
function isTypeUnchanged(
      node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
      expression: TSESTree.Expression,
      uncast: ts.Type,
      cast: ts.Type,
    ): boolean {
      if (uncast === cast) {
        return true;
      }

      if (
        node.typeAnnotation.type === AST_NODE_TYPES.TSIntersectionType &&
        containsTypeVariable(cast)
      ) {
        return false;
      }

      if (
        isTypeFlagSet(uncast, ts.TypeFlags.Undefined) &&
        isTypeFlagSet(cast, ts.TypeFlags.Undefined) &&
        tsutils.isCompilerOptionEnabled(
          compilerOptions,
          'exactOptionalPropertyTypes',
        )
      ) {
        return areUnionPartsEquivalentIgnoringUndefined(uncast, cast);
      }

      if (
        (isTypeFlagSet(uncast, ts.TypeFlags.NonPrimitive) &&
          !isTypeFlagSet(cast, ts.TypeFlags.NonPrimitive)) ||
        (hasIndexSignature(uncast) && !hasIndexSignature(cast)) ||
        containsAny(uncast) ||
        containsAny(cast) ||
        (containsTypeVariable(cast) && !containsTypeVariable(uncast))
      ) {
        return false;
      }

      if (
        isConceptuallyLiteral(expression) &&
        (expression.type !== AST_NODE_TYPES.ObjectExpression ||
          expression.properties.length === 0 ||
          cast
            .getProperties()
            .some(p => isTypeLiteral(checker.getTypeOfSymbol(p))))
      ) {
        return false;
      }

      if (cast.isIntersection() && !uncast.isIntersection()) {
        const castParts = cast.types;
        const otherPart = castParts.find(part => part !== uncast);
        if (
          tsutils.isTypeParameter(uncast) &&
          castParts.length === 2 &&
          castParts.some(part => part === uncast) &&
          otherPart != null &&
          isEmptyObjectType(otherPart) &&
          !containsTypeVariable(otherPart)
        ) {
          const constraint = checker.getBaseConstraintOfType(uncast);
          if (constraint && !isNullableType(constraint)) {
            return true;
          }
        }
        return false;
      }

      if (
        !hasSameProperties(uncast, cast) ||
        !haveSameTypeArguments(uncast, cast)
      ) {
        return false;
      }

      return areMutuallyAssignable(uncast, cast);
    }

isTypeLiteral(type: ts.Type): boolean

Parameters:

  • type ts.Type

Returns: boolean

Calls:

  • type.isLiteral
  • tsutils.isBooleanLiteralType
Code
function isTypeLiteral(type: ts.Type): boolean {
      return type.isLiteral() || tsutils.isBooleanLiteralType(type);
    }

hasIndexSignature(type: ts.Type): boolean

Parameters:

  • type ts.Type

Returns: boolean

Calls:

  • tsutils .unionConstituents(type) .some
  • checker.getIndexInfosOfType
Code
function hasIndexSignature(type: ts.Type): boolean {
      return tsutils
        .unionConstituents(type)
        .some(part => checker.getIndexInfosOfType(part).length > 0);
    }

getTypeArguments(type: ts.Type): readonly ts.Type[]

Parameters:

  • type ts.Type

Returns: readonly ts.Type[]

Calls:

  • tsutils.isTypeReference
  • checker.getTypeArguments
Code
function getTypeArguments(type: ts.Type): readonly ts.Type[] {
      return (
        type.aliasTypeArguments ??
        (tsutils.isTypeReference(type) ? checker.getTypeArguments(type) : [])
      );
    }

typeContains(type: ts.Type, predicate: (type: ts.Type) => boolean, seen: Set<ts.Type>): boolean

Parameters:

  • type ts.Type
  • predicate (type: ts.Type) => boolean
  • seen Set<ts.Type>

Returns: boolean

Calls:

  • seen.has
  • seen.add
  • predicate
  • type.isUnionOrIntersection
  • type.types.some
  • typeContains
  • getTypeArguments
  • type .getCallSignatures() .flatMap
  • sig.getReturnType
  • sig.getParameters().map
  • checker.getTypeOfSymbol
  • nestedTypes.some
Code
function typeContains(
      type: ts.Type,
      predicate: (type: ts.Type) => boolean,
      seen = new Set<ts.Type>(),
    ): boolean {
      if (seen.has(type)) {
        return false;
      }
      seen.add(type);
      if (predicate(type)) {
        return true;
      }
      if (type.isUnionOrIntersection()) {
        return type.types.some(t => typeContains(t, predicate, seen));
      }
      const nestedTypes = [
        ...getTypeArguments(type),
        ...type
          .getCallSignatures()
          .flatMap(sig => [
            sig.getReturnType(),
            ...sig.getParameters().map(p => checker.getTypeOfSymbol(p)),
          ]),
      ];
      return nestedTypes.some(t => typeContains(t, predicate, seen));
    }

containsAny(type: ts.Type): boolean

Parameters:

  • type ts.Type

Returns: boolean

Calls:

  • typeContains
  • isTypeFlagSet (from ../util)

Internal Comments:

// Workaround for https://github.com/typescript-eslint/typescript-eslint/issues/12705

Code
function containsAny(type: ts.Type): boolean {
      try {
        return typeContains(type, t => isTypeFlagSet(t, ts.TypeFlags.Any));
      } catch (error) {
        // Workaround for https://github.com/typescript-eslint/typescript-eslint/issues/12705
        if (
          error instanceof RangeError &&
          error.message === 'Maximum call stack size exceeded'
        ) {
          return false;
        }
        throw error;
      }
    }

containsTypeVariable(type: ts.Type): boolean

Parameters:

  • type ts.Type

Returns: boolean

Calls:

  • typeContains
  • isTypeFlagSet (from ../util)
Code
function containsTypeVariable(type: ts.Type): boolean {
      return typeContains(type, t =>
        isTypeFlagSet(t, ts.TypeFlags.TypeVariable | ts.TypeFlags.Index),
      );
    }

hasPhantomTypeArguments(type: ts.Type): boolean

Parameters:

  • type ts.Type

Returns: boolean

Calls:

  • isEmptyObjectType
  • getTypeArguments
Code
function hasPhantomTypeArguments(type: ts.Type): boolean {
      return isEmptyObjectType(type) && getTypeArguments(type).length > 0;
    }

hasTypeParams(sig: ts.Signature): boolean

Parameters:

  • sig ts.Signature

Returns: boolean

Calls:

  • sig.getTypeParameters
Code
function hasTypeParams(sig: ts.Signature): boolean {
      return (sig.getTypeParameters()?.length ?? 0) > 0;
    }

genericsMismatch(uncast: ts.Type, contextual: ts.Type): boolean

Parameters:

  • uncast ts.Type
  • contextual ts.Type

Returns: boolean

Calls:

  • contextual.getProperties().some
  • checker.getSignaturesOfType
  • checker.getTypeOfSymbol
  • contextualSigs.some
  • uncast.getProperty
  • prop.getEscapedName
  • checker .getSignaturesOfType( checker.getTypeOfSymbol(uncastProp), ts.SignatureKind.Call, ) .some
Code
function genericsMismatch(uncast: ts.Type, contextual: ts.Type): boolean {
      return contextual.getProperties().some(prop => {
        const contextualSigs = checker.getSignaturesOfType(
          checker.getTypeOfSymbol(prop),
          ts.SignatureKind.Call,
        );
        if (!contextualSigs.some(hasTypeParams)) {
          return false;
        }
        const uncastProp = uncast.getProperty(prop.getEscapedName() as string);
        if (!uncastProp) {
          return true;
        }
        return !checker
          .getSignaturesOfType(
            checker.getTypeOfSymbol(uncastProp),
            ts.SignatureKind.Call,
          )
          .some(hasTypeParams);
      });
    }

hasSameProperties(uncast: ts.Type, cast: ts.Type): boolean

Parameters:

  • uncast ts.Type
  • cast ts.Type

Returns: boolean

Calls:

  • uncast.getProperties
  • cast.getProperties
  • castProps.map
  • p.getEscapedName
  • uncastProps.every
  • prop.getEscapedName
  • castPropNames.has
  • tsutils.isPropertyReadonlyInType
Code
function hasSameProperties(uncast: ts.Type, cast: ts.Type): boolean {
      const uncastProps = uncast.getProperties();
      const castProps = cast.getProperties();
      if (uncastProps.length !== castProps.length) {
        return false;
      }
      const castPropNames = new Set(castProps.map(p => p.getEscapedName()));
      return uncastProps.every(prop => {
        const name = prop.getEscapedName();
        return (
          castPropNames.has(name) &&
          tsutils.isPropertyReadonlyInType(uncast, name, checker) ===
            tsutils.isPropertyReadonlyInType(cast, name, checker)
        );
      });
    }

haveSameTypeArguments(uncast: ts.Type, cast: ts.Type): boolean

Parameters:

  • uncast ts.Type
  • cast ts.Type

Returns: boolean

Calls:

  • getTypeArguments
  • uncastArgs.every
Code
function haveSameTypeArguments(uncast: ts.Type, cast: ts.Type): boolean {
      const uncastArgs = getTypeArguments(uncast);
      const castArgs = getTypeArguments(cast);
      return (
        uncastArgs.length === castArgs.length &&
        uncastArgs.every((arg, i) => arg === castArgs[i])
      );
    }

areMutuallyAssignable(a: ts.Type, b: ts.Type): boolean

Parameters:

  • a ts.Type
  • b ts.Type

Returns: boolean

Calls:

  • checker.isTypeAssignableTo
Code
function areMutuallyAssignable(a: ts.Type, b: ts.Type): boolean {
      return (
        checker.isTypeAssignableTo(a, b) && checker.isTypeAssignableTo(b, a)
      );
    }

areUnionPartsEquivalentIgnoringUndefined(uncast: ts.Type, cast: ts.Type): boolean

Parameters:

  • uncast ts.Type
  • cast ts.Type

Returns: boolean

Calls:

  • isTypeFlagSet (from ../util)
  • tsutils .unionConstituents(uncast) .filter
  • tsutils.unionConstituents(cast).filter
  • castParts.every
  • uncastPartsSet.has
Code
function areUnionPartsEquivalentIgnoringUndefined(
      uncast: ts.Type,
      cast: ts.Type,
    ): boolean {
      const filterUndefined = (part: ts.Type): boolean =>
        !isTypeFlagSet(part, ts.TypeFlags.Undefined);
      const uncastParts = tsutils
        .unionConstituents(uncast)
        .filter(filterUndefined);
      const castParts = tsutils.unionConstituents(cast).filter(filterUndefined);
      if (uncastParts.length !== castParts.length) {
        return false;
      }
      const uncastPartsSet = new Set(uncastParts);
      return castParts.every(part => uncastPartsSet.has(part));
    }

filterUndefined(part: ts.Type): boolean

Parameters:

  • part ts.Type

Returns: boolean

Code
(part: ts.Type): boolean =>
        !isTypeFlagSet(part, ts.TypeFlags.Undefined)

getOriginalExpression(node: TSESTree.TSAsExpression | TSESTree.TSTy…): TSESTree.Expression

Parameters:

  • node TSESTree.TSAsExpression | TSESTree.TSTypeAssertion

Returns: TSESTree.Expression

Code
function getOriginalExpression(
      node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
    ): TSESTree.Expression {
      let current = node.expression;
      while (
        current.type === AST_NODE_TYPES.TSAsExpression ||
        current.type === AST_NODE_TYPES.TSTypeAssertion
      ) {
        current = current.expression;
      }
      return current;
    }

isDoubleAssertionUnnecessary(node: TSESTree.TSAsExpression | TSESTree.TSTy…, contextualType: ts.Type | undefined): false | MessageIds

Parameters:

  • node TSESTree.TSAsExpression | TSESTree.TSTypeAssertion
  • contextualType ts.Type | undefined

Returns: false | MessageIds

Calls:

  • getOriginalExpression
  • services.getTypeAtLocation
  • isTypeUnchanged
  • isTypeFlagSet (from ../util)
  • checker.isTypeAssignableTo
Code
function isDoubleAssertionUnnecessary(
      node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
      contextualType: ts.Type | undefined,
    ): false | MessageIds {
      const innerExpression = node.expression;
      if (
        innerExpression.type !== AST_NODE_TYPES.TSAsExpression &&
        innerExpression.type !== AST_NODE_TYPES.TSTypeAssertion
      ) {
        return false;
      }

      const originalExpr = getOriginalExpression(node);
      const originalType = services.getTypeAtLocation(originalExpr);
      const castType = services.getTypeAtLocation(node);

      if (
        isTypeUnchanged(node, innerExpression, originalType, castType) &&
        !isTypeFlagSet(castType, ts.TypeFlags.Any)
      ) {
        return 'unnecessaryAssertion';
      }

      if (contextualType) {
        const intermediateType = services.getTypeAtLocation(innerExpression);
        if (
          (isTypeFlagSet(intermediateType, ts.TypeFlags.Any) ||
            isTypeFlagSet(intermediateType, ts.TypeFlags.Unknown)) &&
          checker.isTypeAssignableTo(originalType, contextualType)
        ) {
          return 'contextuallyUnnecessary';
        }
      }

      return false;
    }

isConceptuallyLiteral(node: TSESTree.Node): boolean

Parameters:

  • node TSESTree.Node

Returns: boolean

Calls:

  • CONCEPTUALLY_LITERAL_TYPES.has
Code
function isConceptuallyLiteral(node: TSESTree.Node): boolean {
      return CONCEPTUALLY_LITERAL_TYPES.has(node.type);
    }

isIIFE(expression: TSESTree.Expression): expression is TSESTree.CallExpression & { callee: TSESTree.…

Parameters:

  • expression TSESTree.Expression

Returns: expression is TSESTree.CallExpression & { callee: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression; }

Code
function isIIFE(
      expression: TSESTree.Expression,
    ): expression is TSESTree.CallExpression & {
      callee: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression;
    } {
      return (
        expression.type === AST_NODE_TYPES.CallExpression &&
        (expression.callee.type === AST_NODE_TYPES.ArrowFunctionExpression ||
          expression.callee.type === AST_NODE_TYPES.FunctionExpression)
      );
    }

isEmptyObjectType(type: ts.Type): boolean

Parameters:

  • type ts.Type

Returns: boolean

Calls:

  • isTypeFlagSet (from ../util)
  • type.getProperties
  • type.getCallSignatures
  • type.getConstructSignatures
  • type.getStringIndexType
  • type.getNumberIndexType
Code
function isEmptyObjectType(type: ts.Type): boolean {
      return (
        isTypeFlagSet(type, ts.TypeFlags.NonPrimitive) ||
        (type.getProperties().length === 0 &&
          !type.getCallSignatures().length &&
          !type.getConstructSignatures().length &&
          !type.getStringIndexType() &&
          !type.getNumberIndexType())
      );
    }

hasGenericCallSignature(type: ts.Type): boolean

Parameters:

  • type ts.Type

Returns: boolean

Calls:

  • type.getCallSignatures().some
Code
function hasGenericCallSignature(type: ts.Type): boolean {
      return type.getCallSignatures().some(hasTypeParams);
    }

isArgumentToOverloadedFunction(node: TSESTree.TSAsExpression | TSESTree.TSTy…): boolean

Parameters:

  • node TSESTree.TSAsExpression | TSESTree.TSTypeAssertion

Returns: boolean

Calls:

  • parent.arguments.includes
  • services .getTypeAtLocation(parent.callee) .getNonNullableType
  • calleeType.getCallSignatures
  • parent.arguments.indexOf
  • signatures.map
  • sig.getParameters
  • checker.getTypeOfSymbol
  • ts.isParameter
  • getTypeArguments
  • paramTypes.some
  • definedParamTypes.every
  • services.getTypeAtLocation
  • checker.isTypeAssignableTo

Internal Comments:

// An optional-chained callee (`foo?.bar(...)`) types as `<method> | undefined`, (x2)
// and a union exposes no call signatures β€” strip the nullability first. (x2)

Code
function isArgumentToOverloadedFunction(
      node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
    ): boolean {
      const { parent } = node;
      if (
        (parent.type !== AST_NODE_TYPES.CallExpression &&
          parent.type !== AST_NODE_TYPES.NewExpression) ||
        !parent.arguments.includes(node)
      ) {
        return false;
      }
      // An optional-chained callee (`foo?.bar(...)`) types as `<method> | undefined`,
      // and a union exposes no call signatures β€” strip the nullability first.
      const calleeType = services
        .getTypeAtLocation(parent.callee)
        .getNonNullableType();
      const signatures = calleeType.getCallSignatures();
      if (signatures.length <= 1) {
        return false;
      }

      const argIndex = parent.arguments.indexOf(node);
      const paramTypes = signatures.map(sig => {
        const params = sig.getParameters();
        if (argIndex >= params.length) {
          return undefined;
        }
        const param = params[argIndex];
        let paramType = checker.getTypeOfSymbol(param);
        if (
          param.valueDeclaration &&
          ts.isParameter(param.valueDeclaration) &&
          param.valueDeclaration.dotDotDotToken
        ) {
          const typeArgs = getTypeArguments(paramType);
          if (typeArgs.length > 0) {
            paramType = typeArgs[0];
          }
        }
        return paramType;
      });

      if (paramTypes.some(type => type == null)) {
        return true;
      }

      const definedParamTypes = paramTypes as ts.Type[];
      const firstParamType = definedParamTypes[0];
      if (definedParamTypes.every(type => type === firstParamType)) {
        return false;
      }

      const uncastType = services.getTypeAtLocation(node.expression);
      return !definedParamTypes.every(type =>
        checker.isTypeAssignableTo(uncastType, type),
      );
    }

isInDestructuringDeclaration(node: TSESTree.TSAsExpression | TSESTree.TSTy…): boolean

Parameters:

  • node TSESTree.TSAsExpression | TSESTree.TSTypeAssertion

Returns: boolean

Code
function isInDestructuringDeclaration(
      node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
    ): boolean {
      const { parent } = node;
      return (
        parent.type === AST_NODE_TYPES.VariableDeclarator &&
        parent.init === node &&
        (parent.id.type === AST_NODE_TYPES.ObjectPattern ||
          parent.id.type === AST_NODE_TYPES.ArrayPattern)
      );
    }

isPropertyInProblematicContext(node: TSESTree.TSAsExpression | TSESTree.TSTy…): boolean

Parameters:

  • node TSESTree.TSAsExpression | TSESTree.TSTypeAssertion

Returns: boolean

Calls:

  • services.esTreeNodeToTSNodeMap.get
  • checker.getContextualType(objectTsNode)?.isUnion
  • checker.getContextualType
  • checker.getNonNullableType
  • nonNullableContextualType.isUnion
  • services.getTypeAtLocation
  • checker.isTypeAssignableTo
Code
function isPropertyInProblematicContext(
      node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
    ): boolean {
      const { parent } = node;
      if (parent.type !== AST_NODE_TYPES.Property || parent.value !== node) {
        return false;
      }
      const objectExpr = parent.parent;
      if (objectExpr.type !== AST_NODE_TYPES.ObjectExpression) {
        return false;
      }
      const objectTsNode = services.esTreeNodeToTSNodeMap.get(objectExpr);
      if (checker.getContextualType(objectTsNode)?.isUnion()) {
        const nodeTsNode = services.esTreeNodeToTSNodeMap.get(node);
        const propContextualType = checker.getContextualType(nodeTsNode);
        if (propContextualType == null) {
          return true;
        }
        const nonNullableContextualType =
          checker.getNonNullableType(propContextualType);
        if (nonNullableContextualType.isUnion()) {
          return true;
        }
        const uncastType = services.getTypeAtLocation(node.expression);
        return !checker.isTypeAssignableTo(
          uncastType,
          nonNullableContextualType,
        );
      }
      const objectParent = objectExpr.parent;
      return (
        objectParent.type === AST_NODE_TYPES.TSSatisfiesExpression ||
        (objectParent.type === AST_NODE_TYPES.CallExpression &&
          objectParent.parent.type === AST_NODE_TYPES.TSSatisfiesExpression)
      );
    }

isAssignmentInNonStatementContext(node: TSESTree.TSAsExpression | TSESTree.TSTy…): boolean

Parameters:

  • node TSESTree.TSAsExpression | TSESTree.TSTypeAssertion

Returns: boolean

Code
function isAssignmentInNonStatementContext(
      node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
    ): boolean {
      const { parent } = node;
      if (
        parent.type !== AST_NODE_TYPES.AssignmentExpression ||
        parent.right !== node
      ) {
        return false;
      }
      const assignmentParent = parent.parent;
      return assignmentParent.type !== AST_NODE_TYPES.ExpressionStatement;
    }

isRightHandSideOfLogicalAssignment(node: TSESTree.TSAsExpression | TSESTree.TSTy…): boolean

Parameters:

  • node TSESTree.TSAsExpression | TSESTree.TSTypeAssertion

Returns: boolean

Code
function isRightHandSideOfLogicalAssignment(
      node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
    ): boolean {
      const { parent } = node;
      return (
        parent.type === AST_NODE_TYPES.AssignmentExpression &&
        parent.right === node &&
        (parent.operator === '&&=' ||
          parent.operator === '||=' ||
          parent.operator === '??=')
      );
    }

isInGenericContext(node: TSESTree.TSAsExpression | TSESTree.TSTy…): boolean

Parameters:

  • node TSESTree.TSAsExpression | TSESTree.TSTypeAssertion

Returns: boolean

Calls:

  • current.arguments.includes
  • checker.getTypeAtLocation
  • services.esTreeNodeToTSNodeMap.get
  • hasGenericCallSignature
Code
function isInGenericContext(
      node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
    ): boolean {
      let seenFunction = false;
      for (
        let current: TSESTree.Node | undefined = node.parent;
        current;
        current = current.parent
      ) {
        if (current.type === AST_NODE_TYPES.FunctionDeclaration) {
          return false;
        }
        if (
          current.type === AST_NODE_TYPES.FunctionExpression ||
          current.type === AST_NODE_TYPES.ArrowFunctionExpression
        ) {
          if (current.body.type === AST_NODE_TYPES.BlockStatement) {
            return false;
          }
          if (seenFunction) {
            return false;
          }
          seenFunction = true;
        }
        if (
          current.type === AST_NODE_TYPES.CallExpression ||
          current.type === AST_NODE_TYPES.NewExpression
        ) {
          if (current.typeArguments != null) {
            continue;
          }
          if (
            current.type === AST_NODE_TYPES.CallExpression &&
            current.callee.type === AST_NODE_TYPES.MemberExpression &&
            current.arguments.includes(node)
          ) {
            continue;
          }
          const calleeType = checker.getTypeAtLocation(
            services.esTreeNodeToTSNodeMap.get(current.callee),
          );
          if (hasGenericCallSignature(calleeType)) {
            return true;
          }
        }
      }
      return false;
    }

hasPhantomTypeArgumentMismatch(node: TSESTree.TSAsExpression | TSESTree.TSTy…, uncastType: ts.Type, contextualType: ts.Type): boolean

Parameters:

  • node TSESTree.TSAsExpression | TSESTree.TSTypeAssertion
  • uncastType ts.Type
  • contextualType ts.Type

Returns: boolean

Calls:

  • isInGenericContext
  • hasPhantomTypeArguments
  • haveSameTypeArguments
Code
function hasPhantomTypeArgumentMismatch(
      node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
      uncastType: ts.Type,
      contextualType: ts.Type,
    ): boolean {
      return (
        isInGenericContext(node) &&
        (hasPhantomTypeArguments(uncastType) ||
          hasPhantomTypeArguments(contextualType)) &&
        !haveSameTypeArguments(uncastType, contextualType)
      );
    }

shouldSkipContextualTypeFallback(node: TSESTree.TSAsExpression | TSESTree.TSTy…, castIsAny: boolean): boolean

Parameters:

  • node TSESTree.TSAsExpression | TSESTree.TSTypeAssertion
  • castIsAny boolean

Returns: boolean

Calls:

  • isInGenericContext
  • isTemplateLiteralWithExpressions
  • SKIP_PARENT_TYPES.has
  • isInDestructuringDeclaration
  • isPropertyInProblematicContext
  • isAssignmentInNonStatementContext
  • isRightHandSideOfLogicalAssignment
  • isArgumentToOverloadedFunction
  • getOriginalExpression
  • isConceptuallyLiteral

Internal Comments:

/**
       * Interpolated template literals can be widened to `string` while contextual
       * typing still accepts them, so the assertion may be required.
       * @see https://github.com/typescript-eslint/typescript-eslint/issues/12276
       */

Code
function shouldSkipContextualTypeFallback(
      node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
      castIsAny: boolean,
    ): boolean {
      if (castIsAny) {
        return (
          node.parent.type === AST_NODE_TYPES.LogicalExpression ||
          isInGenericContext(node)
        );
      }

      /**
       * Interpolated template literals can be widened to `string` while contextual
       * typing still accepts them, so the assertion may be required.
       * @see https://github.com/typescript-eslint/typescript-eslint/issues/12276
       */
      if (isTemplateLiteralWithExpressions(node.expression)) {
        return true;
      }

      if (
        SKIP_PARENT_TYPES.has(node.parent.type) ||
        node.expression.type === AST_NODE_TYPES.ArrayExpression ||
        isInDestructuringDeclaration(node) ||
        isPropertyInProblematicContext(node) ||
        isAssignmentInNonStatementContext(node) ||
        isRightHandSideOfLogicalAssignment(node) ||
        isArgumentToOverloadedFunction(node)
      ) {
        return true;
      }

      if (isInGenericContext(node)) {
        const originalExpr = getOriginalExpression(node);
        return (
          !isConceptuallyLiteral(originalExpr) &&
          node.parent.type !== AST_NODE_TYPES.Property
        );
      }

      return false;
    }

getUncastType(node: TSESTree.TSAsExpression | TSESTree.TSTy…): ts.Type

Parameters:

  • node TSESTree.TSAsExpression | TSESTree.TSTypeAssertion

Returns: ts.Type

Calls:

  • isIIFE
  • services.getTypeAtLocation
  • functionType.getCallSignatures
  • checker.getReturnTypeOfSignature
  • isTypeFlagSet (from ../util)
  • checker.getVoidType

Internal Comments:

// Special handling for IIFE: extract the function's return type
// If the function has no explicit return type annotation and returns undefined,
// treat it as void (TypeScript infers () => {} as () => undefined, but it should be void)

Code
function getUncastType(
      node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
    ): ts.Type {
      // Special handling for IIFE: extract the function's return type
      if (isIIFE(node.expression)) {
        const callee = node.expression.callee;
        const functionType = services.getTypeAtLocation(callee);
        const signatures = functionType.getCallSignatures();

        if (signatures.length > 0) {
          const returnType = checker.getReturnTypeOfSignature(signatures[0]);

          // If the function has no explicit return type annotation and returns undefined,
          // treat it as void (TypeScript infers () => {} as () => undefined, but it should be void)
          if (
            callee.returnType == null &&
            isTypeFlagSet(returnType, ts.TypeFlags.Undefined)
          ) {
            return checker.getVoidType();
          }

          return returnType;
        }
      }

      return services.getTypeAtLocation(node.expression);
    }

createAssertionFixer(node: TSESTree.TSAsExpression | TSESTree.TSTy…): ReportFixFunction

Parameters:

  • node TSESTree.TSAsExpression | TSESTree.TSTypeAssertion

Returns: ReportFixFunction

Calls:

  • nullThrows (from ../util)
  • context.sourceCode.getTokenBefore
  • NullThrowsReasons.MissingToken
  • context.sourceCode.getTokenAfter
  • ['{', 'function', 'class'].includes
  • isStartOfExpressionStatement (from ../util)
  • isStartOfArrowFunctionBody (from ../util)
  • fixes.push
  • fixer.insertTextBefore
  • fixer.removeRange
  • fixer.insertTextAfter

Internal Comments:

// Removing the angle brackets leaves the asserted operand at the (x2)
// assertion's position, so its first token leads whatever the (x2)
// assertion led. A leading `{`/`function`/`class` at the start of an (x2)
// expression statement is parsed as a block / function or class (x2)
// declaration, and a leading `{` at the start of a concise arrow body (x2)
// is parsed as a block body. In those positions the operand must be (x2)
// wrapped in parentheses to stay an expression. (x2)

Code
function createAssertionFixer(
      node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
    ): ReportFixFunction {
      return fixer => {
        if (node.type === AST_NODE_TYPES.TSTypeAssertion) {
          const openingAngleBracket = nullThrows(
            context.sourceCode.getTokenBefore(
              node.typeAnnotation,
              token =>
                token.type === AST_TOKEN_TYPES.Punctuator &&
                token.value === '<',
            ),
            NullThrowsReasons.MissingToken('<', 'type annotation'),
          );
          const closingAngleBracket = nullThrows(
            context.sourceCode.getTokenAfter(
              node.typeAnnotation,
              token =>
                token.type === AST_TOKEN_TYPES.Punctuator &&
                token.value === '>',
            ),
            NullThrowsReasons.MissingToken('>', 'type annotation'),
          );
          // Removing the angle brackets leaves the asserted operand at the
          // assertion's position, so its first token leads whatever the
          // assertion led. A leading `{`/`function`/`class` at the start of an
          // expression statement is parsed as a block / function or class
          // declaration, and a leading `{` at the start of a concise arrow body
          // is parsed as a block body. In those positions the operand must be
          // wrapped in parentheses to stay an expression.
          const firstOperandToken = nullThrows(
            context.sourceCode.getTokenAfter(closingAngleBracket),
            NullThrowsReasons.MissingToken('operand', 'type assertion'),
          );
          const breaksExpressionStatement =
            ['{', 'function', 'class'].includes(firstOperandToken.value) &&
            isStartOfExpressionStatement(node);
          const breaksArrowFunctionBody =
            firstOperandToken.value === '{' &&
            isStartOfArrowFunctionBody(node, context.sourceCode);
          const needsParens =
            breaksExpressionStatement || breaksArrowFunctionBody;

          const fixes: RuleFix[] = [];
          if (needsParens) {
            fixes.push(fixer.insertTextBefore(node, '('));
          }
          fixes.push(
            fixer.removeRange([
              openingAngleBracket.range[0],
              closingAngleBracket.range[1],
            ]),
          );
          if (needsParens) {
            fixes.push(fixer.insertTextAfter(node, ')'));
          }
          return fixes;
        }
        const asToken = nullThrows(
          context.sourceCode.getTokenAfter(
            node.expression,
            token =>
              token.type === AST_TOKEN_TYPES.Identifier && token.value === 'as',
          ),
          NullThrowsReasons.MissingToken('>', 'type annotation'),
        );
        const tokenBeforeAs = nullThrows(
          context.sourceCode.getTokenBefore(asToken, {
            includeComments: true,
          }),
          NullThrowsReasons.MissingToken('comment', 'as'),
        );
        return fixer.removeRange([tokenBeforeAs.range[1], node.range[1]]);
      };
    }

reportDoubleAssertionIfUnnecessary(node: TSESTree.TSAsExpression | TSESTree.TSTy…, contextualType: ts.Type | undefined): void

Parameters:

  • node TSESTree.TSAsExpression | TSESTree.TSTypeAssertion
  • contextualType ts.Type | undefined

Returns: void

Calls:

  • isDoubleAssertionUnnecessary
  • context.report
  • getOriginalExpression
  • context.sourceCode.getText
  • fixer.replaceText
Code
function reportDoubleAssertionIfUnnecessary(
      node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
      contextualType: ts.Type | undefined,
    ): void {
      const doubleAssertionResult = isDoubleAssertionUnnecessary(
        node,
        contextualType,
      );
      if (doubleAssertionResult) {
        context.report({
          node,
          messageId: doubleAssertionResult,
          fix(fixer) {
            const originalExpr = getOriginalExpression(node);
            let text = context.sourceCode.getText(originalExpr);
            if (
              originalExpr.type === AST_NODE_TYPES.ObjectExpression &&
              node.parent.type === AST_NODE_TYPES.ArrowFunctionExpression &&
              node.parent.body === node
            ) {
              text = `(${text})`;
            }
            return fixer.replaceText(node, text);
          },
        });
      }
    }

removeExclamationFix(fixer: any): any

Parameters:

  • fixer any

Returns: any

Calls:

  • nullThrows (from ../util)
  • context.sourceCode.getLastToken
  • NullThrowsReasons.MissingToken
  • fixer.removeRange
Code
fixer => {
          const exclamationToken = nullThrows(
            context.sourceCode.getLastToken(node, token => token.value === '!'),
            NullThrowsReasons.MissingToken(
              'exclamation mark',
              'non-null assertion',
            ),
          );

          return fixer.removeRange(exclamationToken.range);
        }

Type Aliases

Options

type Options = [
  {
    checkLiteralConstAssertions?: boolean;
    typesToIgnore?: string[];
  },
];

MessageIds

type MessageIds = 'contextuallyUnnecessary' | 'unnecessaryAssertion';

Generated by Syntax Scribe