facebook/flow · error · InvalidInsertionError
import/export cannot be inserted into a
Error message
import/export cannot be inserted into a ${insertionParent.parent.type}. What it means
InvalidInsertionError thrown by flow-transform's insertStatement mutation when the nodes being inserted include an import/export (any ModuleDeclaration) but the insertion location cannot legally hold module declarations. `isValidModuleDeclarationParent` only accepts `Program` or a `BlockStatement` whose parent is a `DeclareModule`; anything else (function BlockStatement, SwitchCase consequent, IfStatement branch) is rejected.
Solutions
- Anchor import insertions to a top-level statement: walk up `anchor.parent` until `parent.type === 'Program'` and insert relative to a Program.body statement (e.g. the first existing import).
- If the file has no imports, capture the first `Program.body` statement during a `Program` visit and use it as the anchor.
- Only insert non-module statements relative to nested anchors.
- For `declare module` blocks, a BlockStatement under DeclareModule is also legal.
Example fix
// before - anchor is nested inside a function mutate.insertStatementBefore(usageStatement, buildImport()); // throws // after - hoist the anchor to the top level const topLevel = usageStatement.parent.type === 'Program' ? usageStatement : /* walk up to a Program.body statement */ firstProgramStatement; mutate.insertStatementBefore(topLevel, buildImport());
Defensive patterns
Strategy: type-guard
Validate before calling
function canHoldModuleDeclarations(anchor) {
const p = anchor.parent;
return p.type === 'Program' ||
(p.type === 'BlockStatement' && p.parent?.type === 'DeclareModule');
}
function insertImport(mutate, anchor, importNode) {
if (!canHoldModuleDeclarations(anchor)) {
// hoist: find a Program.body statement instead
let top = anchor;
while (top.parent.type !== 'Program') top = top.parent;
anchor = top;
}
mutate.insertStatementBefore(anchor, importNode);
} Type guard
function isModuleDeclarationAnchor(anchor /*: Statement */): boolean {
const p = anchor.parent;
return p.type === 'Program' ||
(p.type === 'BlockStatement' && p.parent?.type === 'DeclareModule');
} Try / catch
try {
transform(code, visitors);
} catch (e) {
if (e.name === 'InvalidInsertionError' && /import\/export/.test(e.message)) {
// re-anchor the insertion to a Program.body statement
}
throw e;
} Prevention
- Always insert imports relative to a top-level (Program.body) statement.
- Capture a Program-level anchor early (e.g. first import) during the Program visit.
- Nested anchors are only for non-module statements.
When it happens
Trigger: `mutate.insertStatementBefore(anchor, detachedNode<ImportDeclaration>())` where `anchor.parent` is a BlockStatement inside a function, a SwitchCase, or an IfStatement branch - i.e. the anchor statement is not at the top level of the file. Same for inserting ExportNamedDeclaration anywhere but Program level.
Common situations: Codemods that add imports: matching a usage deep in the file (a call inside a function) and inserting the import next to it instead of hoisting to the top; transforming CommonJS requires inside nested scopes.
Related errors
- import/export cannot be replaced into a
- Attempted to insert a statement into
- Cannot perform a remove mutation on node of type
- Could not find target in array of
- Could not find target in array of
AI-assisted analysis of facebook/flow@f88ac94bcf (2026-08-20).
Data as JSON: /api/errors/f9257342882a0f58.
Report an issue: GitHub.
Appendix: source
Thrown at packages/flow-transform/src/transform/mutations/InsertStatement.js:63
export function performInsertStatementMutation(
mutationContext: MutationContext,
mutation: InsertStatementMutation,
): ESNode {
mutationContext.assertNotDeleted(
mutation.target,
`Attempted to insert ${mutation.side} a deleted ${mutation.target.type} node. This likely means that you attempted to mutate around the target after it was deleted/replaced.`,
);
const insertionParent = getStatementParent(mutation.target);
// enforce that if we are inserting module declarations - they are being inserted in a valid location
if (
!isValidModuleDeclarationParent(
insertionParent.parent,
mutation.nodesToInsert,
)
) {
throw new InvalidInsertionError(
`import/export cannot be inserted into a ${insertionParent.parent.type}.`,
);
}
mutationContext.markMutation(insertionParent.parent, insertionParent.key);
if (insertionParent.type === 'array') {
const parent: interface {
[string]: ReadonlyArray<DetachedNode<Statement | ModuleDeclaration>>,
} = insertionParent.parent;
switch (mutation.side) {
case 'before': {
parent[insertionParent.key] = astArrayMutationHelpers.insertInArray(
parent[insertionParent.key],
insertionParent.targetIndex,
mutation.nodesToInsert,
);
break;View on GitHub (pinned to f88ac94bcf)