facebook/flow · error · InvalidStatementError

Attempted to insert a statement into

Error message

Attempted to insert a statement into `${parentWithType.type}.${key}`.

What it means

InvalidStatementError thrown by getStatementParent's `assertValidStatementLocation` helper. Statement mutations (insertStatement*/remove of statements) resolve where the anchor statement lives; for parent types like IfStatement, ForStatement, LabeledStatement, etc. the code first asserts the anchor is NOT sitting in a non-statement slot such as `IfStatement.test`, `ForStatement.init`, `LabeledStatement.label`, or `ForInStatement.left/right`. If you passed one of those non-statement nodes as the anchor, this error names the offending `ParentType.key`.

Solutions

  1. Only use statement mutations on actual Statement/ModuleDeclaration nodes.
  2. From an expression node, walk up parents until you reach a node whose type ends in 'Statement' or is a Declaration, then anchor on that.
  3. For expressions, use replaceNode instead of statement insertions.

Example fix

// before
mutate.insertStatementBefore(callNode.parent.test, stmt); // anchor in IfStatement.test - throws

// after - walk up to the real statement
let anchor = callNode;
while (anchor.parent && !/Statement|Declaration$/.test(anchor.parent.type) === false) { /* ... */ }
mutate.insertStatementBefore(enclosingStatement, stmt);
Defensive patterns

Strategy: type-guard

Validate before calling

const NON_STATEMENT_SLOTS = new Map([
  ['IfStatement', ['test']], ['ForStatement', ['init', 'test', 'update']],
  ['LabeledStatement', ['label']], ['WithStatement', ['object']],
  ['WhileStatement', ['test']], ['DoWhileStatement', ['test']],
  ['ForInStatement', ['left', 'right']], ['ForOfStatement', ['left', 'right']],
]);

function occupiesStatementSlot(node): boolean {
  const bad = NON_STATEMENT_SLOTS.get(node.parent.type) ?? [];
  const inBadSlot = bad.some(k => node.parent[k] === node);
  return !inBadSlot && /Statement|Declaration$/.test(node.type);
}

if (occupiesStatementSlot(anchor)) mutate.insertStatementBefore(anchor, stmt);

Type guard

function isStatementNode(node): boolean {
  return typeof node.type === 'string' &&
    (/Statement$/.test(node.type) || /Declaration$/.test(node.type));
}

Prevention

When it happens

Trigger: Calling `mutate.insertStatementBefore(ifStatement.test, ...)` or otherwise using an expression node (the anchor's parent holds it in a test/init/label/left/right/object slot) where a Statement is required.

Common situations: Visitors that match expression nodes (CallExpression, Identifier) and then call a statement mutation relative to `node` or `node.parent` without checking that the node actually occupies a statement slot.

Related errors


AI-assisted analysis of facebook/flow@f88ac94bcf (2026-08-20). Data as JSON: /api/errors/7b0b048b4f23119c. Report an issue: GitHub.

Appendix: source

Thrown at packages/flow-transform/src/transform/mutations/utils/getStatementParent.js:51

    },
>;

export function getStatementParent(
  target: ModuleDeclaration | Statement,
): StatementParent {
  function assertValidStatementLocation<
    T extends Readonly<interface {type: string}>,
  >(parentWithType: T, ...invalidKeys: ReadonlyArray<keyof T>): void {
    for (const key of invalidKeys) {
      // $FlowExpectedError[prop-missing]
      const value = parentWithType[key];

      if (
        // $FlowFixMe[invalid-compare]
        value === target ||
        (Array.isArray(value) && value.includes(target))
      ) {
        throw new InvalidStatementError(
          `Attempted to insert a statement into \`${parentWithType.type}.${key}\`.`,
        );
      }
    }
  }
  function getAssertedIndex(key: string, arr: ReadonlyArray<unknown>): number {
    const idx = arr.indexOf(target);
    if (idx === -1) {
      throw new InvalidStatementError(
        `Could not find target in array of \`${parent.type}.${key}\`.`,
      );
    }
    return idx;
  }

  const parent = target.parent;
  const result: StatementParent = (() => {
    switch (parent.type) {

View on GitHub (pinned to f88ac94bcf)