How to Handle TypeScript Enums with typescript-go: Complete Compiler Pipeline Guide

The microsoft/typescript-go compiler transforms TypeScript enum declarations into runtime-compatible output through a five-stage pipeline involving parsing, AST generation, runtime transformation, encoding, and printing.

The microsoft/typescript-go project is a ground-up Go port of the TypeScript compiler that parses TypeScript source into an internal AST before code generation. When handling TypeScript enums, the compiler implements the same JavaScript runtime semantics as the original TypeScript compiler but uses Go-based internal structures and transformations.

Understanding the Enum Transformation Pipeline

TypeScript enums undergo a complete transformation pipeline from raw syntax to executable output. Each stage utilizes specific files and functions within the typescript-go repository.

Parsing TypeScript Enum Declarations

The process begins in internal/parser/parser.go, where the Parser.parseEnumDeclaration method consumes the enum keyword and constructs an AST node.

// internal/parser/parser.go (lines 2131-2147)
func (p *Parser) parseEnumDeclaration(pos int, jsdoc jsdocScannerInfo, modifiers *ast.ModifierList) *ast.Node {
    p.parseExpected(ast.KindEnumKeyword)
    name := p.parseIdentifier()
    // parse members inside `{ … }`
    members := p.parseDelimitedList(PCEnumMembers, (*Parser).parseEnumMember)
    return p.finishNode(p.factory.NewEnumDeclaration(modifiers, name, members), pos)
}

This function returns an *ast.EnumDeclaration containing the modifiers, identifier name, and an *EnumMemberList representing the individual enum members.

Generated AST Structure

The concrete Go struct for enum declarations is automatically generated by _scripts/generate-go-ast.ts and defined in internal/ast/ast_generated.go (lines 2541-2550).

// internal/ast/ast_generated.go (excerpt)
type EnumDeclaration struct {
    DeclarationBase
    StatementBase
    ExportableBase
    ModifiersBase
    CompositeBase
    name    *IdentifierNode
    Members *EnumMemberList
}

You should never manually edit this struct. The generator creates Go types that mirror the TypeScript compiler's internal AST structure, ensuring type safety and consistency across the codebase.

Runtime Syntax Transformation

The critical transformation logic resides in internal/transformers/tstransforms/runtimesyntax.go (lines 190-210). The RuntimeSyntaxTransformer emits the classic Immediately Invoked Function Expression (IIFE) pattern used by TypeScript to create the runtime enum object.

The transformEnumDeclaration method constructs the variable declaration and IIFE:

// internal/transformers/tstransforms/runtimesyntax.go (excerpt)
func (tx *RuntimeSyntaxTransformer) transformEnumDeclaration(node *ast.EnumDeclaration) *ast.Node {
    // 1. Emit a `var <EnumName>;` declaration
    // 2. Emit an IIFE that populates members
    // 3. Return the combined statement list
}

The helper getEnumQualifiedElement (in the same file) builds qualified property references for reverse mappings:

func (tx *RuntimeSyntaxTransformer) getEnumQualifiedElement(enum *ast.EnumDeclaration, member *ast.EnumMember) *ast.Expression {
    prop := tx.getNamespaceQualifiedElement(
        tx.getNamespaceContainerName(enum.AsNode()),
        tx.getExpressionForPropertyName(member),
    )
    tx.EmitContext().AddEmitFlags(prop, printer.EFNoComments|printer.EFNoNestedComments|
        printer.EFNoSourceMap|printer.EFNoNestedSourceMaps)
    return prop
}

This produces the characteristic reverse mapping pattern where Color["Red"] = 0 and Color[0] = "Red" are both established.

Language Service Encoding

For incremental compilation and language service features, enum nodes are serialized in internal/api/encoder/encoder_generated.go (lines 143-144). This ensures that enum metadata persists across compilation sessions for features like IntelliSense and go-to-definition.

Final Code Generation

The Printer phase in internal/printer/printer.go (lines 3734-3744) handles the actual output generation using emitEnumDeclaration:

