Skip to content

⬅️ Back to Table of Contents

📄 no-base-to-string

📊 Analysis Summary

Metric Count
🔧 Functions 13
📦 Imports 10
📑 Type Aliases 2
🎯 Enums 1

📚 Table of Contents

🛠️ File Location:

📂 packages/eslint-plugin/src/rules/no-base-to-string.ts

📤 Default Export

export default createRule<Options, MessageIds>({ ... })
Property Value
name 'no-base-to-string'
meta.type 'suggestion'
meta.docs.description 'Require .toString() and .toLocaleString() to only be called on objects which provide useful information when str...
meta.docs.recommended 'recommended'
meta.docs.requiresTypeChecking true
meta.messages.baseArrayJoin "Using join() for {{name}} {{certainty}} use Object's default stringification format ('[object Object]') when strin...
meta.messages.baseToString "'{{name}}' {{certainty}} use Object's default stringification format ('[object Object]') when stringified."
meta.schema [ { type: 'object', additionalProperties: false, properties: { checkUnknown: { type: 'boolean', description: 'Whether...
defaultOptions [ { checkUnknown: false, ignoredTypeNames: ['Error', 'RegExp', 'URL', 'URLSearchParams'], }, ]

Entry point: create — documented under Functions.


📦 Imports

Name Source
TSESTree @typescript-eslint/utils
AST_NODE_TYPES @typescript-eslint/utils
ASTUtils @typescript-eslint/utils
createRule ../util
getConstrainedTypeAtLocation ../util
getParserServices ../util
getTypeName ../util
isSymbolFromDefaultLibrary ../util
matchesTypeOrBaseType ../util
nullThrows ../util

Functions

create(context: any, [option]: any): { 'AssignmentExpression[operator = "+="], BinaryExpression[…

Parameters:

  • context any
  • [option] any

Returns: { 'AssignmentExpression[operator = "+="], BinaryExpression[operator = "+"]'(node: TSESTree.AssignmentExpression | TSESTree.BinaryExpression): void; CallExpression(node: TSESTree.CallExpression): void; 'CallExpression > MemberExpression.callee > Identifier[name = "join"].property'(node: TSESTree.Expression): void; 'CallExpression > MemberExpression.callee > Identifier[name = /^(toLocaleString|toString)$/].property'(node: TSESTree.Expression): void; TemplateLiteral(node: TSESTree.TemplateLiteral): void; }

Calls:

  • getParserServices (from ../util)
  • program.getTypeChecker
  • collectToStringCertainty
  • services.getTypeAtLocation
  • context.report
  • context.sourceCode.getText
  • collectJoinCertainty
  • type.types.map
  • collectSubTypeCertainty
  • certainties.every
  • checker.getTypeArguments
  • typeArgs.map
  • certainties.some
  • nullThrows (from ../util)
  • type.getNumberIndexType
  • tsutils.isUnionType
  • collectUnionTypeCertainty
  • tsutils.isIntersectionType
  • collectIntersectionTypeCertainty
  • checker.isTupleType
  • collectTupleCertainty
  • checker.isArrayType
  • collectArrayCertainty
  • visited.has
  • tsutils.isTypeParameter
  • type.getConstraint
  • tsutils.isTypeFlagSet
  • type.getSymbol
  • symbol?.getDeclarations
  • canHaveTypeParameters
  • ignoredTypeNames.includes
  • matchesTypeOrBaseType (from ../util)
  • getTypeName (from ../util)
  • type.isIntersection
  • type.isUnion
  • isToStringLikeFromObject
  • context.sourceCode.getScope
  • ASTUtils.findVariable
  • ts.isMethodSignature
  • ts.isComputedPropertyName
  • ts.isPropertyAccessExpression
  • ts.isIdentifier
  • isSymbolFromDefaultLibrary (from ../util)
  • checker.getSymbolAtLocation
  • type .getProperties() .some
  • isSymbolToPrimitiveMethod
  • checker.getPropertyOfType
  • candidate.getDeclarations
  • declarations.some
  • ts.isInterfaceDeclaration
  • checkExpression
  • isBuiltInStringCall
  • getConstrainedTypeAtLocation (from ../util)
  • checkExpressionForArrayJoin

Internal Comments:

// don't report if this is a self referencing array or tuple type
// unconstrained generic means `unknown`
// the Boolean type definition missing toString()
// unknown
// e.g. any
// eslint-disable-next-line @typescript-eslint/internal/prefer-ast-types-enum (x6)
// An explicit [Symbol.toPrimitive] declaration is always user-defined
// Otherwise, we check for known methods used in type coercion. (x2)
// We'll try to find one that's not declared on Object itself. (x2)
// Failing that, we'll fall back to one that is. (x2)
// If any declaration is not from the Object interface, this is
// user-defined (e.g. overloaded toString on a class or module).
// see https://github.com/typescript-eslint/typescript-eslint/issues/8585
// see https://github.com/typescript-eslint/typescript-eslint/issues/11945

Code
create(context, [option]) {
    const services = getParserServices(context);
    const { program } = services;
    const checker = program.getTypeChecker();
    const ignoredTypeNames = option.ignoredTypeNames ?? [];

    function checkExpression(node: TSESTree.Expression, type?: ts.Type): void {
      if (node.type === AST_NODE_TYPES.Literal) {
        return;
      }
      const certainty = collectToStringCertainty(
        type ?? services.getTypeAtLocation(node),
        new Set(),
      );

      if (certainty === Usefulness.Always) {
        return;
      }

      context.report({
        node,
        messageId: 'baseToString',
        data: {
          name: context.sourceCode.getText(node),
          certainty,
        },
      });
    }

    function checkExpressionForArrayJoin(
      node: TSESTree.Node,
      type: ts.Type,
    ): void {
      const certainty = collectJoinCertainty(type, new Set());

      if (certainty === Usefulness.Always) {
        return;
      }

      context.report({
        node,
        messageId: 'baseArrayJoin',
        data: {
          name: context.sourceCode.getText(node),
          certainty,
        },
      });
    }

    function collectUnionTypeCertainty(
      type: ts.UnionType,
      collectSubTypeCertainty: (type: ts.Type) => Usefulness,
    ): Usefulness {
      const certainties = type.types.map(t => collectSubTypeCertainty(t));
      if (certainties.every(certainty => certainty === Usefulness.Never)) {
        return Usefulness.Never;
      }

      if (certainties.every(certainty => certainty === Usefulness.Always)) {
        return Usefulness.Always;
      }

      return Usefulness.Sometimes;
    }

    function collectIntersectionTypeCertainty(
      type: ts.IntersectionType,
      collectSubTypeCertainty: (type: ts.Type) => Usefulness,
    ): Usefulness {
      for (const subType of type.types) {
        const subtypeUsefulness = collectSubTypeCertainty(subType);

        if (subtypeUsefulness === Usefulness.Always) {
          return Usefulness.Always;
        }
      }

      return Usefulness.Never;
    }

    function collectTupleCertainty(
      type: ts.TypeReference,
      visited: Set<ts.Type>,
    ): Usefulness {
      const typeArgs = checker.getTypeArguments(type);
      const certainties = typeArgs.map(t =>
        collectToStringCertainty(t, visited),
      );
      if (certainties.some(certainty => certainty === Usefulness.Never)) {
        return Usefulness.Never;
      }

      if (certainties.some(certainty => certainty === Usefulness.Sometimes)) {
        return Usefulness.Sometimes;
      }

      return Usefulness.Always;
    }

    function collectArrayCertainty(
      type: ts.Type,
      visited: Set<ts.Type>,
    ): Usefulness {
      const elemType = nullThrows(
        type.getNumberIndexType(),
        'array should have number index type',
      );
      return collectToStringCertainty(elemType, visited);
    }

    function collectJoinCertainty(
      type: ts.Type,
      visited: Set<ts.Type>,
    ): Usefulness {
      if (tsutils.isUnionType(type)) {
        return collectUnionTypeCertainty(type, t =>
          collectJoinCertainty(t, visited),
        );
      }

      if (tsutils.isIntersectionType(type)) {
        return collectIntersectionTypeCertainty(type, t =>
          collectJoinCertainty(t, visited),
        );
      }

      if (checker.isTupleType(type)) {
        return collectTupleCertainty(type, visited);
      }

      if (checker.isArrayType(type)) {
        return collectArrayCertainty(type, visited);
      }

      return Usefulness.Always;
    }

    function collectToStringCertainty(
      type: ts.Type,
      visited: Set<ts.Type>,
    ): Usefulness {
      if (visited.has(type)) {
        // don't report if this is a self referencing array or tuple type
        return Usefulness.Always;
      }

      if (tsutils.isTypeParameter(type)) {
        const constraint = type.getConstraint();
        if (constraint) {
          return collectToStringCertainty(constraint, visited);
        }
        // unconstrained generic means `unknown`
        return option.checkUnknown ? Usefulness.Sometimes : Usefulness.Always;
      }

      // the Boolean type definition missing toString()
      if (
        tsutils.isTypeFlagSet(type, ts.TypeFlags.Boolean) ||
        tsutils.isTypeFlagSet(type, ts.TypeFlags.BooleanLiteral)
      ) {
        return Usefulness.Always;
      }

      const symbol = type.aliasSymbol ?? type.getSymbol();
      const decl = symbol?.getDeclarations()?.[0];
      if (
        decl &&
        canHaveTypeParameters(decl) &&
        decl.typeParameters &&
        ignoredTypeNames.includes(symbol.name)
      ) {
        return Usefulness.Always;
      }

      if (
        matchesTypeOrBaseType(
          services,
          type => ignoredTypeNames.includes(getTypeName(checker, type)),
          type,
        )
      ) {
        return Usefulness.Always;
      }

      if (type.isIntersection()) {
        return collectIntersectionTypeCertainty(type, t =>
          collectToStringCertainty(t, visited),
        );
      }

      if (type.isUnion()) {
        return collectUnionTypeCertainty(type, t =>
          collectToStringCertainty(t, visited),
        );
      }

      if (checker.isTupleType(type)) {
        return collectTupleCertainty(type, new Set([...visited, type]));
      }

      if (checker.isArrayType(type)) {
        return collectArrayCertainty(type, new Set([...visited, type]));
      }

      switch (isToStringLikeFromObject(type)) {
        case undefined:
          // unknown
          if (option.checkUnknown && type.flags === ts.TypeFlags.Unknown) {
            return Usefulness.Sometimes;
          }
          // e.g. any
          return Usefulness.Always;

        case true:
          return Usefulness.Never;

        case false:
          return Usefulness.Always;
      }
    }

    function isBuiltInStringCall(node: TSESTree.CallExpression): boolean {
      if (
        node.callee.type === AST_NODE_TYPES.Identifier &&
        // eslint-disable-next-line @typescript-eslint/internal/prefer-ast-types-enum
        node.callee.name === 'String' &&
        node.arguments[0]
      ) {
        const scope = context.sourceCode.getScope(node);
        // eslint-disable-next-line @typescript-eslint/internal/prefer-ast-types-enum
        const variable = ASTUtils.findVariable(scope, 'String');
        return !variable?.defs.length;
      }
      return false;
    }

    function isSymbolToPrimitiveMethod(node: ts.Declaration) {
      return (
        ts.isMethodSignature(node) &&
        ts.isComputedPropertyName(node.name) &&
        ts.isPropertyAccessExpression(node.name.expression) &&
        ts.isIdentifier(node.name.expression.expression) &&
        node.name.expression.expression.text === 'Symbol' &&
        ts.isIdentifier(node.name.expression.name) &&
        node.name.expression.name.text === 'toPrimitive' &&
        isSymbolFromDefaultLibrary(
          program,
          checker.getSymbolAtLocation(node.name.expression.expression),
        )
      );
    }

    function isToStringLikeFromObject(type: ts.Type) {
      // An explicit [Symbol.toPrimitive] declaration is always user-defined
      if (
        type
          .getProperties()
          .some(
            property =>
              property.valueDeclaration &&
              isSymbolToPrimitiveMethod(property.valueDeclaration),
          )
      ) {
        return false;
      }

      // Otherwise, we check for known methods used in type coercion.
      // We'll try to find one that's not declared on Object itself.
      // Failing that, we'll fall back to one that is.
      let foundFallbackOnObject = false;

      for (const propertyName of ['toLocaleString', 'toString', 'valueOf']) {
        const candidate = checker.getPropertyOfType(type, propertyName);
        if (!candidate) {
          continue;
        }

        const declarations = candidate.getDeclarations();

        if (!declarations?.length) {
          continue;
        }

        // If any declaration is not from the Object interface, this is
        // user-defined (e.g. overloaded toString on a class or module).
        // see https://github.com/typescript-eslint/typescript-eslint/issues/8585
        // see https://github.com/typescript-eslint/typescript-eslint/issues/11945
        if (
          declarations.some(
            declaration =>
              !(
                ts.isInterfaceDeclaration(declaration.parent) &&
                declaration.parent.name.text === 'Object'
              ),
          )
        ) {
          return false;
        }

        foundFallbackOnObject = true;
      }

      return foundFallbackOnObject ? true : undefined;
    }

    return {
      'AssignmentExpression[operator = "+="], BinaryExpression[operator = "+"]'(
        node: TSESTree.AssignmentExpression | TSESTree.BinaryExpression,
      ): void {
        const leftType = services.getTypeAtLocation(node.left);
        const rightType = services.getTypeAtLocation(node.right);

        if (getTypeName(checker, leftType) === 'string') {
          checkExpression(node.right, rightType);
        } else if (
          node.left.type !== AST_NODE_TYPES.PrivateIdentifier &&
          getTypeName(checker, rightType) === 'string'
        ) {
          checkExpression(node.left, leftType);
        }
      },
      CallExpression(node: TSESTree.CallExpression): void {
        if (
          isBuiltInStringCall(node) &&
          node.arguments[0].type !== AST_NODE_TYPES.SpreadElement
        ) {
          checkExpression(node.arguments[0]);
        }
      },
      'CallExpression > MemberExpression.callee > Identifier[name = "join"].property'(
        node: TSESTree.Expression,
      ): void {
        const memberExpr = node.parent as TSESTree.MemberExpression;
        const type = getConstrainedTypeAtLocation(services, memberExpr.object);
        checkExpressionForArrayJoin(memberExpr.object, type);
      },
      'CallExpression > MemberExpression.callee > Identifier[name = /^(toLocaleString|toString)$/].property'(
        node: TSESTree.Expression,
      ): void {
        const memberExpr = node.parent as TSESTree.MemberExpression;
        checkExpression(memberExpr.object);
      },

      TemplateLiteral(node: TSESTree.TemplateLiteral): void {
        if (node.parent.type === AST_NODE_TYPES.TaggedTemplateExpression) {
          return;
        }
        for (const expression of node.expressions) {
          checkExpression(expression);
        }
      },
    };
  }

canHaveTypeParameters(declaration: ts.Declaration): any

Parameters:

  • declaration ts.Declaration

Returns: any

Calls:

  • ts.isTypeAliasDeclaration
  • ts.isInterfaceDeclaration
  • ts.isClassDeclaration
Code
(declaration: ts.Declaration) => {
  return (
    ts.isTypeAliasDeclaration(declaration) ||
    ts.isInterfaceDeclaration(declaration) ||
    ts.isClassDeclaration(declaration)
  );
}

Internal helpers

Declared inside another function in this file.

checkExpression(node: TSESTree.Expression, type: ts.Type): void

Parameters:

  • node TSESTree.Expression
  • type ts.Type

Returns: void

Calls:

  • collectToStringCertainty
  • services.getTypeAtLocation
  • context.report
  • context.sourceCode.getText
Code
function checkExpression(node: TSESTree.Expression, type?: ts.Type): void {
      if (node.type === AST_NODE_TYPES.Literal) {
        return;
      }
      const certainty = collectToStringCertainty(
        type ?? services.getTypeAtLocation(node),
        new Set(),
      );

      if (certainty === Usefulness.Always) {
        return;
      }

      context.report({
        node,
        messageId: 'baseToString',
        data: {
          name: context.sourceCode.getText(node),
          certainty,
        },
      });
    }

checkExpressionForArrayJoin(node: TSESTree.Node, type: ts.Type): void

Parameters:

  • node TSESTree.Node
  • type ts.Type

Returns: void

Calls:

  • collectJoinCertainty
  • context.report
  • context.sourceCode.getText
Code
function checkExpressionForArrayJoin(
      node: TSESTree.Node,
      type: ts.Type,
    ): void {
      const certainty = collectJoinCertainty(type, new Set());

      if (certainty === Usefulness.Always) {
        return;
      }

      context.report({
        node,
        messageId: 'baseArrayJoin',
        data: {
          name: context.sourceCode.getText(node),
          certainty,
        },
      });
    }

collectUnionTypeCertainty(type: ts.UnionType, collectSubTypeCertainty: (type: ts.Type) => Usefulness): Usefulness

Parameters:

  • type ts.UnionType
  • collectSubTypeCertainty (type: ts.Type) => Usefulness

Returns: Usefulness

Calls:

  • type.types.map
  • collectSubTypeCertainty
  • certainties.every
Code
function collectUnionTypeCertainty(
      type: ts.UnionType,
      collectSubTypeCertainty: (type: ts.Type) => Usefulness,
    ): Usefulness {
      const certainties = type.types.map(t => collectSubTypeCertainty(t));
      if (certainties.every(certainty => certainty === Usefulness.Never)) {
        return Usefulness.Never;
      }

      if (certainties.every(certainty => certainty === Usefulness.Always)) {
        return Usefulness.Always;
      }

      return Usefulness.Sometimes;
    }

collectIntersectionTypeCertainty(type: ts.IntersectionType, collectSubTypeCertainty: (type: ts.Type) => Usefulness): Usefulness

Parameters:

  • type ts.IntersectionType
  • collectSubTypeCertainty (type: ts.Type) => Usefulness

Returns: Usefulness

Calls:

  • collectSubTypeCertainty
Code
function collectIntersectionTypeCertainty(
      type: ts.IntersectionType,
      collectSubTypeCertainty: (type: ts.Type) => Usefulness,
    ): Usefulness {
      for (const subType of type.types) {
        const subtypeUsefulness = collectSubTypeCertainty(subType);

        if (subtypeUsefulness === Usefulness.Always) {
          return Usefulness.Always;
        }
      }

      return Usefulness.Never;
    }

collectTupleCertainty(type: ts.TypeReference, visited: Set<ts.Type>): Usefulness

Parameters:

  • type ts.TypeReference
  • visited Set<ts.Type>

Returns: Usefulness

Calls:

  • checker.getTypeArguments
  • typeArgs.map
  • collectToStringCertainty
  • certainties.some
Code
function collectTupleCertainty(
      type: ts.TypeReference,
      visited: Set<ts.Type>,
    ): Usefulness {
      const typeArgs = checker.getTypeArguments(type);
      const certainties = typeArgs.map(t =>
        collectToStringCertainty(t, visited),
      );
      if (certainties.some(certainty => certainty === Usefulness.Never)) {
        return Usefulness.Never;
      }

      if (certainties.some(certainty => certainty === Usefulness.Sometimes)) {
        return Usefulness.Sometimes;
      }

      return Usefulness.Always;
    }

collectArrayCertainty(type: ts.Type, visited: Set<ts.Type>): Usefulness

Parameters:

  • type ts.Type
  • visited Set<ts.Type>

Returns: Usefulness

Calls:

  • nullThrows (from ../util)
  • type.getNumberIndexType
  • collectToStringCertainty
Code
function collectArrayCertainty(
      type: ts.Type,
      visited: Set<ts.Type>,
    ): Usefulness {
      const elemType = nullThrows(
        type.getNumberIndexType(),
        'array should have number index type',
      );
      return collectToStringCertainty(elemType, visited);
    }

collectJoinCertainty(type: ts.Type, visited: Set<ts.Type>): Usefulness

Parameters:

  • type ts.Type
  • visited Set<ts.Type>

Returns: Usefulness

Calls:

  • tsutils.isUnionType
  • collectUnionTypeCertainty
  • collectJoinCertainty
  • tsutils.isIntersectionType
  • collectIntersectionTypeCertainty
  • checker.isTupleType
  • collectTupleCertainty
  • checker.isArrayType
  • collectArrayCertainty
Code
function collectJoinCertainty(
      type: ts.Type,
      visited: Set<ts.Type>,
    ): Usefulness {
      if (tsutils.isUnionType(type)) {
        return collectUnionTypeCertainty(type, t =>
          collectJoinCertainty(t, visited),
        );
      }

      if (tsutils.isIntersectionType(type)) {
        return collectIntersectionTypeCertainty(type, t =>
          collectJoinCertainty(t, visited),
        );
      }

      if (checker.isTupleType(type)) {
        return collectTupleCertainty(type, visited);
      }

      if (checker.isArrayType(type)) {
        return collectArrayCertainty(type, visited);
      }

      return Usefulness.Always;
    }

collectToStringCertainty(type: ts.Type, visited: Set<ts.Type>): Usefulness

Parameters:

  • type ts.Type
  • visited Set<ts.Type>

Returns: Usefulness

Calls:

  • visited.has
  • tsutils.isTypeParameter
  • type.getConstraint
  • collectToStringCertainty
  • tsutils.isTypeFlagSet
  • type.getSymbol
  • symbol?.getDeclarations
  • canHaveTypeParameters
  • ignoredTypeNames.includes
  • matchesTypeOrBaseType (from ../util)
  • getTypeName (from ../util)
  • type.isIntersection
  • collectIntersectionTypeCertainty
  • type.isUnion
  • collectUnionTypeCertainty
  • checker.isTupleType
  • collectTupleCertainty
  • checker.isArrayType
  • collectArrayCertainty
  • isToStringLikeFromObject

Internal Comments:

// don't report if this is a self referencing array or tuple type
// unconstrained generic means `unknown`
// the Boolean type definition missing toString()
// unknown
// e.g. any

Code
function collectToStringCertainty(
      type: ts.Type,
      visited: Set<ts.Type>,
    ): Usefulness {
      if (visited.has(type)) {
        // don't report if this is a self referencing array or tuple type
        return Usefulness.Always;
      }

      if (tsutils.isTypeParameter(type)) {
        const constraint = type.getConstraint();
        if (constraint) {
          return collectToStringCertainty(constraint, visited);
        }
        // unconstrained generic means `unknown`
        return option.checkUnknown ? Usefulness.Sometimes : Usefulness.Always;
      }

      // the Boolean type definition missing toString()
      if (
        tsutils.isTypeFlagSet(type, ts.TypeFlags.Boolean) ||
        tsutils.isTypeFlagSet(type, ts.TypeFlags.BooleanLiteral)
      ) {
        return Usefulness.Always;
      }

      const symbol = type.aliasSymbol ?? type.getSymbol();
      const decl = symbol?.getDeclarations()?.[0];
      if (
        decl &&
        canHaveTypeParameters(decl) &&
        decl.typeParameters &&
        ignoredTypeNames.includes(symbol.name)
      ) {
        return Usefulness.Always;
      }

      if (
        matchesTypeOrBaseType(
          services,
          type => ignoredTypeNames.includes(getTypeName(checker, type)),
          type,
        )
      ) {
        return Usefulness.Always;
      }

      if (type.isIntersection()) {
        return collectIntersectionTypeCertainty(type, t =>
          collectToStringCertainty(t, visited),
        );
      }

      if (type.isUnion()) {
        return collectUnionTypeCertainty(type, t =>
          collectToStringCertainty(t, visited),
        );
      }

      if (checker.isTupleType(type)) {
        return collectTupleCertainty(type, new Set([...visited, type]));
      }

      if (checker.isArrayType(type)) {
        return collectArrayCertainty(type, new Set([...visited, type]));
      }

      switch (isToStringLikeFromObject(type)) {
        case undefined:
          // unknown
          if (option.checkUnknown && type.flags === ts.TypeFlags.Unknown) {
            return Usefulness.Sometimes;
          }
          // e.g. any
          return Usefulness.Always;

        case true:
          return Usefulness.Never;

        case false:
          return Usefulness.Always;
      }
    }

isBuiltInStringCall(node: TSESTree.CallExpression): boolean

Parameters:

  • node TSESTree.CallExpression

Returns: boolean

Calls:

  • context.sourceCode.getScope
  • ASTUtils.findVariable

Internal Comments:

// eslint-disable-next-line @typescript-eslint/internal/prefer-ast-types-enum (x6)

Code
function isBuiltInStringCall(node: TSESTree.CallExpression): boolean {
      if (
        node.callee.type === AST_NODE_TYPES.Identifier &&
        // eslint-disable-next-line @typescript-eslint/internal/prefer-ast-types-enum
        node.callee.name === 'String' &&
        node.arguments[0]
      ) {
        const scope = context.sourceCode.getScope(node);
        // eslint-disable-next-line @typescript-eslint/internal/prefer-ast-types-enum
        const variable = ASTUtils.findVariable(scope, 'String');
        return !variable?.defs.length;
      }
      return false;
    }

isSymbolToPrimitiveMethod(node: ts.Declaration): any

Parameters:

  • node ts.Declaration

Returns: any

Calls:

  • ts.isMethodSignature
  • ts.isComputedPropertyName
  • ts.isPropertyAccessExpression
  • ts.isIdentifier
  • isSymbolFromDefaultLibrary (from ../util)
  • checker.getSymbolAtLocation
Code
function isSymbolToPrimitiveMethod(node: ts.Declaration) {
      return (
        ts.isMethodSignature(node) &&
        ts.isComputedPropertyName(node.name) &&
        ts.isPropertyAccessExpression(node.name.expression) &&
        ts.isIdentifier(node.name.expression.expression) &&
        node.name.expression.expression.text === 'Symbol' &&
        ts.isIdentifier(node.name.expression.name) &&
        node.name.expression.name.text === 'toPrimitive' &&
        isSymbolFromDefaultLibrary(
          program,
          checker.getSymbolAtLocation(node.name.expression.expression),
        )
      );
    }

isToStringLikeFromObject(type: ts.Type): boolean

Parameters:

  • type ts.Type

Returns: boolean

Calls:

  • type .getProperties() .some
  • isSymbolToPrimitiveMethod
  • checker.getPropertyOfType
  • candidate.getDeclarations
  • declarations.some
  • ts.isInterfaceDeclaration

Internal Comments:

// An explicit [Symbol.toPrimitive] declaration is always user-defined
// Otherwise, we check for known methods used in type coercion. (x2)
// We'll try to find one that's not declared on Object itself. (x2)
// Failing that, we'll fall back to one that is. (x2)
// If any declaration is not from the Object interface, this is
// user-defined (e.g. overloaded toString on a class or module).
// see https://github.com/typescript-eslint/typescript-eslint/issues/8585
// see https://github.com/typescript-eslint/typescript-eslint/issues/11945

Code
function isToStringLikeFromObject(type: ts.Type) {
      // An explicit [Symbol.toPrimitive] declaration is always user-defined
      if (
        type
          .getProperties()
          .some(
            property =>
              property.valueDeclaration &&
              isSymbolToPrimitiveMethod(property.valueDeclaration),
          )
      ) {
        return false;
      }

      // Otherwise, we check for known methods used in type coercion.
      // We'll try to find one that's not declared on Object itself.
      // Failing that, we'll fall back to one that is.
      let foundFallbackOnObject = false;

      for (const propertyName of ['toLocaleString', 'toString', 'valueOf']) {
        const candidate = checker.getPropertyOfType(type, propertyName);
        if (!candidate) {
          continue;
        }

        const declarations = candidate.getDeclarations();

        if (!declarations?.length) {
          continue;
        }

        // If any declaration is not from the Object interface, this is
        // user-defined (e.g. overloaded toString on a class or module).
        // see https://github.com/typescript-eslint/typescript-eslint/issues/8585
        // see https://github.com/typescript-eslint/typescript-eslint/issues/11945
        if (
          declarations.some(
            declaration =>
              !(
                ts.isInterfaceDeclaration(declaration.parent) &&
                declaration.parent.name.text === 'Object'
              ),
          )
        ) {
          return false;
        }

        foundFallbackOnObject = true;
      }

      return foundFallbackOnObject ? true : undefined;
    }

Type Aliases

Options

type Options = [
  {
    ignoredTypeNames?: string[];
    checkUnknown?: boolean;
  },
];

MessageIds

type MessageIds = 'baseArrayJoin' | 'baseToString';

Enums

enum Usefulness

Enum Code
enum Usefulness {
  Always = 'always',
  Never = 'will',
  Sometimes = 'may',
}

Members

Name Value Description
Always always not shown
Never will not shown
Sometimes may not shown

Generated by Syntax Scribe