facebook/flow · error · InvalidStatementError

Expected to find the target

Error message

Expected to find the target "${target.type}" on the "${result.parent.type}.${result.key}", but found a different node. This likely means that you attempted to mutate around the target after it was deleted/replaced.

What it means

InvalidStatementError thrown by getStatementParent's final identity check: for single-key parents (e.g. `IfStatement.consequent`, `LabeledStatement.body`, loop bodies) it verifies `parent[key] === target`. If a previous mutation in the same run already replaced that slot, the slot now holds a different node and the error explicitly hints that you mutated around a target after it was deleted/replaced.

Solutions

  1. Anchor follow-up mutations on the replacement node you installed, not the original.
  2. Issue at most one statement mutation per single-child slot per run.
  3. When both a replace and insert are needed, use one replaceStatementWithMany with the full final statement list.

Example fix

// before
mutate.replaceNode(ifStmt.consequent, newBlock);
mutate.insertStatementBefore(oldConsequent, extra); // slot now holds newBlock - throws

// after
mutate.replaceStatementWithMany(ifStmt.consequent, [extra, ...oldConsequentBody]);
Defensive patterns

Strategy: validation

Validate before calling

function slotStillHoldsTarget(anchor, key): boolean {
  return anchor.parent[key] === anchor;
}

if (slotStillHoldsTarget(anchor, 'consequent')) {
  mutate.insertStatementBefore(anchor, stmt);
}

Try / catch

try {
  mutate.insertStatementBefore(anchor, stmt);
} catch (e) {
  if (e.name === 'InvalidStatementError' && /found a different node/.test(e.message)) {
    // slot was replaced earlier in this run - anchor on the replacement instead
  } else throw e;
}

Prevention

When it happens

Trigger: replaceNode(ifStmt.consequent, x) followed by insertStatementBefore(originalConsequent, ...) in the same run; any second statement mutation anchored on a single-child slot that an earlier replace already swapped.

Common situations: Chained codemod visitors both targeting the same if-branch or loop body; inserting around a statement that a previous visitor replaced with a block.

Related errors


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

Appendix: source

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

          key: 'body',
          targetIndex: getAssertedIndex('body', parent.body),
        };
      }
    }

    throw new InvalidStatementError(
      `Expected to find a valid statement parent, but found a parent of type "${parent.type}".`,
    );
  })();

  if (
    // array insertions are already validated by the getAssertedIndex function
    result.targetIndex == null &&
    // $FlowExpectedError[prop-missing]
    // $FlowFixMe[invalid-compare]
    result.parent[result.key] !== target
  ) {
    throw new InvalidStatementError(
      `Expected to find the target "${target.type}" on the "${result.parent.type}.${result.key}", but found a different node. ` +
        'This likely means that you attempted to mutate around the target after it was deleted/replaced.',
    );
  }

  return result;
}

View on GitHub (pinned to f88ac94bcf)