facebook/flow · error

Expected parent node to be set on

Error message

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

What it means

The node-mutation helpers locate a node by walking its parent's visitor keys, so the target must have .parent set. getNodeOnParent throws when target.parent is null: the node is detached — raw flow-parser parse() output does not set parent pointers; parents are attached during SimpleTransform traversal.

Solutions

  1. Do mutations inside a SimpleTransform visitor — traversal sets parent pointers, and the visitor hands you nodes with parents attached.
  2. Or set node.parent yourself as you attach children when building/mutating trees manually.
  3. Check `target.parent != null` before calling and fail with your own diagnostic if detached.

Example fix

// before
const ast = parse(code);
replaceNodeOnParent(ast.body[0].expression, newNode); // parent is null -> throws

// after
SimpleTransform.transformProgram(ast, {
  transform(node) {
    if (node.type === 'ThatExpression') return newNode; // parents set by traversal
    return node;
  },
});
Defensive patterns

Strategy: type-guard

Validate before calling

// Ensure parents exist before mutating
function nodesHaveParents(program) {
  let ok = true;
  (function walk(n) {
    if (n == null || typeof n !== 'object') return;
    if (typeof n.type === 'string' && n.parent == null && n !== program) ok = false;
    for (const k of Object.keys(n)) if (k !== 'parent') walk(n[k]);
  })(program);
  return ok;
}

Type guard

function isAttachedToTree(node) {
  return node != null && node.parent != null;
}

Prevention

When it happens

Trigger: Calling replaceNodeOnParent/removeNodeOnParent on a node obtained directly from parse() or constructed by hand, outside a transform traversal, so no parent pointer was ever assigned.

Common situations: Custom AST tooling that parses, then tries to mutate nodes in place without running a transform; tests that build node literals manually; code ported from libraries whose parser sets parents (babel/traverse sets them, flow-parser does not).

Related errors


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

Appendix: 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 f88ac94bcf)