{"record":{"id":"252a09718a65e6b8","repo":"facebook/flow","slug":"expected-parent-node-to-be-set-on-target-type","errorCode":null,"errorMessage":"Expected parent node to be set on \"${target.type}\"","messagePattern":"Expected parent node to be set on \"(.+?)\"","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/flow-parser/oxidized-src/transform/astNodeMutationHelpers.js","lineNumber":42,"sourceCode":"function getParentKey(\n  target: ESNode,\n  parent: ESNode,\n  visitorKeys?: ?VisitorKeysType,\n): Readonly<\n  | {\n      type: 'single',\n      node: ESNode,\n      key: string,\n    }\n  | {\n      type: 'array',\n      node: ESNode,\n      key: string,\n      targetIndex: number,\n    },\n> {\n  if (parent == null) {\n    throw new Error(`Expected parent node to be set on \"${target.type}\"`);\n  }\n  for (const key of getVisitorKeys(parent, visitorKeys)) {\n    if (\n      isNode(\n        // $FlowExpectedError[prop-missing]\n        parent[key],\n      )\n    ) {\n      // $FlowFixMe[invalid-compare]\n      if (parent[key] === target) {\n        return {type: 'single', node: parent, key};\n      }\n    } else if (\n      Array.isArray(\n        // $FlowExpectedError[prop-missing]\n        parent[key],\n      )\n    ) {","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/facebook/flow/blob/f88ac94bcf6992f5d5a158854d94613ebb92c6e6/packages/flow-parser/oxidized-src/transform/astNodeMutationHelpers.js#L24-L60","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","solutions":["Do mutations inside a SimpleTransform visitor — traversal sets parent pointers, and the visitor hands you nodes with parents attached.","Or set node.parent yourself as you attach children when building/mutating trees manually.","Check `target.parent != null` before calling and fail with your own diagnostic if detached."],"exampleFix":"// before\nconst ast = parse(code);\nreplaceNodeOnParent(ast.body[0].expression, newNode); // parent is null -> throws\n\n// after\nSimpleTransform.transformProgram(ast, {\n  transform(node) {\n    if (node.type === 'ThatExpression') return newNode; // parents set by traversal\n    return node;\n  },\n});","handlingStrategy":"type-guard","validationCode":"// Ensure parents exist before mutating\nfunction nodesHaveParents(program) {\n  let ok = true;\n  (function walk(n) {\n    if (n == null || typeof n !== 'object') return;\n    if (typeof n.type === 'string' && n.parent == null && n !== program) ok = false;\n    for (const k of Object.keys(n)) if (k !== 'parent') walk(n[k]);\n  })(program);\n  return ok;\n}","typeGuard":"function isAttachedToTree(node) {\n  return node != null && node.parent != null;\n}","tryCatchPattern":null,"preventionTips":["Mutate only inside SimpleTransform visitors, where parents are set.","If building trees manually, assign .parent when attaching children.","Remember flow-parser's parse() does not set parents — unlike babel/traverse."],"tags":["flow","parser","ast","mutation","parent-pointers"],"backgroundTag":"missing-parent-pointer","analyzedSha":"f88ac94bcf6992f5d5a158854d94613ebb92c6e6","analyzedAt":"2026-08-20T10:41:37.992Z","contentChangedAt":"2026-08-20T10:41:37.992Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}