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

  1. Split the mutation: put module declarations in a separate replace or insert anchored at a top-level statement
  2. Walk up from the target to the first statement whose parent is Program and chain the mutations there
  3. 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

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


AI-assisted analysis of facebook/flow@d1341dac89 (2026-08-17). Data as JSON: /api/errors/e9ffe57b4eabdcd6. Report an issue: GitHub.