facebook/flow · error · InvalidStatementError
Could not find target in array of `${parent.type}.${key}`.
Error message
Could not find target in array of `${parent.type}.${key}`. What it means
Thrown by getAssertedIndex() inside getStatementParent() when the target's parent is a BlockStatement, Program, or SwitchCase but target is not present in the parent's body/consequent array (indexOf returns -1). This means the node is detached or stale: its .parent pointer references a container whose array no longer contains it, usually because the node was already removed or the array was mutated without keeping the parent link in sync. Statement mutations (InsertStatement, RemoveStatement, ReplaceStatementWithMany) require the target to be findable in its container to compute the insertion/removal index.
Source
Thrown at packages/flow-transform/src/transform/mutations/utils/getStatementParent.js:60
for (const key of invalidKeys) {
// $FlowExpectedError[prop-missing]
const value = parentWithType[key];
if (
// $FlowFixMe[invalid-compare]
value === target ||
(Array.isArray(value) && value.includes(target))
) {
throw new InvalidStatementError(
`Attempted to insert a statement into \`${parentWithType.type}.${key}\`.`,
);
}
}
}
function getAssertedIndex(key: string, arr: ReadonlyArray<unknown>): number {
const idx = arr.indexOf(target);
if (idx === -1) {
throw new InvalidStatementError(
`Could not find target in array of \`${parent.type}.${key}\`.`,
);
}
return idx;
}
const parent = target.parent;
const result: StatementParent = (() => {
switch (parent.type) {
case 'IfStatement': {
assertValidStatementLocation(parent, 'test');
const key = parent.consequent === target ? 'consequent' : 'alternate';
return {type: 'single', parent, key};
}
case 'LabeledStatement': {
assertValidStatementLocation(parent, 'label');
return {type: 'single', parent, key: 'body'};View on GitHub (pinned to d1341dac89)
Solutions
- Re-traverse (or re-collect the node references) immediately before applying each mutation so targets are fresh.
- When batching mutations, filter out any mutation whose target is no longer present in its parent array after earlier mutations run.
- Never manually splice body/consequent arrays; express every change as a library mutation so parent links stay consistent.
- Order mutations so that a remove/replace of a node never precedes another mutation that references the same node.
Example fix
// before
const mutations = collectFromTraversal(ast); // stale references
applyAll(mutations); // second mutation targets a node removed by the first
// after
for (const mutation of collectFromTraversal(ast)) {
applyMutation(ast, mutation); // one mutation per pass
ast = reparseOrRetraverse(ast); // refresh references before the next
} Defensive patterns
Strategy: validation
Validate before calling
function isStillInParentArray(node) {
const parent = node.parent;
if (parent == null) return false;
const arr = parent.type === 'SwitchCase' ? parent.consequent : parent.body;
return Array.isArray(arr) && arr.includes(node);
}
const safeMutations = mutations.filter(m => isStillInParentArray(m.node ?? m.target)); Type guard
/** True when the node is still reachable from its declared array container. */
function isAttachedArrayMember(node) {
const parent = node.parent;
if (parent == null) return false;
if (parent.type === 'SwitchCase') return parent.consequent.includes(node);
if (parent.type === 'BlockStatement' || parent.type === 'Program') {
return parent.body.includes(node);
}
return false;
} Try / catch
import {InvalidStatementError} from 'flow-transform/src/transform/Errors';
for (const mutation of mutations) {
try {
applyMutation(ast, mutation);
} catch (err) {
if (err instanceof InvalidStatementError && err.message.startsWith('Could not find target')) {
continue; // target already removed by an earlier mutation; skip
}
throw err;
}
} Prevention
- Apply one mutation per traversal pass and re-collect node references between passes.
- Never splice body/consequent arrays manually; use the mutation API so parent links stay consistent.
- Before applying a queued mutation, re-check that the target is still present in its container.
When it happens
Trigger: Applying a second mutation whose target was already removed by an earlier mutation in the same batch; splicing parent.body manually (body.splice(i, 1)) without clearing node.parent and then calling a mutation on the removed node; holding node references from a traversal done before earlier mutations were applied.
Common situations: Batching multiple mutations computed from a single pre-mutation traversal, where two mutations touch overlapping nodes; mixing manual AST edits with the library's mutation API; reusing node references across transform passes.
Related errors
- Expected to find the target "${target.type}" on the "${resul
- Attempted to insert a statement into `${parentWithType.type}
- Expected to find a valid statement parent, but found a paren
- Invalid Mutation: Tried to mutate an elements array with an
- Expected parent node to be set on "${target.type}"
AI-assisted analysis of facebook/flow@d1341dac89 (2026-08-17).
Data as JSON: /api/errors/34f234df54064d00.
Report an issue: GitHub.