Repository: babel/babel
File: packages/babel-plugin-transform-typescript/src/enum.ts (TypeScript)

Code before:
  t: t;
};

function ReferencedIdentifier(
  expr: NodePath<t.Identifier>,
  state: EnumSelfReferenceVisitorState,
) {
  const { seen, path, t } = state;
  const name = expr.node.name;
  if (seen.has(name) && !expr.scope.hasOwnBinding(name)) {
    expr.replaceWith(
      t.memberExpression(t.cloneNode(path.node.id), t.cloneNode(expr.node)),
    );
    expr.skip();
  }
}

const enumSelfReferenceVisitor = {
  ReferencedIdentifier,
};

export function translateEnumValues(path: NodePath<t.TSEnumDeclaration>, t: t) {
  const bindingIdentifier = path.scope.getBindingIdentifier(path.node.id.name);
  const seen: PreviousEnumMembers = ENUMS.get(bindingIdentifier) ?? new Map();

  // Start at -1 so the first enum member is its increment, 0.
  let constValue: number | string | undefined = -1;
  let lastName: string;
  let isPure = true;

  const enumValues: Array<[name: string, value: t.Expression]> = path
    .get("members")
    .map(memberPath => {
      const member = memberPath.node;
      const name = t.isIdentifier(member.id) ? member.id.name : member.id.value;
      const initializerPath = memberPath.get("initializer");
      const initializer = member.initializer;
      let value: t.Expression;
      if (initializer) {
        constValue = computeConstantValue(initializerPath, seen);
        if (constValue !== undefined) {
          seen.set(name, constValue);
          assert(
            typeof constValue === "number" || typeof constValue === "string",
          );
          // We do not use `t.valueToNode` because `Infinity`/`NaN` might refer
          // to a local variable. Even 1/0
          // Revisit once https://github.com/microsoft/TypeScript/issues/55091
          // is fixed. Note: we will have to distinguish between actual
          // infinities and reference  to non-infinite variables names Infinity.
          if (constValue === Infinity || Number.isNaN(constValue)) {

Review comment (suggestion):
This part seems like something we should provide in the scope prototype for Babel 8. The `Scope.prototype.hasBinding` could accept an `upToScope` parameter where the scope search will end.

Code after:
  t: t;
};

function ReferencedIdentifier(
  expr: NodePath<t.Identifier>,
  state: EnumSelfReferenceVisitorState,
) {
  const { seen, path, t } = state;
  const name = expr.node.name;

  if (seen.has(name)) {
    for (
      let curScope = expr.scope;
      curScope !== path.scope;
      curScope = curScope.parent
    ) {
      if (curScope.hasOwnBinding(name)) {
        /* The name is declared inside enum:
        enum Foo {
          A,
          B = (() => {
            const A = 1;
            return A;
          })())
        } */
        return;
      }
    }

    expr.replaceWith(
      t.memberExpression(t.cloneNode(path.node.id), t.cloneNode(expr.node)),
    );
    expr.skip();
  }
}

const enumSelfReferenceVisitor = {
  ReferencedIdentifier,
};

export function translateEnumValues(path: NodePath<t.TSEnumDeclaration>, t: t) {
  const bindingIdentifier = path.scope.getBindingIdentifier(path.node.id.name);
  const seen: PreviousEnumMembers = ENUMS.get(bindingIdentifier) ?? new Map();

  // Start at -1 so the first enum member is its increment, 0.
  let constValue: number | string | undefined = -1;
  let lastName: string;
  let isPure = true;

  const enumValues: Array<[name: string, value: t.Expression]> = path
    .get("members")