function visitor()

in src/enum_transformer.ts [96:225]


    function visitor<T extends ts.Node>(node: T): T|ts.Node[] {
      if (!ts.isEnumDeclaration(node)) return ts.visitEachChild(node, visitor, context);

      // TODO(martinprobst): The enum transformer does not work for enums embedded in namespaces,
      // because TS does not support splitting export and declaration ("export {Foo};") in
      // namespaces. tsickle's emit for namespaces is unintelligible for Closure in any case, so
      // this is left to fix for another day.
      if (isInNamespace(node)) return ts.visitEachChild(node, visitor, context);

      // TypeScript does not emit any code for ambient enums, so early exit here to prevent the code
      // below from producing runtime values for an ambient structure.
      if (isAmbient(node)) return ts.visitEachChild(node, visitor, context);

      const isExported = hasModifierFlag(node, ts.ModifierFlags.Export);
      const enumType = getEnumType(typeChecker, node);

      const values: ts.PropertyAssignment[] = [];
      let enumIndex = 0;
      for (const member of node.members) {
        let enumValue: ts.Expression;
        if (member.initializer) {
          const enumConstValue = typeChecker.getConstantValue(member);
          if (typeof enumConstValue === 'number') {
            enumIndex = enumConstValue + 1;
            enumValue = ts.createLiteral(enumConstValue);
          } else if (typeof enumConstValue === 'string') {
            // tsickle does not care about string enum values. However TypeScript expects compile
            // time constant enum values to be replaced with their constant expression, and e.g.
            // doesn't emit imports for modules referenced in them. Because tsickle replaces the
            // enum with an object literal, i.e. handles the enum transform, it must thus also do
            // the const value substitution for strings.
            enumValue = ts.createLiteral(enumConstValue);
          } else {
            // Non-numeric enum value (string or an expression).
            // Emit this initializer expression as-is.
            // Note: if the member's initializer expression refers to another
            // value within the enum (e.g. something like
            //   enum Foo {
            //     Field1,
            //     Field2 = Field1 + something(),
            //   }
            // Then when we emit the initializer we produce invalid code because
            // on the Closure side the reference to Field1 has to be namespaced,
            // e.g. written "Foo.Field1 + something()".
            // Hopefully this doesn't come up often -- if the enum instead has
            // something like
            //     Field2 = Field1 + 3,
            // then it's still a constant expression and we inline the constant
            // value in the above branch of this "if" statement.
            enumValue = visitor(member.initializer) as ts.Expression;
          }
        } else {
          enumValue = ts.createLiteral(enumIndex);
          enumIndex++;
        }
        values.push(ts.setOriginalNode(
            ts.setTextRange(ts.createPropertyAssignment(member.name, enumValue), member), member));
      }

      const varDecl = ts.createVariableDeclaration(
          node.name, /* type */ undefined,
          ts.createObjectLiteral(
              ts.setTextRange(ts.createNodeArray(values, true), node.members), true));
      const varDeclStmt = ts.setOriginalNode(
          ts.setTextRange(
              ts.createVariableStatement(
                  /* modifiers */ undefined,
                  ts.createVariableDeclarationList(
                      [varDecl],
                      /* create a const var */ ts.NodeFlags.Const)),
              node),
          node);
      const comment: ts.SynthesizedComment = {
        kind: ts.SyntaxKind.MultiLineCommentTrivia,
        text: `* @enum {${enumType}} `,
        hasTrailingNewLine: true,
        pos: -1,
        end: -1
      };
      ts.setSyntheticLeadingComments(varDeclStmt, [comment]);

      const name = node.name.getText();
      const resultNodes: ts.Node[] = [varDeclStmt];
      if (isExported) {
        // Create a separate export {...} statement, so that the enum name can be used in local
        // type annotations within the file.
        resultNodes.push(ts.createExportDeclaration(
            undefined, undefined,
            ts.createNamedExports([ts.createExportSpecifier(
                /* isTypeOnly */ false, undefined, name)])));
      }

      if (hasModifierFlag(node, ts.ModifierFlags.Const)) {
        // By TypeScript semantics, const enums disappear after TS compilation.
        // We still need to generate the runtime value above to make Closure Compiler's type system
        // happy and allow refering to enums from JS code, but we should at least not emit string
        // value mappings.
        return resultNodes;
      }

      // Emit the reverse mapping of foo[foo.BAR] = 'BAR'; lines for number enum members
      for (const member of node.members) {
        const memberName = member.name;
        const memberType = getEnumMemberType(typeChecker, member);
        // Enum members cannot be named with a private identifier, although it
        // is technically valid in the AST.
        if (memberType !== 'number' || ts.isPrivateIdentifier(memberName)) {
          continue;
        }

        // TypeScript enum members can have Identifier names or String names.
        // We need to emit slightly different code to support these two syntaxes:
        let nameExpr: ts.Expression;
        let memberAccess: ts.Expression;
        if (ts.isIdentifier(memberName)) {
          // Foo[Foo.ABC] = "ABC";
          nameExpr = createSingleQuoteStringLiteral(memberName.text);
          // Make sure to create a clean, new identifier, so comments do not get emitted twice.
          const ident = ts.createIdentifier(getIdentifierText(memberName));
          memberAccess = ts.createPropertyAccess(ts.createIdentifier(name), ident);
        } else {
          // Foo[Foo["A B C"]] = "A B C"; or Foo[Foo[expression]] = expression;
          nameExpr = ts.isComputedPropertyName(memberName) ? memberName.expression : memberName;
          memberAccess = ts.createElementAccess(ts.createIdentifier(name), nameExpr);
        }
        resultNodes.push(ts.createStatement(ts.createAssignment(
            ts.createElementAccess(ts.createIdentifier(name), memberAccess), nameExpr)));
      }
      return resultNodes;
    }