facebook/flow · error · Error
Expected parent node to be set on "${target.type}"
Error message
Expected parent node to be set on "${target.type}" What it means
The mutation helpers in astNodeMutationHelpers locate where a node is attached by walking its parent's visitor keys; that only works if node.parent is set. This error is thrown when target.parent is null or undefined, i.e. the node is detached (freshly constructed, parsed from JSON) or is the root. The library uses parent pointers everywhere instead of re-walking the tree, so a missing parent is fatal for mutations.
Source
Thrown at packages/flow-parser/oxidized-src/transform/astNodeMutationHelpers.js:42
function getParentKey(
target: ESNode,
parent: ESNode,
visitorKeys?: ?VisitorKeysType,
): Readonly<
| {
type: 'single',
node: ESNode,
key: string,
}
| {
type: 'array',
node: ESNode,
key: string,
targetIndex: number,
},
> {
if (parent == null) {
throw new Error(`Expected parent node to be set on "${target.type}"`);
}
for (const key of getVisitorKeys(parent, visitorKeys)) {
if (
isNode(
// $FlowExpectedError[prop-missing]
parent[key],
)
) {
// $FlowFixMe[invalid-compare]
if (parent[key] === target) {
return {type: 'single', node: parent, key};
}
} else if (
Array.isArray(
// $FlowExpectedError[prop-missing]
parent[key],
)
) {View on GitHub (pinned to d1341dac89)
Solutions
- Run a traversal that sets parent pointers on the whole tree before mutating (the parser and traverse utilities do this for parsed ASTs)
- Only call mutation helpers on nodes obtained from the same parsed or traversed AST in the same pass
- For fresh replacement nodes, use the library's detached-node helpers instead of raw object literals
Example fix
// before
const clone = JSON.parse(JSON.stringify(ast));
replaceNodeOnParent(clone.body[0], clone, newNode); // no parent pointers
// after
const ast2 = parse(source);
traverse(ast2, {enter(n, parent) { n.parent = parent; }});
replaceNodeOnParent(ast2.body[0], ast2, newNode); Defensive patterns
Strategy: type-guard
Validate before calling
function assertAttached(node) {
if (node.parent == null) {
throw new Error('Node ' + node.type + ' is detached; set parent pointers before mutating');
}
} Type guard
const isAttached = (node) => node.parent != null && typeof node.parent.type === 'string';
Try / catch
try {
replaceNodeOnParent(target, target.parent, replacement);
} catch (e) {
if (e.message.includes('Expected parent node to be set')) {
throw new Error('Cannot mutate detached nodes; re-traverse to set parent pointers first');
}
throw e;
} Prevention
- Never JSON round-trip an AST you intend to mutate; parent pointers do not survive
- Run a parent-linking traversal immediately after parsing or cloning before any mutation
- Build replacement nodes with the library's detached-node helpers so links are managed for you
When it happens
Trigger: Calling replaceNodeOnParent or removeNodeOnParent (or SimpleTransform's infra on a hand-built AST) with a node whose parent pointer was never assigned; operating on an AST restored via JSON.parse (parent pointers are not serializable); reusing a node from a previous transform whose links were severed.
Common situations: Deep-cloning an AST to 'safely' modify it and thereby stripping parent links; building replacement nodes and immediately trying to mutate them; mixing hand-constructed nodes into a parsed tree without running a parent-linking traversal first.
Related errors
- Expected to find the ${target.type} as a direct child of the
- SimpleTransform: invalid array result for root node
- SimpleTransform.transformProgram: Expected program node.
- Invalid Mutation: Tried to mutate an elements array with an
- Cannot insert array into non-array parent type: ${parent.typ
AI-assisted analysis of facebook/flow@d1341dac89 (2026-08-17).
Data as JSON: /api/errors/252a09718a65e6b8.
Report an issue: GitHub.