facebook/flow · error · InvalidStatementError
Expected to find a valid statement parent, but found a paren
Error message
Expected to find a valid statement parent, but found a parent of type "${parent.type}". What it means
Thrown by getStatementParent() when the type of target.parent is not one of the supported statement containers (IfStatement, LabeledStatement, WithStatement, DoWhileStatement, WhileStatement, ForStatement, ForInStatement, ForOfStatement, SwitchCase, BlockStatement, Program). The switch falls through and the function rejects the mutation because it cannot determine a statement slot for the target. Typical culprits are parents like ExportNamedDeclaration (a declaration held directly in .declaration), VariableDeclarator (the target is an init expression), or ReturnStatement (the target is an argument).
Source
Thrown at packages/flow-transform/src/transform/mutations/utils/getStatementParent.js:130
type: 'array',
parent,
key: 'consequent',
targetIndex: getAssertedIndex('consequent', parent.consequent),
};
}
case 'BlockStatement':
case 'Program': {
return {
type: 'array',
parent,
key: 'body',
targetIndex: getAssertedIndex('body', parent.body),
};
}
}
throw new InvalidStatementError(
`Expected to find a valid statement parent, but found a parent of type "${parent.type}".`,
);
})();
if (
// array insertions are already validated by the getAssertedIndex function
result.targetIndex == null &&
// $FlowExpectedError[prop-missing]
// $FlowFixMe[invalid-compare]
result.parent[result.key] !== target
) {
throw new InvalidStatementError(
`Expected to find the target "${target.type}" on the "${result.parent.type}.${result.key}", but found a different node. ` +
'This likely means that you attempted to mutate around the target after it was deleted/replaced.',
);
}
return result;View on GitHub (pinned to d1341dac89)
Solutions
- Guard before mutating: only create the mutation when target.parent.type is one of IfStatement, LabeledStatement, WithStatement, DoWhileStatement, WhileStatement, ForStatement, ForInStatement, ForOfStatement, SwitchCase, BlockStatement, Program.
- For exported declarations, target the enclosing ExportNamedDeclaration or restructure the tree so the declaration moves into a BlockStatement body first.
- For expression positions (VariableDeclarator.init, ReturnStatement.argument), replace the enclosing statement with a new one instead of using a statement mutation on the expression.
- Ensure the AST comes from the Flow parser so parent links and node shapes match what the switch handles.
Example fix
// before
const decl = exportedNode.declaration; // parent is ExportNamedDeclaration
const mutation = {kind: 'remove_statement', node: decl}; // throws
// after
const mutation = {
kind: 'remove_statement',
node: exportedNode, // remove the whole export declaration
}; Defensive patterns
Strategy: type-guard
Validate before calling
const STATEMENT_PARENT_TYPES = new Set([
'IfStatement', 'LabeledStatement', 'WithStatement', 'DoWhileStatement',
'WhileStatement', 'ForStatement', 'ForInStatement', 'ForOfStatement',
'SwitchCase', 'BlockStatement', 'Program',
]);
function hasSupportedStatementParent(node) {
return node.parent != null && STATEMENT_PARENT_TYPES.has(node.parent.type);
} Type guard
/** True when the target's parent is a statement container getStatementParent supports. */
function hasSupportedStatementParent(node) {
return (
node.parent != null &&
STATEMENT_PARENT_TYPES.has(node.parent.type) // Set of the 11 supported parent types
);
} Try / catch
import {InvalidStatementError} from 'flow-transform/src/transform/Errors';
try {
applyMutation(ast, mutation);
} catch (err) {
if (err instanceof InvalidStatementError && err.message.includes('valid statement parent')) {
// parent.type not in the supported switch; retarget to an enclosing statement
console.warn('unsupported parent, retargeting:', err.message);
} else {
throw err;
}
} Prevention
- Remember ExportNamedDeclaration.declaration is not a supported slot; target the export node itself.
- Only feed Flow-parser ASTs into flow-transform mutations.
- Keep the supported-parent list next to your codemod's target selection logic and assert it.
When it happens
Trigger: Calling RemoveStatement/InsertStatement/ReplaceStatementWithMany on the FunctionDeclaration inside `export function foo() {}` (its parent is ExportNamedDeclaration, not BlockStatement); passing an expression whose parent is a VariableDeclarator or ReturnStatement; passing a node built by a different parser whose parent chain includes non-Flow node shapes.
Common situations: Codemods that remove or wrap exported declarations without realizing the declaration sits in ExportNamedDeclaration.declaration; feeding hand-built or Babel/TS ASTs into flow-transform mutations; assuming every node with a Statement ancestor can be targeted directly.
Related errors
- Attempted to insert a statement into `${parentWithType.type}
- Could not find target in array of `${parent.type}.${key}`.
- 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/fc349cdc2bab3752.
Report an issue: GitHub.