facebook/flow · error · InvalidInsertionError

import/export cannot be inserted into a ${insertionParent.pa

Error message

import/export cannot be inserted into a ${insertionParent.parent.type}.

What it means

InsertStatement validates insertion sites for module declarations: imports and exports are only legal as direct children of a Program (top level) or of a BlockStatement that is the body of a Flow DeclareModule. isValidModuleDeclarationParent returns false when any node being inserted is an Import or Export declaration and the insertion parent is anything else, and this InvalidInsertionError is the result.

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 d1341dac89)

Solutions

  1. Anchor the insertion on a top-level statement: walk up from the target until parent.type === 'Program'
  2. For DeclareModule bodies, ensure the anchor's parent is the DeclareModule's BlockStatement
  3. If you only need non-module statements inserted, the nested location is fine: drop the import from the batch

Example fix

// before
const target = referencingStmt; // nested in a function body
mutations.push(insertStatement('before', target, [importDecl])); // throws

// after
let top = target;
while (top.parent && 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 canInsertHere(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(insertStatement(side, target, nodes));
} catch (e) {
  if (e.message.includes('import/export cannot be inserted')) {
    // hoist anchor to top level and retry
    let top = target;
    while (top.parent && top.parent.type !== 'Program') top = top.parent;
    mutations.push(insertStatement('before', top, nodes));
  } else throw e;
}

Prevention

When it happens

Trigger: insertStatementMutation('before' or 'after', target, nodes) where any node in nodes is an ImportDeclaration or Export* and the target's statement parent is a BlockStatement (function or if body), SwitchCase, or another non-Program container.

Common situations: Auto-import codemods (e.g. adding an import for an undefined identifier) anchoring on the first statement that references the identifier, which sits inside a function; inserting an export statement inside a nested block.

Related errors


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