facebook/flow · error · InvalidReplacementError
import/export cannot be replaced into a
Error message
import/export cannot be replaced into a ${replacementParent.parent.type}. What it means
InvalidReplacementError thrown by flow-transform's replaceStatementWithMany when the replacement nodes include an import/export (ModuleDeclaration) but the statement being replaced does not live where module declarations are legal. The same `isValidModuleDeclarationParent` check as insertStatement applies: only `Program` (or a BlockStatement inside `DeclareModule`) may receive module declarations.
Solutions
- Split the operation: replace the nested statement with non-module statements, and insert the import separately relative to a top-level Program.body statement.
- Only replace-with-imports when `stmt.parent.type === 'Program'`.
- Collect imports during traversal and emit them once at Program level.
Example fix
// before mutate.replaceStatementWithMany(nestedStmt, [buildImport(), buildStatement()]); // throws // after mutate.replaceStatementWithMany(nestedStmt, [buildStatement()]); mutate.insertStatementBefore(firstTopLevelStmt, buildImport());
Defensive patterns
Strategy: type-guard
Validate before calling
function canReplaceWithModuleDeclarations(anchor) {
const p = anchor.parent;
return p.type === 'Program' ||
(p.type === 'BlockStatement' && p.parent?.type === 'DeclareModule');
}
if (newNodes.some(isModuleDeclaration) && !canReplaceWithModuleDeclarations(anchor)) {
// split: hoist module declarations to Program level
const stmts = newNodes.filter(n => !isModuleDeclaration(n));
mutate.replaceStatementWithMany(anchor, stmts);
mutate.insertStatementBefore(firstTopLevelStatement, moduleNodes[0]);
} else {
mutate.replaceStatementWithMany(anchor, newNodes);
} Type guard
function isModuleDeclaration(n): boolean {
return ['ImportDeclaration', 'ExportNamedDeclaration',
'ExportDefaultDeclaration', 'ExportAllDeclaration'].includes(n.type);
} Prevention
- Only replace-with-imports at Program level (or DeclareModule blocks).
- Hoist imports: collect them during traversal, emit once against a Program.body anchor.
When it happens
Trigger: `mutate.replaceStatementWithMany(nestedStmt, [detachedImport, ...])` where nestedStmt.parent is a function BlockStatement, SwitchCase consequent, or IfStatement branch - i.e. replacing a non-top-level statement with an import/export.
Common situations: Codemods converting a nested statement to a module import (e.g. replacing an inner require() call's statement with an import); forgetting to hoist when replacing a statement that sits inside a function body.
Related errors
- import/export cannot be inserted 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/e9ffe57b4eabdcd6.
Report an issue: GitHub.
Appendix: source
Thrown at packages/flow-transform/src/transform/mutations/ReplaceStatementWithMany.js:63
nodesToReplaceWith,
keepComments: options?.keepComments ?? false,
};
}
export function performReplaceStatementWithManyMutation(
mutationContext: MutationContext,
mutation: ReplaceStatementWithManyMutation,
): ESNode {
const replacementParent = getStatementParent(mutation.target);
// enforce that if we are replacing with module declarations - they are being inserted in a valid location
if (
!isValidModuleDeclarationParent(
replacementParent.parent,
mutation.nodesToReplaceWith,
)
) {
throw new InvalidReplacementError(
`import/export cannot be replaced into a ${replacementParent.parent.type}.`,
);
}
mutationContext.markDeletion(mutation.target);
mutationContext.markMutation(replacementParent.parent, replacementParent.key);
if (mutation.keepComments) {
// attach comments to the very first replacement node
moveCommentsToNewNode(mutation.target, mutation.nodesToReplaceWith[0]);
}
if (replacementParent.type === 'array') {
const parent: interface {
[string]: ReadonlyArray<DetachedNode<Statement | ModuleDeclaration>>,
} = replacementParent.parent;
parent[replacementParent.key] = astArrayMutationHelpers.replaceInArray(
parent[replacementParent.key],View on GitHub (pinned to f88ac94bcf)