{"record":{"id":"bbf1edd71d448789","repo":"facebook/flow","slug":"expected-to-find-the-target-type-as-a-direct-ch-bbf1ed","errorCode":null,"errorMessage":"Expected to find the ${target.type} as a direct child of the ${parent.type}.","messagePattern":"Expected to find the (.+?) as a direct child of the (.+?)\\.","errorType":"exception","errorClass":"InvalidReplacementError","httpStatus":null,"severity":"error","filePath":"packages/flow-transform/src/transform/mutations/ReplaceNode.js","lineNumber":109,"sourceCode":"    const child = (parent as $FlowFixMe)[key];\n    if (isNode(child)) {\n      // $FlowFixMe[invalid-compare]\n      if (child === target) {\n        return {type: 'single', parent, key};\n      }\n    } else if (Array.isArray(child)) {\n      for (let i = 0; i < child.length; i += 1) {\n        const current = child[i];\n        const originalNode = getOriginalNode(current);\n        if (current === target || originalNode === target) {\n          return {type: 'array', parent, key, targetIndex: i};\n        }\n      }\n    }\n  }\n\n  // this shouldn't happen ever\n  throw new InvalidReplacementError(\n    `Expected to find the ${target.type} as a direct child of the ${parent.type}.`,\n  );\n}\n","sourceCodeStart":91,"sourceCodeEnd":113,"githubUrl":"https://github.com/facebook/flow/blob/d1341dac899a79c027762f6b423d896045287620/packages/flow-transform/src/transform/mutations/ReplaceNode.js#L91-L113","documentation":"replaceNodeMutation resolves the target's location by scanning the parent's visitor keys, checking both direct identity and getOriginalNode(...) so that detached (wrapper) nodes whose original matches the target are found. If no key or array slot holds the target, this InvalidReplacementError is thrown: the flow-transform counterpart of the 'not a direct child' invariant, indicating the parent link is stale or the target was already replaced or detached.","triggerScenarios":"Calling replaceNodeMutation on a node that was already replaced earlier in the same pass; replacing a node whose parent pointer references a tree that was concurrently modified; mixing raw original nodes with detached-node wrappers from different transform runs.","commonSituations":"Queueing multiple replacements for the same node; caching nodes between the traversal and mutation phases; reusing detached-node wrappers built against an older AST.","solutions":["Replace each node at most once per pass; check your queued mutations for duplicate targets before applying","Perform mutations during the traversal that found the nodes, not from a cached list afterwards","Re-run traversal on the mutated AST if you need a second round of replacements"],"exampleFix":"// before\nconst seen = collectTargets(ast);\nfor (const t of seen) replaceNodeMutation(t, newNode); // second replace of same t throws\n\n// after\nconst done = new Set();\nfor (const t of collectTargets(ast)) {\n  if (done.has(t)) continue;\n  done.add(t);\n  replaceNodeMutation(t, newNode);\n}","handlingStrategy":"try-catch","validationCode":"const replaced = new Set();\nfunction safeReplace(target, replacement) {\n  if (replaced.has(target)) return; // one replacement per node per pass\n  replaced.add(target);\n  mutations.push(replaceNodeMutation(target, replacement));\n}","typeGuard":"const isDirectChildOf = (parent, target) =>\n  Object.keys(parent).some(k =>\n    parent[k] === target ||\n    (Array.isArray(parent[k]) &&\n      parent[k].some(c => c === target || (c && c.original === target)))\n  );","tryCatchPattern":"try {\n  mutations.push(replaceNodeMutation(target, replacement));\n} catch (e) {\n  if (e.message.includes('as a direct child')) {\n    throw new Error('Target is no longer attached (already replaced?): re-traverse before replacing');\n  }\n  throw e;\n}","preventionTips":["Replace each node at most once per pass; dedupe queued replacements by target","Issue mutations during traversal, not from node lists cached before mutation","Keep detached-node wrappers and originals consistent: do not mix raw nodes with wrapped ones"],"tags":["flow-transform","replacement","stale-pointer","invariant"],"backgroundTag":"stale-ast-parent-pointer","analyzedSha":"d1341dac899a79c027762f6b423d896045287620","analyzedAt":"2026-08-17T00:07:02.212Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}