facebook/flow · error · Error
Expected to find the ${target.type} as a direct child of the
Error message
Expected to find the ${target.type} as a direct child of the ${parent.type}. What it means
After confirming target.parent exists, the helper scans every visitor key of the parent looking for the target either as a direct property or inside an array property; if the target is not found anywhere, this 'should never happen' invariant error is thrown. In practice it means the parent pointer is stale: the parent used to hold the node but no longer does, typically because another transform already replaced or removed it.
Source
Thrown at packages/flow-parser/oxidized-src/transform/astNodeMutationHelpers.js:73
} else if (
Array.isArray(
// $FlowExpectedError[prop-missing]
parent[key],
)
) {
for (let i = 0; i < parent[key].length; i += 1) {
// $FlowExpectedError[invalid-tuple-index]
const current = parent[key][i];
// $FlowFixMe[invalid-compare]
if (current === target) {
return {type: 'array', node: parent, key, targetIndex: i};
}
}
}
}
// this shouldn't happen ever
throw new Error(
`Expected to find the ${target.type} as a direct child of the ${parent.type}.`,
);
}
/**
* Replace a node with a new node within an AST (via the parent pointer).
*/
export function replaceNodeOnParent(
originalNode: ESNode,
originalNodeParent: ESNode,
nodeToReplaceWith: ESNode | ReadonlyArray<ESNode>,
visitorKeys?: ?VisitorKeysType,
): void {
const replacementParent = getParentKey(
originalNode,
originalNodeParent,
visitorKeys,
);View on GitHub (pinned to d1341dac89)
Solutions
- Re-parse the source (or re-traverse a pristine copy of the AST) for each independent transform pass
- Never cache node references across transforms; re-find nodes by position or type in each pass
- Do all changes for one node in a single visitor callback instead of multiple passes
Example fix
// before const targets = findNodes(ast, 'Foo'); ast = SimpleTransform.transform(ast, visitorA); mutateAll(targets); // stale references into transformed tree // after ast = SimpleTransform.transform(ast, visitorA); const targets = findNodes(ast, 'Foo'); // re-find on the new tree mutateAll(targets);
Defensive patterns
Strategy: try-catch
Type guard
const isDirectChild = (parent, target, visitorKeys) =>
(visitorKeys[parent.type] || []).some(k =>
parent[k] === target || (Array.isArray(parent[k]) && parent[k].includes(target))
); Try / catch
try {
SimpleTransform.transform(ast, options);
} catch (e) {
if (e.message.includes('as a direct child')) {
// invariant corruption: start over from source instead of patching
ast = parse(originalSource, {});
SimpleTransform.transform(ast, safeOptions);
} else throw e;
} Prevention
- Treat this error as a signal of tree corruption: re-parse rather than continuing
- Do not mutate the AST outside SimpleTransform between passes
- Never cache node references across transform passes; re-find nodes each pass
When it happens
Trigger: Two transforms (or two steps of one codemod) sharing node references, where the first replaced the node on its parent and the second then tries to mutate the same node; an AST mutated directly by hand so that parent arrays no longer contain the nodes they point to.
Common situations: Running multiple SimpleTransform passes while caching node references between them; codemod frameworks that queue up node-level operations and flush them after the tree has already changed.
Related errors
- Expected parent node to be set on "${target.type}"
- SimpleTransform: invalid array result for root node
- SimpleTransform.transformProgram: Expected program node.
- Cannot insert array into non-array parent type: ${parent.typ
- No visitor keys found for node type "${node.type}".
AI-assisted analysis of facebook/flow@d1341dac89 (2026-08-17).
Data as JSON: /api/errors/2c7a081adedd8174.
Report an issue: GitHub.