Skip to content

⬅️ Back to Table of Contents

📄 no-inferrable-types

📊 Analysis Summary

Metric Count
🔧 Functions 10
📦 Imports 6
📑 Type Aliases 3

📚 Table of Contents

🛠️ File Location:

📂 packages/eslint-plugin/src/rules/no-inferrable-types.ts

📤 Default Export

export default createRule<Options, MessageIds>({ ... })
Property Value
name 'no-inferrable-types'
meta.type 'suggestion'
meta.docs.description 'Disallow explicit type declarations for variables or parameters initialized to a number, string, or boolean'
meta.docs.recommended 'stylistic'
meta.fixable 'code'
meta.messages.noInferrableType 'Type {{type}} trivially inferred from a {{type}} literal, remove type annotation.'
meta.schema [ { type: 'object', additionalProperties: false, properties: { ignoreParameters: { type: 'boolean', description: 'Whe...
defaultOptions [ { ignoreParameters: false, ignoreProperties: false, }, ]

Entry point: create — documented under Functions.


📦 Imports

Name Source
TSESTree @typescript-eslint/utils
AST_NODE_TYPES @typescript-eslint/utils
createRule ../util
nullThrows ../util
NullThrowsReasons ../util
skipChainExpression ../util

Functions

create(context: any, [{ ignoreParameters, ignoreProp…: any): { AccessorProperty: (node: TSESTree.AccessorProperty | TSES…

Parameters:

  • context any
  • [{ ignoreParameters, ignoreProperties }] any

Returns: { AccessorProperty: (node: TSESTree.AccessorProperty | TSESTree.PropertyDefinition) => void; ArrowFunctionExpression: (node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionDeclaration | TSESTree.FunctionExpression) => void; FunctionDeclaration: (node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionDeclaration | TSESTree.FunctionExpression) => void; FunctionExpression: (node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionDeclaration | TSESTree.FunctionExpression) => void; PropertyDefinition: (node: TSESTree.AccessorProperty | TSESTree.PropertyDefinition) => void; VariableDeclarator: (node: TSESTree.VariableDeclarator) => void; }

Calls:

  • skipChainExpression (from ../util)
  • names.includes
  • operators.includes
  • hasUnaryPrefix
  • isFunctionCall
  • isLiteral
  • isIdentifier
  • isInferrable
  • context.report
  • fixer.remove
  • nullThrows (from ../util)
  • context.sourceCode.getTokenBefore
  • NullThrowsReasons.MissingToken
  • reportInferrableType
  • node.params.forEach

Internal Comments:

/**
     * Returns whether a node has an inferrable value or not
     */
// note that bigint cannot have + prefixed to it (x2)
/**
     * Reports an inferrable type declaration, if any
     */
// We ignore `readonly` because of Microsoft/TypeScript#14416
// Essentially a readonly property without a type
// will result in its value being the type, leading to
// compile errors if the type is stripped.

Code
create(context, [{ ignoreParameters, ignoreProperties }]) {
    function isFunctionCall(
      init: TSESTree.Expression,
      callName: string,
    ): boolean {
      const node = skipChainExpression(init);

      return (
        node.type === AST_NODE_TYPES.CallExpression &&
        node.callee.type === AST_NODE_TYPES.Identifier &&
        node.callee.name === callName
      );
    }
    function isLiteral(init: TSESTree.Expression, typeName: string): boolean {
      return (
        init.type === AST_NODE_TYPES.Literal && typeof init.value === typeName
      );
    }
    function isIdentifier(
      init: TSESTree.Expression,
      ...names: string[]
    ): boolean {
      return (
        init.type === AST_NODE_TYPES.Identifier && names.includes(init.name)
      );
    }
    function hasUnaryPrefix(
      init: TSESTree.Expression,
      ...operators: string[]
    ): init is TSESTree.UnaryExpression {
      return (
        init.type === AST_NODE_TYPES.UnaryExpression &&
        operators.includes(init.operator)
      );
    }

    type Keywords =
      | TSESTree.TSBigIntKeyword
      | TSESTree.TSBooleanKeyword
      | TSESTree.TSNullKeyword
      | TSESTree.TSNumberKeyword
      | TSESTree.TSStringKeyword
      | TSESTree.TSSymbolKeyword
      | TSESTree.TSTypeReference
      | TSESTree.TSUndefinedKeyword;
    const keywordMap = {
      [AST_NODE_TYPES.TSBigIntKeyword]: 'bigint',
      [AST_NODE_TYPES.TSBooleanKeyword]: 'boolean',
      [AST_NODE_TYPES.TSNullKeyword]: 'null',
      [AST_NODE_TYPES.TSNumberKeyword]: 'number',
      [AST_NODE_TYPES.TSStringKeyword]: 'string',
      [AST_NODE_TYPES.TSSymbolKeyword]: 'symbol',
      [AST_NODE_TYPES.TSUndefinedKeyword]: 'undefined',
    };

    /**
     * Returns whether a node has an inferrable value or not
     */
    function isInferrable(
      annotation: TSESTree.TypeNode,
      init: TSESTree.Expression,
    ): annotation is Keywords {
      switch (annotation.type) {
        case AST_NODE_TYPES.TSBigIntKeyword: {
          // note that bigint cannot have + prefixed to it
          const unwrappedInit = hasUnaryPrefix(init, '-')
            ? init.argument
            : init;

          return (
            isFunctionCall(unwrappedInit, 'BigInt') ||
            unwrappedInit.type === AST_NODE_TYPES.Literal
          );
        }

        case AST_NODE_TYPES.TSBooleanKeyword:
          return (
            hasUnaryPrefix(init, '!') ||
            isFunctionCall(init, 'Boolean') ||
            isLiteral(init, 'boolean')
          );

        case AST_NODE_TYPES.TSNumberKeyword: {
          const unwrappedInit = hasUnaryPrefix(init, '+', '-')
            ? init.argument
            : init;

          return (
            isIdentifier(unwrappedInit, 'Infinity', 'NaN') ||
            isFunctionCall(unwrappedInit, 'Number') ||
            isLiteral(unwrappedInit, 'number')
          );
        }

        case AST_NODE_TYPES.TSNullKeyword:
          return init.type === AST_NODE_TYPES.Literal && init.value == null;

        case AST_NODE_TYPES.TSStringKeyword:
          return (
            isFunctionCall(init, 'String') ||
            isLiteral(init, 'string') ||
            init.type === AST_NODE_TYPES.TemplateLiteral
          );

        case AST_NODE_TYPES.TSSymbolKeyword:
          return isFunctionCall(init, 'Symbol');

        case AST_NODE_TYPES.TSTypeReference: {
          if (
            annotation.typeName.type === AST_NODE_TYPES.Identifier &&
            annotation.typeName.name === 'RegExp'
          ) {
            const isRegExpLiteral =
              init.type === AST_NODE_TYPES.Literal &&
              init.value instanceof RegExp;
            const isRegExpNewCall =
              init.type === AST_NODE_TYPES.NewExpression &&
              init.callee.type === AST_NODE_TYPES.Identifier &&
              init.callee.name === 'RegExp';
            const isRegExpCall = isFunctionCall(init, 'RegExp');

            return isRegExpLiteral || isRegExpCall || isRegExpNewCall;
          }

          return false;
        }

        case AST_NODE_TYPES.TSUndefinedKeyword:
          return (
            hasUnaryPrefix(init, 'void') || isIdentifier(init, 'undefined')
          );
      }

      return false;
    }

    /**
     * Reports an inferrable type declaration, if any
     */
    function reportInferrableType(
      node:
        | TSESTree.AccessorProperty
        | TSESTree.Parameter
        | TSESTree.PropertyDefinition
        | TSESTree.VariableDeclarator,
      typeNode: TSESTree.TSTypeAnnotation | undefined,
      initNode: TSESTree.Expression | null | undefined,
    ): void {
      if (!typeNode || !initNode) {
        return;
      }

      if (!isInferrable(typeNode.typeAnnotation, initNode)) {
        return;
      }

      const type =
        typeNode.typeAnnotation.type === AST_NODE_TYPES.TSTypeReference
          ? // TODO - if we add more references
            'RegExp'
          : keywordMap[typeNode.typeAnnotation.type];

      context.report({
        node,
        messageId: 'noInferrableType',
        data: {
          type,
        },
        *fix(fixer) {
          if (
            (node.type === AST_NODE_TYPES.AssignmentPattern &&
              node.left.optional) ||
            (node.type === AST_NODE_TYPES.PropertyDefinition && node.definite)
          ) {
            yield fixer.remove(
              nullThrows(
                context.sourceCode.getTokenBefore(typeNode),
                NullThrowsReasons.MissingToken('token before', 'type node'),
              ),
            );
          }
          yield fixer.remove(typeNode);
        },
      });
    }

    function inferrableVariableVisitor(
      node: TSESTree.VariableDeclarator,
    ): void {
      reportInferrableType(node, node.id.typeAnnotation, node.init);
    }

    function inferrableParameterVisitor(
      node:
        | TSESTree.ArrowFunctionExpression
        | TSESTree.FunctionDeclaration
        | TSESTree.FunctionExpression,
    ): void {
      if (ignoreParameters) {
        return;
      }

      node.params.forEach(param => {
        if (param.type === AST_NODE_TYPES.TSParameterProperty) {
          param = param.parameter;
        }

        if (param.type === AST_NODE_TYPES.AssignmentPattern) {
          reportInferrableType(param, param.left.typeAnnotation, param.right);
        }
      });
    }

    function inferrablePropertyVisitor(
      node: TSESTree.AccessorProperty | TSESTree.PropertyDefinition,
    ): void {
      // We ignore `readonly` because of Microsoft/TypeScript#14416
      // Essentially a readonly property without a type
      // will result in its value being the type, leading to
      // compile errors if the type is stripped.
      if (ignoreProperties || node.readonly || node.optional) {
        return;
      }
      reportInferrableType(node, node.typeAnnotation, node.value);
    }

    return {
      AccessorProperty: inferrablePropertyVisitor,
      ArrowFunctionExpression: inferrableParameterVisitor,
      FunctionDeclaration: inferrableParameterVisitor,
      FunctionExpression: inferrableParameterVisitor,
      PropertyDefinition: inferrablePropertyVisitor,
      VariableDeclarator: inferrableVariableVisitor,
    };
  }

Internal helpers

Declared inside another function in this file.

isFunctionCall(init: TSESTree.Expression, callName: string): boolean

Parameters:

  • init TSESTree.Expression
  • callName string

Returns: boolean

Calls:

  • skipChainExpression (from ../util)
Code
function isFunctionCall(
      init: TSESTree.Expression,
      callName: string,
    ): boolean {
      const node = skipChainExpression(init);

      return (
        node.type === AST_NODE_TYPES.CallExpression &&
        node.callee.type === AST_NODE_TYPES.Identifier &&
        node.callee.name === callName
      );
    }

isLiteral(init: TSESTree.Expression, typeName: string): boolean

Parameters:

  • init TSESTree.Expression
  • typeName string

Returns: boolean

Code
function isLiteral(init: TSESTree.Expression, typeName: string): boolean {
      return (
        init.type === AST_NODE_TYPES.Literal && typeof init.value === typeName
      );
    }

isIdentifier(init: TSESTree.Expression, names: string[]): boolean

Parameters:

  • init TSESTree.Expression
  • names string[]

Returns: boolean

Calls:

  • names.includes
Code
function isIdentifier(
      init: TSESTree.Expression,
      ...names: string[]
    ): boolean {
      return (
        init.type === AST_NODE_TYPES.Identifier && names.includes(init.name)
      );
    }

hasUnaryPrefix(init: TSESTree.Expression, operators: string[]): init is TSESTree.UnaryExpression

Parameters:

  • init TSESTree.Expression
  • operators string[]

Returns: init is TSESTree.UnaryExpression

Calls:

  • operators.includes
Code
function hasUnaryPrefix(
      init: TSESTree.Expression,
      ...operators: string[]
    ): init is TSESTree.UnaryExpression {
      return (
        init.type === AST_NODE_TYPES.UnaryExpression &&
        operators.includes(init.operator)
      );
    }

isInferrable(annotation: TSESTree.TypeNode, init: TSESTree.Expression): annotation is Keywords

Returns whether a node has an inferrable value or not

Raw JSDoc
/**
     * Returns whether a node has an inferrable value or not
     */

Calls:

  • hasUnaryPrefix
  • isFunctionCall
  • isLiteral
  • isIdentifier

Internal Comments:

// note that bigint cannot have + prefixed to it (x2)

Code
function isInferrable(
      annotation: TSESTree.TypeNode,
      init: TSESTree.Expression,
    ): annotation is Keywords {
      switch (annotation.type) {
        case AST_NODE_TYPES.TSBigIntKeyword: {
          // note that bigint cannot have + prefixed to it
          const unwrappedInit = hasUnaryPrefix(init, '-')
            ? init.argument
            : init;

          return (
            isFunctionCall(unwrappedInit, 'BigInt') ||
            unwrappedInit.type === AST_NODE_TYPES.Literal
          );
        }

        case AST_NODE_TYPES.TSBooleanKeyword:
          return (
            hasUnaryPrefix(init, '!') ||
            isFunctionCall(init, 'Boolean') ||
            isLiteral(init, 'boolean')
          );

        case AST_NODE_TYPES.TSNumberKeyword: {
          const unwrappedInit = hasUnaryPrefix(init, '+', '-')
            ? init.argument
            : init;

          return (
            isIdentifier(unwrappedInit, 'Infinity', 'NaN') ||
            isFunctionCall(unwrappedInit, 'Number') ||
            isLiteral(unwrappedInit, 'number')
          );
        }

        case AST_NODE_TYPES.TSNullKeyword:
          return init.type === AST_NODE_TYPES.Literal && init.value == null;

        case AST_NODE_TYPES.TSStringKeyword:
          return (
            isFunctionCall(init, 'String') ||
            isLiteral(init, 'string') ||
            init.type === AST_NODE_TYPES.TemplateLiteral
          );

        case AST_NODE_TYPES.TSSymbolKeyword:
          return isFunctionCall(init, 'Symbol');

        case AST_NODE_TYPES.TSTypeReference: {
          if (
            annotation.typeName.type === AST_NODE_TYPES.Identifier &&
            annotation.typeName.name === 'RegExp'
          ) {
            const isRegExpLiteral =
              init.type === AST_NODE_TYPES.Literal &&
              init.value instanceof RegExp;
            const isRegExpNewCall =
              init.type === AST_NODE_TYPES.NewExpression &&
              init.callee.type === AST_NODE_TYPES.Identifier &&
              init.callee.name === 'RegExp';
            const isRegExpCall = isFunctionCall(init, 'RegExp');

            return isRegExpLiteral || isRegExpCall || isRegExpNewCall;
          }

          return false;
        }

        case AST_NODE_TYPES.TSUndefinedKeyword:
          return (
            hasUnaryPrefix(init, 'void') || isIdentifier(init, 'undefined')
          );
      }

      return false;
    }

reportInferrableType(node: | TSESTree.AccessorProperty | TSESTree.…, typeNode: TSESTree.TSTypeAnnotation | undefined, initNode: TSESTree.Expression | null | undefined): void

Reports an inferrable type declaration, if any

Raw JSDoc
/**
     * Reports an inferrable type declaration, if any
     */

Calls:

  • isInferrable
  • context.report
  • fixer.remove
  • nullThrows (from ../util)
  • context.sourceCode.getTokenBefore
  • NullThrowsReasons.MissingToken
Code
function reportInferrableType(
      node:
        | TSESTree.AccessorProperty
        | TSESTree.Parameter
        | TSESTree.PropertyDefinition
        | TSESTree.VariableDeclarator,
      typeNode: TSESTree.TSTypeAnnotation | undefined,
      initNode: TSESTree.Expression | null | undefined,
    ): void {
      if (!typeNode || !initNode) {
        return;
      }

      if (!isInferrable(typeNode.typeAnnotation, initNode)) {
        return;
      }

      const type =
        typeNode.typeAnnotation.type === AST_NODE_TYPES.TSTypeReference
          ? // TODO - if we add more references
            'RegExp'
          : keywordMap[typeNode.typeAnnotation.type];

      context.report({
        node,
        messageId: 'noInferrableType',
        data: {
          type,
        },
        *fix(fixer) {
          if (
            (node.type === AST_NODE_TYPES.AssignmentPattern &&
              node.left.optional) ||
            (node.type === AST_NODE_TYPES.PropertyDefinition && node.definite)
          ) {
            yield fixer.remove(
              nullThrows(
                context.sourceCode.getTokenBefore(typeNode),
                NullThrowsReasons.MissingToken('token before', 'type node'),
              ),
            );
          }
          yield fixer.remove(typeNode);
        },
      });
    }

inferrableVariableVisitor(node: TSESTree.VariableDeclarator): void

Parameters:

  • node TSESTree.VariableDeclarator

Returns: void

Calls:

  • reportInferrableType
Code
function inferrableVariableVisitor(
      node: TSESTree.VariableDeclarator,
    ): void {
      reportInferrableType(node, node.id.typeAnnotation, node.init);
    }

inferrableParameterVisitor(node: | TSESTree.ArrowFunctionExpression | TS…): void

Parameters:

  • node | TSESTree.ArrowFunctionExpression | TSESTree.FunctionDeclaration | TSESTree.FunctionExpression

Returns: void

Calls:

  • node.params.forEach
  • reportInferrableType
Code
function inferrableParameterVisitor(
      node:
        | TSESTree.ArrowFunctionExpression
        | TSESTree.FunctionDeclaration
        | TSESTree.FunctionExpression,
    ): void {
      if (ignoreParameters) {
        return;
      }

      node.params.forEach(param => {
        if (param.type === AST_NODE_TYPES.TSParameterProperty) {
          param = param.parameter;
        }

        if (param.type === AST_NODE_TYPES.AssignmentPattern) {
          reportInferrableType(param, param.left.typeAnnotation, param.right);
        }
      });
    }

inferrablePropertyVisitor(node: TSESTree.AccessorProperty | TSESTree.Pr…): void

Parameters:

  • node TSESTree.AccessorProperty | TSESTree.PropertyDefinition

Returns: void

Calls:

  • reportInferrableType

Internal Comments:

// We ignore `readonly` because of Microsoft/TypeScript#14416
// Essentially a readonly property without a type
// will result in its value being the type, leading to
// compile errors if the type is stripped.

Code
function inferrablePropertyVisitor(
      node: TSESTree.AccessorProperty | TSESTree.PropertyDefinition,
    ): void {
      // We ignore `readonly` because of Microsoft/TypeScript#14416
      // Essentially a readonly property without a type
      // will result in its value being the type, leading to
      // compile errors if the type is stripped.
      if (ignoreProperties || node.readonly || node.optional) {
        return;
      }
      reportInferrableType(node, node.typeAnnotation, node.value);
    }

Type Aliases

Options

type Options = [
  {
    ignoreParameters?: boolean;
    ignoreProperties?: boolean;
  },
];

MessageIds

type MessageIds = 'noInferrableType';

Keywords

type Keywords = | TSESTree.TSBigIntKeyword
      | TSESTree.TSBooleanKeyword
      | TSESTree.TSNullKeyword
      | TSESTree.TSNumberKeyword
      | TSESTree.TSStringKeyword
      | TSESTree.TSSymbolKeyword
      | TSESTree.TSTypeReference
      | TSESTree.TSUndefinedKeyword;

Generated by Syntax Scribe