{"record":{"id":"bbf1edd71d448789","repo":"facebook/flow","slug":"expected-to-find-the-target-type-as-a-direct-ch","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":"validation","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/f88ac94bcf6992f5d5a158854d94613ebb92c6e6/packages/flow-transform/src/transform/mutations/ReplaceNode.js#L91-L113","documentation":"InvalidReplacementError thrown by flow-transform's replaceNode when locating the target: it walks every key of the parent and looks for the target as a direct child (by identity, or via getOriginalNode for detached nodes). If the target is not a direct child of its parent - it is deeper in the tree, already replaced, or a stale node object - the lookup fails and throws.","triggerScenarios":"`mutate.replaceNode(node, replacement)` after an earlier mutation in the same run replaced or removed `node` or its parent; passing a node whose `parent` pointer points at a different (old) subtree; replacing a node captured from a previous transform() call.","commonSituations":"Chained codemod operations where a replace happens on a node that a previous visitor already replaced; holding onto nodes between separate transform runs; replacing a node found via scope analysis after the tree was mutated.","solutions":["Call replaceNode on the node itself (the mutation derives the parent) and only once per node per run.","After any replace, continue mutating from the replacement node, not the original.","Use `replaceNodeWithContext` when you need the surrounding context to stay consistent.","Never reuse node objects across separate transform() invocations."],"exampleFix":"// before\nmutate.replaceNode(oldTarget, replacement); // oldTarget already replaced earlier - throws\n\n// after - mutate the current tree's node\nmutate.replaceNode(currentTarget, replacement);","handlingStrategy":"validation","validationCode":"function isDirectChild(parent, target) {\n  return Object.values(parent).some(v =>\n    v === target || (Array.isArray(v) && v.includes(target)),\n  );\n}\n\nif (isDirectChild(node.parent, node)) {\n  mutate.replaceNode(node, replacement);\n}","typeGuard":null,"tryCatchPattern":"try {\n  mutate.replaceNode(node, replacement);\n} catch (e) {\n  if (e.name === 'InvalidReplacementError' && /Expected to find/.test(e.message)) {\n    // node is stale (already replaced) - re-run the traversal on current AST\n  } else throw e;\n}","preventionTips":["One replacement per node per run.","Continue from the replacement node after a replace.","Never cache nodes across transform() calls."],"tags":["flow","flow-transform","codemod","replace-node","stale-reference","ast-mutation"],"backgroundTag":"invalid-ast-mutation","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"}