// internal/printer/printer.go (excerpt)
func (p *Printer) emitEnumDeclaration(node *ast.EnumDeclaration) {
    state := p.enterNode(node.AsNode())
    p.emitModifierList(node.AsNode(), node.Modifiers(), false)
    p.writeKeyword("enum")
    p.writeSpace()
    p.emitBindingIdentifier(node.Name().AsIdentifier())
    p.writeSpace()
    p.writePunctuation("{")
    p.emitList((*Printer).emitEnumMemberNode, node.AsNode(), node.Members, LFEnumMembers)
    p.writePunctuation("}")
    p.exitNode(node.AsNode(), state)
}

The printer outputs the original enum syntax for declaration files, while the transformed IIFE statements (produced by the RuntimeSyntaxTransformer) are printed by the standard statement emitter for the runtime output.

Querying Enum Values in Generated Code

When working with the compiled output programmatically, you can introspect enum values by traversing the AST using utility functions. This is useful for custom code generators or analysis tools built on top of typescript-go:

// Example: find the numeric value of a generated enum member
func EnumMemberValue(enumName, member string, src *ast.SourceFile) (int, bool) {
    var val int
    found := false
    ast.ForEachChild(src, func(n *ast.Node) bool {
        if ast.IsEnumDeclaration(n) && n.Name().Text() == enumName {
            for i := 0; i < n.Members.Length(); i++ {
                m := n.Members.Get(i)
                if m.Name().Text() == member {
                    if lit := m.Initializer(); lit != nil && ast.IsNumericLiteral(lit) {
                        fmt.Sscanf(lit.Text(), "%d", &val)
                        found = true
                        return false
                    }
                }
            }
        }
        return true
    })
    return val, found
}

This pattern allows Go-based tooling to extract specific enum metadata from the compiled TypeScript source without parsing the text manually.

Compiler Option Enum Maps

Beyond source-code enums, typescript-go uses enum maps for command-line options (such as --watchFile) in internal/tsoptions/enummaps.go. These utilize the OrderedMap pattern to map string values to their corresponding enum types:

// internal/tsoptions/enummaps.go (excerpt)
var watchFileEnumMap = collections.NewOrderedMapFromList([]collections.MapEntry[string, any]{
    {Key: "watchFile", Value: "WatchFileKind"},
    // …
})

When extending the compiler with new command-line options that require enum values, you add entries to the appropriate map in this file, and the parser automatically handles string-to-value conversion.

Summary

  • Parsing: The Parser.parseEnumDeclaration function in internal/parser/parser.go creates EnumDeclaration nodes from TypeScript source.
  • AST Generation: Go structs for enums are auto-generated in internal/ast/ast_generated.go via _scripts/generate-go-ast.ts.
  • Transformation: RuntimeSyntaxTransformer in runtimesyntax.go emits the IIFE pattern that implements JavaScript enum semantics.
  • Encoding: encoder_generated.go serializes enum nodes for language service persistence.
  • Printing: Printer.emitEnumDeclaration in printer.go outputs the final syntax.
  • Introspection: Use ast.ForEachChild with type guards like ast.IsEnumDeclaration to query enum values programmatically.

Frequently Asked Questions

How does typescript-go preserve TypeScript enum runtime behavior?

The RuntimeSyntaxTransformer implements the same IIFE pattern used by the original TypeScript compiler. By emitting var declarations followed by immediately invoked function expressions that assign both forward and reverse mappings (e.g., Color["Red"] = 0 and Color[0] = "Red"), the generated output maintains JavaScript-compatible enum semantics, including support for computed values and reverse lookups.

Where is the enum AST structure defined in the source code?

The EnumDeclaration struct is defined in internal/ast/ast_generated.go (lines 2541-2550). This file is machine-generated by _scripts/generate-go-ast.ts and should not be manually modified. The struct embeds several base types (DeclarationBase, StatementBase, ExportableBase) to provide common AST node functionality.

Can I extract specific enum values from the AST using Go code?

Yes. Use the ast.ForEachChild function to traverse the *ast.SourceFile, checking for ast.IsEnumDeclaration nodes. Iterate through the Members field (type *EnumMemberList) and inspect Initializer() nodes for numeric literals or string literals to extract the compiled values.

How are compiler option enums different from source code enums?

Compiler option enums in internal/tsoptions/enummaps.go are mapping structures used for parsing command-line arguments, while source code enums represent TypeScript constructs being compiled. The former use OrderedMap instances to map CLI string flags to internal constants, whereas the latter follow the full parsing-to-printing pipeline described in this guide.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →