{"record":{"id":"6b17a6c136834b56","repo":"facebook/flow","slug":"expected-to-find-the-target-target-type-on-th","errorCode":null,"errorMessage":"Expected to find the target \"${target.type}\" on the \"${result.parent.type}.${result.key}\", but found a different node. This likely means that you attempted to mutate around the target after it was deleted/replaced.","messagePattern":"Expected to find the target \"(.+?)\" on the \"(.+?)\\.(.+?)\", but found a different node\\. This likely means that you attempted to mutate around the target after it was deleted/replaced\\.","errorType":"exception","errorClass":"InvalidStatementError","httpStatus":null,"severity":"error","filePath":"packages/flow-transform/src/transform/mutations/utils/getStatementParent.js","lineNumber":142,"sourceCode":"          key: 'body',\n          targetIndex: getAssertedIndex('body', parent.body),\n        };\n      }\n    }\n\n    throw new InvalidStatementError(\n      `Expected to find a valid statement parent, but found a parent of type \"${parent.type}\".`,\n    );\n  })();\n\n  if (\n    // array insertions are already validated by the getAssertedIndex function\n    result.targetIndex == null &&\n    // $FlowExpectedError[prop-missing]\n    // $FlowFixMe[invalid-compare]\n    result.parent[result.key] !== target\n  ) {\n    throw new InvalidStatementError(\n      `Expected to find the target \"${target.type}\" on the \"${result.parent.type}.${result.key}\", but found a different node. ` +\n        'This likely means that you attempted to mutate around the target after it was deleted/replaced.',\n    );\n  }\n\n  return result;\n}\n","sourceCodeStart":124,"sourceCodeEnd":150,"githubUrl":"https://github.com/facebook/flow/blob/d1341dac899a79c027762f6b423d896045287620/packages/flow-transform/src/transform/mutations/utils/getStatementParent.js#L124-L150","documentation":"Thrown by getStatementParent() as a final consistency check for 'single' slots: after computing which property of the parent should hold the target (e.g. IfStatement.consequent/alternate, loop body), it verifies result.parent[result.key] === target and finds a different node there. As the message states, this means you attempted to mutate around a target after it was deleted or replaced — the target still points at the old parent via .parent, but the slot now holds a new node. It is the single-child counterpart of the array-index check performed by getAssertedIndex().","triggerScenarios":"Batching two mutations where the first replaces an if-consequent (or loop body) and the second inserts/removes around the old node; manually assigning parent.consequent = newNode while the old node keeps .parent pointing at that IfStatement, then calling a mutation on the old node.","commonSituations":"Codemod pipelines that compute a mutation list from one traversal and apply them in order, with a replace earlier in the list invalidating a later insertion target; mixing manual property assignment with the mutation API; retrying a failed mutation against an already-replaced node.","solutions":["Apply one mutation per traversal pass, re-collecting targets after each pass, so no mutation references a replaced node.","Before applying each queued mutation, re-verify that target.parent[key] === target for single-slot parents (or that the array still contains it) and drop the mutation otherwise.","Express replacements through the library's mutation API so parent links are updated, instead of assigning parent.consequent/body directly.","If a replace and an insert must touch the same region, retarget the insert at the replacement node."],"exampleFix":"// before\nparent.consequent = newBlock;              // manual replace, old node keeps .parent\napplyMutation(ast, {kind: 'insert_statement', target: oldNode, ...}); // throws\n\n// after\napplyMutation(ast, {kind: 'replace_statement_with_many', target: oldNode, statements: [...]},);\n// then re-traverse before inserting around the new node","handlingStrategy":"validation","validationCode":"function stillOwnsSlot(node) {\n  const parent = node.parent;\n  if (parent == null) return false;\n  switch (parent.type) {\n    case 'IfStatement':\n      return parent.consequent === node || parent.alternate === node;\n    case 'LabeledStatement':\n    case 'WithStatement':\n    case 'DoWhileStatement':\n    case 'WhileStatement':\n    case 'ForStatement':\n    case 'ForInStatement':\n    case 'ForOfStatement':\n      return parent.body === node;\n    default:\n      return true; // array containers validated separately\n  }\n}","typeGuard":"/** True when the single-slot parent still holds this exact node (not a replacement). */\nfunction isCurrentSlotOwner(node) {\n  const parent = node.parent;\n  if (parent == null) return false;\n  const key = parent.type === 'IfStatement'\n    ? (parent.consequent === node ? 'consequent' : 'alternate')\n    : 'body';\n  return parent[key] === node;\n}","tryCatchPattern":"import {InvalidStatementError} from 'flow-transform/src/transform/Errors';\n\ntry {\n  applyMutation(ast, mutation);\n} catch (err) {\n  if (err instanceof InvalidStatementError && err.message.includes('deleted/replaced')) {\n    // stale target: re-traverse and rebuild this mutation against the new node\n    mutation = rebuildMutationFromTraversal(ast, mutation);\n  } else {\n    throw err;\n  }\n}","preventionTips":["Apply mutations one per pass and re-traverse after each replace/remove.","Drop queued mutations whose target no longer owns its parent slot.","Never assign parent.consequent/parent.body directly; use the mutation API."],"tags":["flow-transform","ast","codemod","stale-reference","mutation"],"backgroundTag":"stale-ast-reference","analyzedSha":"d1341dac899a79c027762f6b423d896045287620","analyzedAt":"2026-08-17T00:07:02.212Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}