Skip to content

⬅️ Back to Table of Contents

📄 consistent-type-assertions

📊 Analysis Summary

Metric Count
🔧 Functions 8
📦 Imports 10
📑 Type Aliases 4

📚 Table of Contents

🛠️ File Location:

📂 packages/eslint-plugin/src/rules/consistent-type-assertions.ts

📤 Default Export

export default createRule<Options, MessageIds>({ ... })
Property Value
name 'consistent-type-assertions'
meta.type 'suggestion'
meta.docs.description 'Enforce consistent usage of type assertions'
meta.docs.recommended 'stylistic'
meta.fixable 'code'
meta.hasSuggestions true
meta.messages.as "Use 'as {{cast}}' instead of '<{{cast}}>'."
meta.messages.never 'Do not use any type assertions.'
meta.messages.replaceArrayTypeAssertionWithAnnotation 'Use const x: {{cast}} = [ ... ] instead.'
meta.messages.replaceArrayTypeAssertionWithSatisfies 'Use const x = [ ... ] satisfies {{cast}} instead.'
meta.messages.replaceObjectTypeAssertionWithAnnotation 'Use const x: {{cast}} = { ... } instead.'
meta.messages.replaceObjectTypeAssertionWithSatisfies 'Use const x = { ... } satisfies {{cast}} instead.'
meta.messages.unexpectedArrayTypeAssertion 'Always prefer const x: T[] = [ ... ].'
meta.messages.unexpectedObjectTypeAssertion 'Always prefer const x: T = { ... }.'
meta.schema [ { oneOf: [ { type: 'object', additionalProperties: false, properties: { assertionStyle: { type: 'string', descripti...
defaultOptions [ { arrayLiteralTypeAssertions: 'allow', assertionStyle: 'as', objectLiteralTypeAssertions: 'allow', }, ]

Entry point: create — documented under Functions.


📦 Imports

Name Source
TSESLint @typescript-eslint/utils
TSESTree @typescript-eslint/utils
AST_NODE_TYPES @typescript-eslint/utils
createRule ../util
getOperatorPrecedence ../util
getOperatorPrecedenceForNode ../util
getParserServices ../util
getTextWithParentheses ../util
isParenthesized ../util
getWrappedCode ../util/getWrappedCode

Functions

create(context: any, [options]: any): { TSAsExpression(node: any): void; TSTypeAssertion(node: an…

Parameters:

  • context any
  • [options] any

Returns: { TSAsExpression(node: any): void; TSTypeAssertion(node: any): void; }

Calls:

  • isConst
  • context.report
  • context.sourceCode.getText
  • getParserServices( context, true, ).esTreeNodeToTSNodeMap.get
  • getOperatorPrecedence (from ../util)
  • ts.isBinaryExpression
  • ts.isNewExpression
  • getOperatorPrecedenceForNode (from ../util)
  • getWrappedCode (from ../util/getWrappedCode)
  • fixer.replaceText
  • isParenthesized (from ../util)
  • suggestions.push
  • fixer.insertTextAfter
  • getTextWithParentheses (from ../util)
  • isAsParameter
  • checkType
  • getSuggestions
  • reportIncorrectAssertionType
  • checkExpressionForObjectAssertion
  • checkExpressionForArrayAssertion

Internal Comments:

// If this node is `as const`, then don't report an error.
// lazily access parserServices to avoid crashing on non TS files (#9860) (x2)
// Ignore `as const` and `<const>` (x2)
// Allow qualified names which have dots between identifiers, `Foo.Bar` (x4)

Code
create(context, [options]) {
    function isConst(node: TSESTree.TypeNode): boolean {
      if (node.type !== AST_NODE_TYPES.TSTypeReference) {
        return false;
      }

      return (
        node.typeName.type === AST_NODE_TYPES.Identifier &&
        node.typeName.name === 'const'
      );
    }

    function reportIncorrectAssertionType(
      node: AsExpressionOrTypeAssertion,
    ): void {
      const messageId = options.assertionStyle;

      // If this node is `as const`, then don't report an error.
      if (isConst(node.typeAnnotation) && messageId === 'never') {
        return;
      }
      context.report({
        node,
        messageId,
        data:
          messageId !== 'never'
            ? { cast: context.sourceCode.getText(node.typeAnnotation) }
            : {},
        fix:
          messageId === 'as'
            ? (fixer): TSESLint.RuleFix => {
                // lazily access parserServices to avoid crashing on non TS files (#9860)
                const tsNode = getParserServices(
                  context,
                  true,
                ).esTreeNodeToTSNodeMap.get(node as TSESTree.TSTypeAssertion);

                const expressionCode = context.sourceCode.getText(
                  node.expression,
                );
                const typeAnnotationCode = context.sourceCode.getText(
                  node.typeAnnotation,
                );

                const asPrecedence = getOperatorPrecedence(
                  ts.SyntaxKind.AsExpression,
                  ts.SyntaxKind.Unknown,
                );
                const parentPrecedence = getOperatorPrecedence(
                  tsNode.parent.kind,
                  ts.isBinaryExpression(tsNode.parent)
                    ? tsNode.parent.operatorToken.kind
                    : ts.SyntaxKind.Unknown,
                  ts.isNewExpression(tsNode.parent)
                    ? tsNode.parent.arguments != null &&
                        tsNode.parent.arguments.length > 0
                    : undefined,
                );

                const expressionPrecedence = getOperatorPrecedenceForNode(
                  node.expression,
                );

                const expressionCodeWrapped = getWrappedCode(
                  expressionCode,
                  expressionPrecedence,
                  asPrecedence,
                );

                const text = `${expressionCodeWrapped} as ${typeAnnotationCode}`;
                return fixer.replaceText(
                  node,
                  isParenthesized(node, context.sourceCode)
                    ? text
                    : getWrappedCode(text, asPrecedence, parentPrecedence),
                );
              }
            : undefined,
      });
    }

    function checkType(node: TSESTree.TypeNode): boolean {
      switch (node.type) {
        case AST_NODE_TYPES.TSAnyKeyword:
        case AST_NODE_TYPES.TSUnknownKeyword:
          return false;
        case AST_NODE_TYPES.TSTypeReference:
          return (
            // Ignore `as const` and `<const>`
            !isConst(node) ||
            // Allow qualified names which have dots between identifiers, `Foo.Bar`
            node.typeName.type === AST_NODE_TYPES.TSQualifiedName
          );

        default:
          return true;
      }
    }

    function getSuggestions(
      node: AsExpressionOrTypeAssertion,
      annotationMessageId: MessageIds,
      satisfiesMessageId: MessageIds,
    ): TSESLint.ReportSuggestionArray<MessageIds> {
      const suggestions: TSESLint.ReportSuggestionArray<MessageIds> = [];
      if (
        node.parent.type === AST_NODE_TYPES.VariableDeclarator &&
        !node.parent.id.typeAnnotation
      ) {
        const { parent } = node;
        suggestions.push({
          messageId: annotationMessageId,
          data: { cast: context.sourceCode.getText(node.typeAnnotation) },
          fix: fixer => [
            fixer.insertTextAfter(
              parent.id,
              `: ${context.sourceCode.getText(node.typeAnnotation)}`,
            ),
            fixer.replaceText(
              node,
              getTextWithParentheses(context.sourceCode, node.expression),
            ),
          ],
        });
      }
      suggestions.push({
        messageId: satisfiesMessageId,
        data: { cast: context.sourceCode.getText(node.typeAnnotation) },
        fix: fixer => [
          fixer.replaceText(
            node,
            getTextWithParentheses(context.sourceCode, node.expression),
          ),
          fixer.insertTextAfter(
            node,
            ` satisfies ${context.sourceCode.getText(node.typeAnnotation)}`,
          ),
        ],
      });
      return suggestions;
    }

    function isAsParameter(node: AsExpressionOrTypeAssertion): boolean {
      return (
        node.parent.type === AST_NODE_TYPES.NewExpression ||
        node.parent.type === AST_NODE_TYPES.CallExpression ||
        node.parent.type === AST_NODE_TYPES.ThrowStatement ||
        node.parent.type === AST_NODE_TYPES.AssignmentPattern ||
        node.parent.type === AST_NODE_TYPES.JSXExpressionContainer ||
        (node.parent.type === AST_NODE_TYPES.TemplateLiteral &&
          node.parent.parent.type === AST_NODE_TYPES.TaggedTemplateExpression)
      );
    }

    function checkExpressionForObjectAssertion(
      node: AsExpressionOrTypeAssertion,
    ): void {
      if (
        options.assertionStyle === 'never' ||
        options.objectLiteralTypeAssertions === 'allow' ||
        node.expression.type !== AST_NODE_TYPES.ObjectExpression
      ) {
        return;
      }

      if (
        options.objectLiteralTypeAssertions === 'allow-as-parameter' &&
        isAsParameter(node)
      ) {
        return;
      }

      if (checkType(node.typeAnnotation)) {
        const suggest = getSuggestions(
          node,
          'replaceObjectTypeAssertionWithAnnotation',
          'replaceObjectTypeAssertionWithSatisfies',
        );

        context.report({
          node,
          messageId: 'unexpectedObjectTypeAssertion',
          suggest,
        });
      }
    }

    function checkExpressionForArrayAssertion(
      node: AsExpressionOrTypeAssertion,
    ): void {
      if (
        options.assertionStyle === 'never' ||
        options.arrayLiteralTypeAssertions === 'allow' ||
        node.expression.type !== AST_NODE_TYPES.ArrayExpression
      ) {
        return;
      }

      if (
        options.arrayLiteralTypeAssertions === 'allow-as-parameter' &&
        isAsParameter(node)
      ) {
        return;
      }

      if (checkType(node.typeAnnotation)) {
        const suggest = getSuggestions(
          node,
          'replaceArrayTypeAssertionWithAnnotation',
          'replaceArrayTypeAssertionWithSatisfies',
        );

        context.report({
          node,
          messageId: 'unexpectedArrayTypeAssertion',
          suggest,
        });
      }
    }

    return {
      TSAsExpression(node): void {
        if (options.assertionStyle !== 'as') {
          reportIncorrectAssertionType(node);
          return;
        }

        checkExpressionForObjectAssertion(node);
        checkExpressionForArrayAssertion(node);
      },
      TSTypeAssertion(node): void {
        if (options.assertionStyle !== 'angle-bracket') {
          reportIncorrectAssertionType(node);
          return;
        }

        checkExpressionForObjectAssertion(node);
        checkExpressionForArrayAssertion(node);
      },
    };
  }

Internal helpers

Declared inside another function in this file.

isConst(node: TSESTree.TypeNode): boolean

Parameters:

  • node TSESTree.TypeNode

Returns: boolean

Code
function isConst(node: TSESTree.TypeNode): boolean {
      if (node.type !== AST_NODE_TYPES.TSTypeReference) {
        return false;
      }

      return (
        node.typeName.type === AST_NODE_TYPES.Identifier &&
        node.typeName.name === 'const'
      );
    }

reportIncorrectAssertionType(node: AsExpressionOrTypeAssertion): void

Parameters:

  • node AsExpressionOrTypeAssertion

Returns: void

Calls:

  • isConst
  • context.report
  • context.sourceCode.getText
  • getParserServices( context, true, ).esTreeNodeToTSNodeMap.get
  • getOperatorPrecedence (from ../util)
  • ts.isBinaryExpression
  • ts.isNewExpression
  • getOperatorPrecedenceForNode (from ../util)
  • getWrappedCode (from ../util/getWrappedCode)
  • fixer.replaceText
  • isParenthesized (from ../util)

Internal Comments:

// If this node is `as const`, then don't report an error.
// lazily access parserServices to avoid crashing on non TS files (#9860) (x2)

Code
function reportIncorrectAssertionType(
      node: AsExpressionOrTypeAssertion,
    ): void {
      const messageId = options.assertionStyle;

      // If this node is `as const`, then don't report an error.
      if (isConst(node.typeAnnotation) && messageId === 'never') {
        return;
      }
      context.report({
        node,
        messageId,
        data:
          messageId !== 'never'
            ? { cast: context.sourceCode.getText(node.typeAnnotation) }
            : {},
        fix:
          messageId === 'as'
            ? (fixer): TSESLint.RuleFix => {
                // lazily access parserServices to avoid crashing on non TS files (#9860)
                const tsNode = getParserServices(
                  context,
                  true,
                ).esTreeNodeToTSNodeMap.get(node as TSESTree.TSTypeAssertion);

                const expressionCode = context.sourceCode.getText(
                  node.expression,
                );
                const typeAnnotationCode = context.sourceCode.getText(
                  node.typeAnnotation,
                );

                const asPrecedence = getOperatorPrecedence(
                  ts.SyntaxKind.AsExpression,
                  ts.SyntaxKind.Unknown,
                );
                const parentPrecedence = getOperatorPrecedence(
                  tsNode.parent.kind,
                  ts.isBinaryExpression(tsNode.parent)
                    ? tsNode.parent.operatorToken.kind
                    : ts.SyntaxKind.Unknown,
                  ts.isNewExpression(tsNode.parent)
                    ? tsNode.parent.arguments != null &&
                        tsNode.parent.arguments.length > 0
                    : undefined,
                );

                const expressionPrecedence = getOperatorPrecedenceForNode(
                  node.expression,
                );

                const expressionCodeWrapped = getWrappedCode(
                  expressionCode,
                  expressionPrecedence,
                  asPrecedence,
                );

                const text = `${expressionCodeWrapped} as ${typeAnnotationCode}`;
                return fixer.replaceText(
                  node,
                  isParenthesized(node, context.sourceCode)
                    ? text
                    : getWrappedCode(text, asPrecedence, parentPrecedence),
                );
              }
            : undefined,
      });
    }

checkType(node: TSESTree.TypeNode): boolean

Parameters:

  • node TSESTree.TypeNode

Returns: boolean

Calls:

  • isConst

Internal Comments:

// Ignore `as const` and `<const>` (x2)
// Allow qualified names which have dots between identifiers, `Foo.Bar` (x4)

Code
function checkType(node: TSESTree.TypeNode): boolean {
      switch (node.type) {
        case AST_NODE_TYPES.TSAnyKeyword:
        case AST_NODE_TYPES.TSUnknownKeyword:
          return false;
        case AST_NODE_TYPES.TSTypeReference:
          return (
            // Ignore `as const` and `<const>`
            !isConst(node) ||
            // Allow qualified names which have dots between identifiers, `Foo.Bar`
            node.typeName.type === AST_NODE_TYPES.TSQualifiedName
          );

        default:
          return true;
      }
    }

getSuggestions(node: AsExpressionOrTypeAssertion, annotationMessageId: MessageIds, satisfiesMessageId: MessageIds): TSESLint.ReportSuggestionArray<MessageIds>

Parameters:

  • node AsExpressionOrTypeAssertion
  • annotationMessageId MessageIds
  • satisfiesMessageId MessageIds

Returns: TSESLint.ReportSuggestionArray<MessageIds>

Calls:

  • suggestions.push
  • context.sourceCode.getText
  • fixer.insertTextAfter
  • fixer.replaceText
  • getTextWithParentheses (from ../util)
Code
function getSuggestions(
      node: AsExpressionOrTypeAssertion,
      annotationMessageId: MessageIds,
      satisfiesMessageId: MessageIds,
    ): TSESLint.ReportSuggestionArray<MessageIds> {
      const suggestions: TSESLint.ReportSuggestionArray<MessageIds> = [];
      if (
        node.parent.type === AST_NODE_TYPES.VariableDeclarator &&
        !node.parent.id.typeAnnotation
      ) {
        const { parent } = node;
        suggestions.push({
          messageId: annotationMessageId,
          data: { cast: context.sourceCode.getText(node.typeAnnotation) },
          fix: fixer => [
            fixer.insertTextAfter(
              parent.id,
              `: ${context.sourceCode.getText(node.typeAnnotation)}`,
            ),
            fixer.replaceText(
              node,
              getTextWithParentheses(context.sourceCode, node.expression),
            ),
          ],
        });
      }
      suggestions.push({
        messageId: satisfiesMessageId,
        data: { cast: context.sourceCode.getText(node.typeAnnotation) },
        fix: fixer => [
          fixer.replaceText(
            node,
            getTextWithParentheses(context.sourceCode, node.expression),
          ),
          fixer.insertTextAfter(
            node,
            ` satisfies ${context.sourceCode.getText(node.typeAnnotation)}`,
          ),
        ],
      });
      return suggestions;
    }

isAsParameter(node: AsExpressionOrTypeAssertion): boolean

Parameters:

  • node AsExpressionOrTypeAssertion

Returns: boolean

Code
function isAsParameter(node: AsExpressionOrTypeAssertion): boolean {
      return (
        node.parent.type === AST_NODE_TYPES.NewExpression ||
        node.parent.type === AST_NODE_TYPES.CallExpression ||
        node.parent.type === AST_NODE_TYPES.ThrowStatement ||
        node.parent.type === AST_NODE_TYPES.AssignmentPattern ||
        node.parent.type === AST_NODE_TYPES.JSXExpressionContainer ||
        (node.parent.type === AST_NODE_TYPES.TemplateLiteral &&
          node.parent.parent.type === AST_NODE_TYPES.TaggedTemplateExpression)
      );
    }

checkExpressionForObjectAssertion(node: AsExpressionOrTypeAssertion): void

Parameters:

  • node AsExpressionOrTypeAssertion

Returns: void

Calls:

  • isAsParameter
  • checkType
  • getSuggestions
  • context.report
Code
function checkExpressionForObjectAssertion(
      node: AsExpressionOrTypeAssertion,
    ): void {
      if (
        options.assertionStyle === 'never' ||
        options.objectLiteralTypeAssertions === 'allow' ||
        node.expression.type !== AST_NODE_TYPES.ObjectExpression
      ) {
        return;
      }

      if (
        options.objectLiteralTypeAssertions === 'allow-as-parameter' &&
        isAsParameter(node)
      ) {
        return;
      }

      if (checkType(node.typeAnnotation)) {
        const suggest = getSuggestions(
          node,
          'replaceObjectTypeAssertionWithAnnotation',
          'replaceObjectTypeAssertionWithSatisfies',
        );

        context.report({
          node,
          messageId: 'unexpectedObjectTypeAssertion',
          suggest,
        });
      }
    }

checkExpressionForArrayAssertion(node: AsExpressionOrTypeAssertion): void

Parameters:

  • node AsExpressionOrTypeAssertion

Returns: void

Calls:

  • isAsParameter
  • checkType
  • getSuggestions
  • context.report
Code
function checkExpressionForArrayAssertion(
      node: AsExpressionOrTypeAssertion,
    ): void {
      if (
        options.assertionStyle === 'never' ||
        options.arrayLiteralTypeAssertions === 'allow' ||
        node.expression.type !== AST_NODE_TYPES.ArrayExpression
      ) {
        return;
      }

      if (
        options.arrayLiteralTypeAssertions === 'allow-as-parameter' &&
        isAsParameter(node)
      ) {
        return;
      }

      if (checkType(node.typeAnnotation)) {
        const suggest = getSuggestions(
          node,
          'replaceArrayTypeAssertionWithAnnotation',
          'replaceArrayTypeAssertionWithSatisfies',
        );

        context.report({
          node,
          messageId: 'unexpectedArrayTypeAssertion',
          suggest,
        });
      }
    }

Type Aliases

MessageIds

type MessageIds = | 'angle-bracket'
  | 'as'
  | 'never'
  | 'replaceArrayTypeAssertionWithAnnotation'
  | 'replaceArrayTypeAssertionWithSatisfies'
  | 'replaceObjectTypeAssertionWithAnnotation'
  | 'replaceObjectTypeAssertionWithSatisfies'
  | 'unexpectedArrayTypeAssertion'
  | 'unexpectedObjectTypeAssertion';

OptUnion

type OptUnion = | {
      assertionStyle: 'angle-bracket' | 'as';
      objectLiteralTypeAssertions?: 'allow' | 'allow-as-parameter' | 'never';
      arrayLiteralTypeAssertions?: 'allow' | 'allow-as-parameter' | 'never';
    }
  | {
      assertionStyle: 'never';
    };

Options

type Options = readonly [OptUnion];

AsExpressionOrTypeAssertion

type AsExpressionOrTypeAssertion = TSESTree.TSAsExpression | TSESTree.TSTypeAssertion;

Generated by Syntax Scribe