facebook/flow · error · InvalidReplacementError
import/export cannot be replaced into a ${replacementParent.
Error message
import/export cannot be replaced into a ${replacementParent.parent.type}. What it means
ReplaceStatementWithMany is the 'replace one statement with many nodes' mutation, and it runs the same module-declaration placement check as insertion: if any replacement node is an Import or Export declaration, the replaced statement's parent must be a Program (or a BlockStatement that is a DeclareModule body). Otherwise it throws InvalidReplacementError naming the offending parent type, because an import or export inside a nested block is not legal JavaScript.
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 d1341dac89)
Solutions
- Split the mutation: put module declarations in a separate replace or insert anchored at a top-level statement
- Walk up from the target to the first statement whose parent is Program and chain the mutations there
- Drop the import and export nodes from the replacement batch if the nested location must be kept
Example fix
// before
mutations.push(replaceWithMany(nestedStmt, [importDecl, callStmt])); // throws
// after
mutations.push(replaceWithMany(nestedStmt, [callStmt]));
let top = nestedStmt;
while (top.parent.type !== 'Program') top = top.parent;
mutations.push(insertStatement('before', top, [importDecl])); Defensive patterns
Strategy: validation
Validate before calling
const MODULE_DECLS = new Set(['ImportDeclaration', 'ExportNamedDeclaration', 'ExportDefaultDeclaration', 'ExportAllDeclaration']);
function canReplaceHere(target, nodes) {
const p = target.parent;
const parentOk = p.type === 'Program' ||
(p.type === 'BlockStatement' && p.parent && p.parent.type === 'DeclareModule');
const hasModuleDecl = nodes.some(n => MODULE_DECLS.has(n.type));
return !hasModuleDecl || parentOk;
} Type guard
const isModuleDeclaration = (n) => n.type === 'ImportDeclaration' || n.type === 'ExportNamedDeclaration' || n.type === 'ExportDefaultDeclaration' || n.type === 'ExportAllDeclaration';
Try / catch
try {
mutations.push(replaceWithMany(target, nodes));
} catch (e) {
if (e.message.includes('import/export cannot be replaced')) {
// split: local statements replace in place, module declarations hoist to top level
mutations.push(replaceWithMany(target, nodes.filter(n => !isModuleDeclaration(n))));
mutations.push(insertStatement('before', topLevelOf(target), nodes.filter(isModuleDeclaration)));
} else throw e;
} Prevention
- Only include import and export nodes in replaceWithMany batches anchored at top-level statements
- Check target.parent.type === 'Program' before issuing module-declaration replacements
- Split mixed batches: local statements replace in place, module declarations hoist to Program.body
When it happens
Trigger: replaceWithMany(target, [importDecl, ...rest]) where target is a statement nested in a function body, if-block, or SwitchCase: getStatementParent(target).parent.type is anything but Program or a DeclareModule's BlockStatement.
Common situations: Codemods that rewrite a statement into an 'import + call + export' bundle and fire on nested statements; export-generation transforms anchored on the last statement of a function instead of the module.
Related errors
- import/export cannot be inserted into a ${insertionParent.pa
- Expected to find the ${target.type} as a direct child of the
- ImportDeclaration should appear when the mode is ES6 and in
- Cannot insert array into non-array parent type: ${parent.typ
- Attempted to mutate a `${node.type}.${key}` on a deleted nod
AI-assisted analysis of facebook/flow@d1341dac89 (2026-08-17).
Data as JSON: /api/errors/e9ffe57b4eabdcd6.
Report an issue: GitHub.