facebook/flow · error · InvalidStatementError

Could not find target in array of

Error message

Could not find target in array of `${parent.type}.${key}`.

What it means

InvalidStatementError thrown by getStatementParent's `getAssertedIndex` helper. For array-based statement parents (Program.body, BlockStatement.body, SwitchCase.consequent) the anchor statement must be present by identity (`indexOf`) in that array. If the anchor was already removed/replaced by an earlier mutation in the run, or is a stale/detached node object, indexOf returns -1 and this error fires.

Solutions

  1. One mutation per anchor statement per run; insert relative to a statement you have not mutated.
  2. Re-query the current AST (re-traverse) between dependent mutation batches.
  3. Assert `anchor.parent.body.includes(anchor)` before issuing the mutation.

Example fix

// before
mutate.removeNode(stmt);
mutate.insertStatementAfter(stmt, replacement); // stmt no longer in body - throws

// after
mutate.replaceStatementWithMany(stmt, [replacement]);
Defensive patterns

Strategy: validation

Validate before calling

function anchorStillInBody(anchor): boolean {
  const p = anchor.parent;
  const arr = p.type === 'SwitchCase' ? p.consequent : p.body;
  return Array.isArray(arr) && arr.includes(anchor);
}

if (anchorStillInBody(anchor)) {
  mutate.insertStatementBefore(anchor, stmt);
}

Try / catch

try {
  mutate.insertStatementBefore(anchor, stmt);
} catch (e) {
  if (e.name === 'InvalidStatementError' && /Could not find target/.test(e.message)) {
    // anchor was already removed/replaced - re-traverse and pick a live anchor
  } else throw e;
}

Prevention

When it happens

Trigger: insertStatementBefore/After or statement removal anchored on a statement that a previous mutation in the same transform run already deleted or replaced; using a node captured from an earlier traversal.

Common situations: Codemods that both remove statements and insert around them; chained visitors mutating the same BlockStatement body; holding node references across transform passes.

Related errors


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

Appendix: source

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

    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) {
      case 'IfStatement': {
        assertValidStatementLocation(parent, 'test');
        const key = parent.consequent === target ? 'consequent' : 'alternate';
        return {type: 'single', parent, key};
      }

      case 'LabeledStatement': {
        assertValidStatementLocation(parent, 'label');
        return {type: 'single', parent, key: 'body'};

View on GitHub (pinned to f88ac94bcf)