Skip to content

⬅️ Back to Table of Contents

📄 enum-utils/shared

📊 Analysis Summary

Metric Count
🔧 Functions 7
📦 Imports 3

📚 Table of Contents

🛠️ File Location:

📂 packages/eslint-plugin/src/rules/enum-utils/shared.ts

📦 Imports

Name Source
isNumberLike ../../util
isStringLike ../../util
isTypeFlagSet ../../util

Functions

getEnumLiterals(type: ts.Type): ts.LiteralType[]

Retrieve only the Enum literals from a type. for example: - 123 --> [] - {} --> [] - Fruit.Apple --> [Fruit.Apple] - Fruit.Apple | Vegetable.Lettuce --> [Fruit.Apple, Vegetable.Lettuce] - Fruit.Apple | Vegetable.Lettuce | 123 --> [Fruit.Apple, Vegetable.Lettuce] - T extends Fruit --> [Fruit]

Raw JSDoc
/**
 * Retrieve only the Enum literals from a type. for example:
 * - 123 --> []
 * - {} --> []
 * - Fruit.Apple --> [Fruit.Apple]
 * - Fruit.Apple | Vegetable.Lettuce --> [Fruit.Apple, Vegetable.Lettuce]
 * - Fruit.Apple | Vegetable.Lettuce | 123 --> [Fruit.Apple, Vegetable.Lettuce]
 * - T extends Fruit --> [Fruit]
 */

Calls:

  • tsutils .unionConstituents(type) .filter
  • isTypeFlagSet (from ../../util)
Code
export function getEnumLiterals(type: ts.Type): ts.LiteralType[] {
  return tsutils
    .unionConstituents(type)
    .filter((subType): subType is ts.LiteralType =>
      isTypeFlagSet(subType, ts.TypeFlags.EnumLiteral),
    );
}

getEnumTypes(typeChecker: ts.TypeChecker, type: ts.Type): ts.Type[]

A type can have 0 or more enum types. For example: - 123 --> [] - {} --> [] - Fruit.Apple --> [Fruit] - Fruit.Apple | Vegetable.Lettuce --> [Fruit, Vegetable] - Fruit.Apple | Vegetable.Lettuce | 123 --> [Fruit, Vegetable] - T extends Fruit --> [Fruit]

Raw JSDoc
/**
 * A type can have 0 or more enum types. For example:
 * - 123 --> []
 * - {} --> []
 * - Fruit.Apple --> [Fruit]
 * - Fruit.Apple | Vegetable.Lettuce --> [Fruit, Vegetable]
 * - Fruit.Apple | Vegetable.Lettuce | 123 --> [Fruit, Vegetable]
 * - T extends Fruit --> [Fruit]
 */

Calls:

  • getEnumLiterals(type).map
  • getBaseEnumType
Code
export function getEnumTypes(
  typeChecker: ts.TypeChecker,
  type: ts.Type,
): ts.Type[] {
  return getEnumLiterals(type).map(type => getBaseEnumType(typeChecker, type));
}

isMismatchedEnumComparisonTypes(typeChecker: ts.TypeChecker, leftType: ts.Type, rightType: ts.Type): boolean

Returns: undefined Whether two types compare unsafely because an enum value is being compared against a non-enum value of the same primitive kind.

Raw JSDoc
/**
 * @returns Whether two types compare unsafely because an enum value is being
 * compared against a non-enum value of the same primitive kind.
 */

Calls:

  • getEnumTypes
  • rightEnumTypes.has
  • tsutils.unionConstituents
  • rightTypeParts.includes
  • typeViolates

Internal Comments:

// Allow comparisons that don't have anything to do with enums: (x2)
// (x6)
// ```ts (x6)
// 1 === 2; (x2)
// ``` (x6)
// Allow comparisons that share an enum type:
// Fruit.Apple === Fruit.Banana;
// We need to split the type into the union type parts in order to find (x2)
// valid enum comparisons like: (x2)
// declare const something: Fruit | Vegetable; (x2)
// something === Fruit.Apple; (x2)
// If a type exists in both sides, we consider this comparison safe:
// declare const fruit: Fruit.Apple | 0;
// fruit === 0;

Code
export function isMismatchedEnumComparisonTypes(
  typeChecker: ts.TypeChecker,
  leftType: ts.Type,
  rightType: ts.Type,
): boolean {
  // Allow comparisons that don't have anything to do with enums:
  //
  // ```ts
  // 1 === 2;
  // ```
  const leftEnumTypes = getEnumTypes(typeChecker, leftType);
  const rightEnumTypes = new Set(getEnumTypes(typeChecker, rightType));
  if (leftEnumTypes.length === 0 && rightEnumTypes.size === 0) {
    return false;
  }

  // Allow comparisons that share an enum type:
  //
  // ```ts
  // Fruit.Apple === Fruit.Banana;
  // ```
  for (const leftEnumType of leftEnumTypes) {
    if (rightEnumTypes.has(leftEnumType)) {
      return false;
    }
  }

  // We need to split the type into the union type parts in order to find
  // valid enum comparisons like:
  //
  // ```ts
  // declare const something: Fruit | Vegetable;
  // something === Fruit.Apple;
  // ```
  const leftTypeParts = tsutils.unionConstituents(leftType);
  const rightTypeParts = tsutils.unionConstituents(rightType);

  // If a type exists in both sides, we consider this comparison safe:
  //
  // ```ts
  // declare const fruit: Fruit.Apple | 0;
  // fruit === 0;
  // ```
  for (const leftTypePart of leftTypeParts) {
    if (rightTypeParts.includes(leftTypePart)) {
      return false;
    }
  }

  return (
    typeViolates(leftTypeParts, rightType) ||
    typeViolates(rightTypeParts, leftType)
  );
}

