{"record":{"id":"2c7a081adedd8174","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":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/flow-parser/oxidized-src/transform/astNodeMutationHelpers.js","lineNumber":73,"sourceCode":"    } else if (\n      Array.isArray(\n        // $FlowExpectedError[prop-missing]\n        parent[key],\n      )\n    ) {\n      for (let i = 0; i < parent[key].length; i += 1) {\n        // $FlowExpectedError[invalid-tuple-index]\n        const current = parent[key][i];\n        // $FlowFixMe[invalid-compare]\n        if (current === target) {\n          return {type: 'array', node: parent, key, targetIndex: i};\n        }\n      }\n    }\n  }\n\n  // this shouldn't happen ever\n  throw new Error(\n    `Expected to find the ${target.type} as a direct child of the ${parent.type}.`,\n  );\n}\n\n/**\n * Replace a node with a new node within an AST (via the parent pointer).\n */\nexport function replaceNodeOnParent(\n  originalNode: ESNode,\n  originalNodeParent: ESNode,\n  nodeToReplaceWith: ESNode | ReadonlyArray<ESNode>,\n  visitorKeys?: ?VisitorKeysType,\n): void {\n  const replacementParent = getParentKey(\n    originalNode,\n    originalNodeParent,\n    visitorKeys,\n  );","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/facebook/flow/blob/d1341dac899a79c027762f6b423d896045287620/packages/flow-parser/oxidized-src/transform/astNodeMutationHelpers.js#L55-L91","documentation":"After confirming target.parent exists, the helper scans every visitor key of the parent looking for the target either as a direct property or inside an array property; if the target is not found anywhere, this 'should never happen' invariant error is thrown. In practice it means the parent pointer is stale: the parent used to hold the node but no longer does, typically because another transform already replaced or removed it.","triggerScenarios":"Two transforms (or two steps of one codemod) sharing node references, where the first replaced the node on its parent and the second then tries to mutate the same node; an AST mutated directly by hand so that parent arrays no longer contain the nodes they point to.","commonSituations":"Running multiple SimpleTransform passes while caching node references between them; codemod frameworks that queue up node-level operations and flush them after the tree has already changed.","solutions":["Re-parse the source (or re-traverse a pristine copy of the AST) for each independent transform pass","Never cache node references across transforms; re-find nodes by position or type in each pass","Do all changes for one node in a single visitor callback instead of multiple passes"],"exampleFix":"// before\nconst targets = findNodes(ast, 'Foo');\nast = SimpleTransform.transform(ast, visitorA);\nmutateAll(targets); // stale references into transformed tree\n\n// after\nast = SimpleTransform.transform(ast, visitorA);\nconst targets = findNodes(ast, 'Foo'); // re-find on the new tree\nmutateAll(targets);","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"const isDirectChild = (parent, target, visitorKeys) =>\n  (visitorKeys[parent.type] || []).some(k =>\n    parent[k] === target || (Array.isArray(parent[k]) && parent[k].includes(target))\n  );","tryCatchPattern":"try {\n  SimpleTransform.transform(ast, options);\n} catch (e) {\n  if (e.message.includes('as a direct child')) {\n    // invariant corruption: start over from source instead of patching\n    ast = parse(originalSource, {});\n    SimpleTransform.transform(ast, safeOptions);\n  } else throw e;\n}","preventionTips":["Treat this error as a signal of tree corruption: re-parse rather than continuing","Do not mutate the AST outside SimpleTransform between passes","Never cache node references across transform passes; re-find nodes each pass"],"tags":["ast","parent-pointer","invariant","flow-parser"],"backgroundTag":"stale-ast-parent-pointer","analyzedSha":"d1341dac899a79c027762f6b423d896045287620","analyzedAt":"2026-08-17T00:07:02.212Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}