Error Handling in TypeScript-Go Code Generation: Generation-Time Validation and Runtime Safety
The typescript-go project implements a dual-layer error strategy that throws JavaScript errors during schema generation while emitting Go code that uses panics for programmer errors and fmt.Errorf returns for recoverable runtime failures.
The microsoft/typescript-go repository transforms TypeScript AST schemas into native Go implementations through code generation. Error handling spans two distinct phases: Node.js scripts that validate the schema before emitting code, and the resulting Go binaries that parse AST nodes at runtime. This architecture separates configuration failures (which abort generation) from runtime behavior (which distinguishes between programming bugs and malformed input).
Generation-Time Validation in Node.js Scripts
The generator script _scripts/generate-go-ast.ts validates kind markers and union definitions before writing any Go source. If a marker references a non-existent kind, the script throws a standard JavaScript Error to abort immediately:
// _scripts/generate-go-ast.ts
// Lines 880-894
if (!definedKinds.has(marker.value) && !markerNames.has(marker.value)) {
throw new Error(
`Kind marker ${marker.name} references undefined kind or marker "${marker.value}"`
);
}
...
if (!definedKinds.has(m) && !api.hasKindAlias(m)) {
throw new Error(
`Kind union ${kindUnion.name} references undefined kind or union "${m}"`
);
}
This aggressive validation ensures that inconsistent schemas never produce invalid Go code. The generation process exits with a non-zero status code, preventing corrupted builds from reaching compilation.
Panic-Based Error Handling in Generated Go Code
For the generated Go implementation in internal/ast/ast_generated.go, the emitter inserts defensive panics for unreachable code paths. When generating Update methods for nodes with multiple aliases, the script writes a default case that panics if an unexpected Kind reaches the method:
// _scripts/generate-go-ast.ts
// Lines 400-406
w.write("default:");
w.push();
w.write(`panic("unexpected kind in Update${node.name}: " + node.Kind.String())`);
w.pop();
These panics indicate generator bugs rather than user errors. If UpdateIdentifier or similar methods receive an incompatible node kind, the program crashes immediately with a descriptive message containing the offending kind value from internal/ast/kind_generated.go. This fail-fast approach prevents silent data corruption during AST manipulation.
Recoverable Runtime Errors with fmt.Errorf
Unlike Update methods, the decoder functions generated in internal/ast/decoder_generated.go return explicit error values for gracefully handling malformed input. The encoder script _scripts/generate-encoder.ts emits code that uses fmt.Errorf when encountering unknown node kinds during deserialization:
// _scripts/generate-encoder.ts
// Lines 64-68 (inside createStringNode)
default:
w.push();
w.write(`return nil, fmt.Errorf("unknown string node kind %v", kind)`);
w.pop();
Similar patterns appear in createExtendedNode and createChildrenNode functions. This allows callers such as the LSP server or CLI tools to capture decoding failures, log them, and continue operation rather than crashing the entire process.
Practical Implementation Examples
The following patterns demonstrate how to interact with these error handling layers in production code.
Catching decoder errors in Go:
node, err := decoder.Decode(data)
if err != nil {
// Propagate the error to the LSP client or log it
log.Printf("AST decode failed: %v", err)
return nil, err
}
Recovering from panics in Update methods:
func safeUpdateIdent(f *ast.NodeFactory, n *ast.Identifier, newName string) (node *ast.Node) {
defer func() {
if r := recover(); r != nil {
log.Printf("UpdateIdentifier panic: %v", r)
// Return the original node to keep the program alive
node = n.AsNode()
}
}()
return f.UpdateIdentifier(n, newName)
}
Running the generator with error handling:
node --experimental-strip-types _scripts/generate-go-ast.ts \
&& node --experimental-strip-types _scripts/generate-encoder.ts
# If any validation error occurs, the script exits with a non-zero code
# because the thrown Error aborts the process.
Summary
- Generation-time validation in
_scripts/generate-go-ast.tsthrows JavaScript Errors for schema inconsistencies, aborting the build before invalid Go code is produced. - Panic statements emitted into
internal/ast/ast_generated.goserve as assertions for impossible code paths in Update methods, surfacing generator bugs immediately. - Recoverable errors in
internal/ast/decoder_generated.gousefmt.Errorfto allow graceful handling of malformed AST input at runtime. - The architecture distinguishes between programming errors (panics) and user input errors (returned errors), following Go idioms for error handling while maintaining strict schema integrity during the build process.
Frequently Asked Questions
Why does typescript-go use panics instead of errors for Update methods?
Update methods in the generated internal/ast/ast_generated.go file use panics because receiving an incompatible node kind represents a programming error in the generator itself, not a runtime condition that users should handle. By panicking with a message containing the unexpected kind value from internal/ast/kind_generated.go, developers can immediately identify when the code generator has produced an incomplete switch statement or missed an alias case during development.
How does the generator handle invalid schema definitions?
The Node.js generator scripts in _scripts/generate-go-ast.ts validate all kind markers and union definitions against the schema before emitting code. If a marker references a non-existent kind at lines 880-894, the script throws a JavaScript Error that aborts the entire generation process with a non-zero exit code, preventing the compilation of invalid Go source files.
Can runtime decoder errors be recovered gracefully?
Yes. The generated decoder functions in internal/ast/decoder_generated.go return explicit error values created with fmt.Errorf when encountering unknown node kinds. This allows callers such as LSP servers or CLI tools to log the malformed input and continue operation, unlike the panic-based handling in Update methods which terminates the process.
Where are the generated error handling patterns located?
The error handling implementations reside in three generated files under internal/ast/: ast_generated.go contains panic guards in Update methods, decoder_generated.go contains error-returning decoder functions, and kind_generated.go provides the Kind enum used in panic messages. These files are produced by _scripts/generate-go-ast.ts and _scripts/generate-encoder.ts during the build process.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →