facebook/flow · error · InvalidStatementError
Attempted to insert a statement into `${parentWithType.type}
Error message
Attempted to insert a statement into `${parentWithType.type}.${key}`. What it means
Thrown by getStatementParent() (used by the InsertStatement, RemoveStatement, and ReplaceStatementWithMany mutations) when the target node lives in a non-statement slot of its parent, such as the test of an IfStatement/WhileStatement, the init/test/update of a ForStatement, the label of a LabeledStatement, the object of a WithStatement, the left/right of a ForIn/ForOf, or the test of a SwitchCase. assertValidStatementLocation() checks each of these invalid keys and rejects the mutation because statements cannot be inserted relative to a node that is itself an expression or identifier position. The error names the exact offending slot (e.g. `IfStatement.test`) so you can see where the node actually sits.
Source
Thrown at packages/flow-transform/src/transform/mutations/utils/getStatementParent.js:51
},
>;
export function getStatementParent(
target: ModuleDeclaration | Statement,
): StatementParent {
function assertValidStatementLocation<
T extends Readonly<interface {type: string}>,
>(parentWithType: T, ...invalidKeys: ReadonlyArray<keyof T>): void {
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) {View on GitHub (pinned to d1341dac89)
Solutions
- Pass a node that actually occupies a statement position: a member of Program.body / BlockStatement.body, an IfStatement consequent/alternate, a loop body, or a SwitchCase.consequent entry.
- Before building the mutation, check which property of target.parent holds the target; if it is test/init/update/label/object/left/right, target the enclosing statement instead.
- If you intended to operate on the expression itself, use an expression-level transform (replace the parent statement with a new one containing the desired expression) rather than a statement insertion/removal mutation.
- In a codemod pipeline, type-narrow traversed nodes to Statement/ModuleDeclaration before constructing mutations.
Example fix
// before
const mutation = {
kind: 'remove_statement',
node: ifStatement.test, // BinaryExpression in a non-statement slot
};
// after
const mutation = {
kind: 'remove_statement',
node: ifStatement, // remove the whole statement, or target a body/consequent member
}; Defensive patterns
Strategy: type-guard
Validate before calling
import {FlowVisitorKeys} from 'flow-ast';
const STATEMENT_CONTAINER_SLOTS = {
IfStatement: ['consequent', 'alternate'],
LabeledStatement: ['body'],
WithStatement: ['body'],
DoWhileStatement: ['body'],
WhileStatement: ['body'],
ForStatement: ['body'],
ForInStatement: ['body'],
ForOfStatement: ['body'],
SwitchCase: ['consequent'],
BlockStatement: ['body'],
Program: ['body'],
};
function isStatementPosition(node) {
const parent = node.parent;
if (parent == null) return false;
const slots = STATEMENT_CONTAINER_SLOTS[parent.type];
if (slots == null) return false;
return slots.some(key => {
const v = parent[key];
return v === node || (Array.isArray(v) && v.includes(node));
});
} Type guard
/** True when `node` sits in a statement slot of its parent and statement mutations are safe. */
function isStatementPosition(node) {
const parent = node.parent;
if (parent == null) return false;
switch (parent.type) {
case 'IfStatement':
return parent.consequent === node || parent.alternate === node;
case 'LabeledStatement':
case 'WithStatement':
case 'DoWhileStatement':
case 'WhileStatement':
case 'ForStatement':
case 'ForInStatement':
case 'ForOfStatement':
return parent.body === node;
case 'SwitchCase':
return parent.consequent.includes(node);
case 'BlockStatement':
case 'Program':
return parent.body.includes(node);
default:
return false;
}
} Try / catch
import {InvalidStatementError} from 'flow-transform/src/transform/Errors';
try {
applyMutation(ast, mutation);
} catch (err) {
if (err instanceof InvalidStatementError && /Attempted to insert/.test(err.message)) {
// target is in a non-statement slot; retarget or skip
console.warn('skipping non-statement target:', err.message);
} else {
throw err;
}
} Prevention
- Type-narrow traversal results to Statement/ModuleDeclaration before building statement mutations.
- Log target.parent.type and the owning key when composing mutations during codemod development.
- Keep a mapping of parent type -> valid statement slots and assert against it in tests.
When it happens
Trigger: Creating a mutation whose target is an expression node, e.g. RemoveStatement({node: ifStatement.test}), InsertStatement({target: forStatement.init, ...}), or ReplaceStatementWithMany({target: labeledStatement.label, ...}). Any of these hits assertValidStatementLocation because the target is stored in the parent's test/init/update/label/object/left/right property rather than a statement container.
Common situations: Codemods that traverse to a node via a visitor (e.g. matching BinaryExpression or Identifier) and then feed that node into a statement mutation API; selecting the if-test or loop-condition because it matched first; porting Babel/jscodeshift code where replaceWith() worked on arbitrary nodes and assuming the statement APIs behave the same.
Related errors
- Could not find target in array of `${parent.type}.${key}`.
- Expected to find a valid statement parent, but found a paren
- Expected to find the target "${target.type}" on the "${resul
- 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/7b0b048b4f23119c.
Report an issue: GitHub.