facebook/flow · error
SimpleTransform.transformProgram: Expected program node.
Error message
SimpleTransform.transformProgram: Expected program node.
What it means
SimpleTransform.transformProgram wraps SimpleTransform.transform and requires the result to be a node with type 'Program'. If the visitor removed the Program (returned null) or replaced it with a different node type, the wrapper throws because downstream code assumes a Program shape (body, etc.).
Solutions
- Guard deletion/replacement logic so the Program node is never removed: if (node.type === 'Program') return node;
- If you must rebuild the root, return a valid Program node (type: 'Program' with a body array).
- Use SimpleTransform.transform (which allows null) instead of transformProgram when root removal is intentional, and handle null yourself.
Example fix
// before
transform(node) {
if (shouldDelete(node)) return null; // shouldDelete matches Program -> throws
}
// after
transform(node) {
if (node.type !== 'Program' && shouldDelete(node)) return null;
return node;
} Defensive patterns
Strategy: validation
Validate before calling
// Ensure the visitor never nulls the root
function guarded(transform) {
return (node) => (node.type === 'Program' ? node : transform(node));
}
const out = SimpleTransform.transformProgram(program, {transform: guarded(myTransform)}); Type guard
function isProgramNode(v) {
return v != null && typeof v === 'object' && v.type === 'Program';
} Prevention
- Make delete-predicates exclude Program by construction.
- Use SimpleTransform.transform and handle a null result yourself when root removal is intentional.
When it happens
Trigger: SimpleTransform.transformProgram(program, {transform}) with a visitor that returns null for the Program node or converts it to another node type (e.g. wrapping into a custom File node, or returning a body statement by mistake).
Common situations: Visitors meant to delete nodes that match too broadly (e.g. a delete-if predicate that also matches Program); refactorings that rebuild the root as a different structure; chained transforms where an earlier step already replaced the root.
Related errors
- SimpleTransform: invalid array result for root node
- Cannot insert array into non-array parent type
- Expected parent node to be set on
- flowImportTo: Unexpected transform result.
- Invalid Mutation: Tried to mutate an elements array with an…
AI-assisted analysis of facebook/flow@f88ac94bcf (2026-08-20).
Data as JSON: /api/errors/c0678ffb39d7f698.
Report an issue: GitHub.
Appendix: source
Thrown at packages/flow-parser/oxidized-src/transform/SimpleTransform.js:147
/**
* Transform the given AST tree.
* @param node The root node to traverse.
* @param options The option object.
*/
static transform(node: ESNode, options: TransformOptions): ESNode | null {
return new SimpleTransform().transform(node, options);
}
static transformProgram(
program: Program,
options: TransformOptions,
): Program {
const result = SimpleTransform.transform(program, options);
if (result?.type === 'Program') {
return result;
}
throw new Error('SimpleTransform.transformProgram: Expected program node.');
}
/**
* Return a new AST node with the given properties overrided if needed.
*
* This function takes care to only create new nodes when needed. Referential equality of nodes
* is important as its used to know if a node should be re-traversed.
*
* @param node The base AST node.
* @param overrideProps New properties to apply to the node.
* @return Either the orginal node if the properties matched the existing node or a new node with
* the new properties.
*/
static nodeWith<T extends ESNode>(
node: T,
overrideProps: Partial<T>,
visitorKeys?: VisitorKeysType,
): T {View on GitHub (pinned to f88ac94bcf)