getEnumKeyForLiteral(enumLiterals: ts.LiteralType[], literal: unknown): string | null

Returns the enum key that matches the given literal node, or null if none match. For example:

enum Fruit {
  Apple = 'apple',
  Banana = 'banana',
}

getEnumKeyForLiteral([Fruit.Apple, Fruit.Banana], 'apple') --> 'Fruit.Apple'
getEnumKeyForLiteral([Fruit.Apple, Fruit.Banana], 'banana') --> 'Fruit.Banana'
getEnumKeyForLiteral([Fruit.Apple, Fruit.Banana], 'cherry') --> null

Raw JSDoc
/**
 * Returns the enum key that matches the given literal node, or null if none
 * match. For example:
 * ```ts
 * enum Fruit {
 *   Apple = 'apple',
 *   Banana = 'banana',
 * }
 *
 * getEnumKeyForLiteral([Fruit.Apple, Fruit.Banana], 'apple') --> 'Fruit.Apple'
 * getEnumKeyForLiteral([Fruit.Apple, Fruit.Banana], 'banana') --> 'Fruit.Banana'
 * getEnumKeyForLiteral([Fruit.Apple, Fruit.Banana], 'cherry') --> null
 * ```
 */

Calls:

  • memberNameIdentifier.text.replaceAll
  • memberNameIdentifier.expression.getText
Code
export function getEnumKeyForLiteral(
  enumLiterals: ts.LiteralType[],
  literal: unknown,
): string | null {
  for (const enumLiteral of enumLiterals) {
    if (enumLiteral.value === literal) {
      const { symbol } = enumLiteral;

      const memberDeclaration = symbol.valueDeclaration as ts.EnumMember;
      const enumDeclaration = memberDeclaration.parent;

      const memberNameIdentifier = memberDeclaration.name;
      const enumName = enumDeclaration.name.text;

      switch (memberNameIdentifier.kind) {
        case ts.SyntaxKind.Identifier:
          return `${enumName}.${memberNameIdentifier.text}`;

        case ts.SyntaxKind.StringLiteral: {
          const memberName = memberNameIdentifier.text.replaceAll("'", "\\'");

          return `${enumName}['${memberName}']`;
        }

        case ts.SyntaxKind.ComputedPropertyName:
          return `${enumName}[${memberNameIdentifier.expression.getText()}]`;

        default:
          break;
      }
    }
  }

  return null;
}

getBaseEnumType(typeChecker: ts.TypeChecker, type: ts.Type): ts.Type

Parameters:

  • typeChecker ts.TypeChecker
  • type ts.Type

Returns: ts.Type

Calls:

  • type.getSymbol
  • tsutils.isSymbolFlagSet
  • typeChecker.getTypeAtLocation

Internal Comments:

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion (x2)

Code
function getBaseEnumType(typeChecker: ts.TypeChecker, type: ts.Type): ts.Type {
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  const symbol = type.getSymbol()!;
  if (!tsutils.isSymbolFlagSet(symbol, ts.SymbolFlags.EnumMember)) {
    return type;
  }

  return typeChecker.getTypeAtLocation(
    (symbol.valueDeclaration as ts.EnumMember).parent,
  );
}

typeViolates(leftTypeParts: ts.Type[], rightType: ts.Type): boolean

Returns: undefined Whether the right type is an unsafe comparison against any left type.

Raw JSDoc
/**
 * @returns Whether the right type is an unsafe comparison against any left type.
 */

Calls:

  • leftTypeParts.map
  • leftEnumValueTypes.has
  • isNumberLike (from ../../util)
  • isStringLike (from ../../util)
Code
function typeViolates(leftTypeParts: ts.Type[], rightType: ts.Type): boolean {
  const leftEnumValueTypes = new Set(leftTypeParts.map(getEnumValueType));

  return (
    (leftEnumValueTypes.has(ts.TypeFlags.Number) && isNumberLike(rightType)) ||
    (leftEnumValueTypes.has(ts.TypeFlags.String) && isStringLike(rightType))
  );
}

getEnumValueType(type: ts.Type): ts.TypeFlags | undefined

Returns: undefined What type a type's enum value is (number or string), if either.

Raw JSDoc
/**
 * @returns What type a type's enum value is (number or string), if either.
 */

Calls:

  • tsutils.isTypeFlagSet
Code
function getEnumValueType(type: ts.Type): ts.TypeFlags | undefined {
  return tsutils.isTypeFlagSet(type, ts.TypeFlags.EnumLike)
    ? tsutils.isTypeFlagSet(type, ts.TypeFlags.NumberLiteral)
      ? ts.TypeFlags.Number
      : ts.TypeFlags.String
    : undefined;
}

Generated by Syntax Scribe