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

  1. 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).
  2. If the file has no imports, capture the first `Program.body` statement during a `Program` visit and use it as the anchor.
  3. Only insert non-module statements relative to nested anchors.
  4. 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

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


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)