facebook/flow · error · Error

Expected parent node to be set on "${target.type}"

Error message

Expected parent node to be set on "${target.type}"

What it means

The mutation helpers in astNodeMutationHelpers locate where a node is attached by walking its parent's visitor keys; that only works if node.parent is set. This error is thrown when target.parent is null or undefined, i.e. the node is detached (freshly constructed, parsed from JSON) or is the root. The library uses parent pointers everywhere instead of re-walking the tree, so a missing parent is fatal for mutations.

Source

Thrown at packages/flow-parser/oxidized-src/transform/astNodeMutationHelpers.js:42

function getParentKey(
  target: ESNode,
  parent: ESNode,
  visitorKeys?: ?VisitorKeysType,
): Readonly<
  | {
      type: 'single',
      node: ESNode,
      key: string,
    }
  | {
      type: 'array',
      node: ESNode,
      key: string,
      targetIndex: number,
    },
> {
  if (parent == null) {
    throw new Error(`Expected parent node to be set on "${target.type}"`);
  }
  for (const key of getVisitorKeys(parent, visitorKeys)) {
    if (
      isNode(
        // $FlowExpectedError[prop-missing]
        parent[key],
      )
    ) {
      // $FlowFixMe[invalid-compare]
      if (parent[key] === target) {
        return {type: 'single', node: parent, key};
      }
    } else if (
      Array.isArray(
        // $FlowExpectedError[prop-missing]
        parent[key],
      )
    ) {

View on GitHub (pinned to d1341dac89)

Solutions

  1. Run a traversal that sets parent pointers on the whole tree before mutating (the parser and traverse utilities do this for parsed ASTs)
  2. Only call mutation helpers on nodes obtained from the same parsed or traversed AST in the same pass
  3. For fresh replacement nodes, use the library's detached-node helpers instead of raw object literals

Example fix

// before
const clone = JSON.parse(JSON.stringify(ast));
replaceNodeOnParent(clone.body[0], clone, newNode); // no parent pointers

// after
const ast2 = parse(source);
traverse(ast2, {enter(n, parent) { n.parent = parent; }});
replaceNodeOnParent(ast2.body[0], ast2, newNode);
Defensive patterns

Strategy: type-guard

Validate before calling

function assertAttached(node) {
  if (node.parent == null) {
    throw new Error('Node ' + node.type + ' is detached; set parent pointers before mutating');
  }
}

Type guard

const isAttached = (node) => node.parent != null && typeof node.parent.type === 'string';

Try / catch

try {
  replaceNodeOnParent(target, target.parent, replacement);
} catch (e) {
  if (e.message.includes('Expected parent node to be set')) {
    throw new Error('Cannot mutate detached nodes; re-traverse to set parent pointers first');
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling replaceNodeOnParent or removeNodeOnParent (or SimpleTransform's infra on a hand-built AST) with a node whose parent pointer was never assigned; operating on an AST restored via JSON.parse (parent pointers are not serializable); reusing a node from a previous transform whose links were severed.

Common situations: Deep-cloning an AST to 'safely' modify it and thereby stripping parent links; building replacement nodes and immediately trying to mutate them; mixing hand-constructed nodes into a parsed tree without running a parent-linking traversal first.

Related errors


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