{"record":{"id":"7b0b048b4f23119c","repo":"facebook/flow","slug":"attempted-to-insert-a-statement-into-parentwith","errorCode":null,"errorMessage":"Attempted to insert a statement into `${parentWithType.type}.${key}`.","messagePattern":"Attempted to insert a statement into `(.+?)\\.(.+?)`\\.","errorType":"validation","errorClass":"InvalidStatementError","httpStatus":null,"severity":"error","filePath":"packages/flow-transform/src/transform/mutations/utils/getStatementParent.js","lineNumber":51,"sourceCode":"    },\n>;\n\nexport function getStatementParent(\n  target: ModuleDeclaration | Statement,\n): StatementParent {\n  function assertValidStatementLocation<\n    T extends Readonly<interface {type: string}>,\n  >(parentWithType: T, ...invalidKeys: ReadonlyArray<keyof T>): void {\n    for (const key of invalidKeys) {\n      // $FlowExpectedError[prop-missing]\n      const value = parentWithType[key];\n\n      if (\n        // $FlowFixMe[invalid-compare]\n        value === target ||\n        (Array.isArray(value) && value.includes(target))\n      ) {\n        throw new InvalidStatementError(\n          `Attempted to insert a statement into \\`${parentWithType.type}.${key}\\`.`,\n        );\n      }\n    }\n  }\n  function getAssertedIndex(key: string, arr: ReadonlyArray<unknown>): number {\n    const idx = arr.indexOf(target);\n    if (idx === -1) {\n      throw new InvalidStatementError(\n        `Could not find target in array of \\`${parent.type}.${key}\\`.`,\n      );\n    }\n    return idx;\n  }\n\n  const parent = target.parent;\n  const result: StatementParent = (() => {\n    switch (parent.type) {","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/facebook/flow/blob/f88ac94bcf6992f5d5a158854d94613ebb92c6e6/packages/flow-transform/src/transform/mutations/utils/getStatementParent.js#L33-L69","documentation":"InvalidStatementError thrown by getStatementParent's `assertValidStatementLocation` helper. Statement mutations (insertStatement*/remove of statements) resolve where the anchor statement lives; for parent types like IfStatement, ForStatement, LabeledStatement, etc. the code first asserts the anchor is NOT sitting in a non-statement slot such as `IfStatement.test`, `ForStatement.init`, `LabeledStatement.label`, or `ForInStatement.left/right`. If you passed one of those non-statement nodes as the anchor, this error names the offending `ParentType.key`.","triggerScenarios":"Calling `mutate.insertStatementBefore(ifStatement.test, ...)` or otherwise using an expression node (the anchor's parent holds it in a test/init/label/left/right/object slot) where a Statement is required.","commonSituations":"Visitors that match expression nodes (CallExpression, Identifier) and then call a statement mutation relative to `node` or `node.parent` without checking that the node actually occupies a statement slot.","solutions":["Only use statement mutations on actual Statement/ModuleDeclaration nodes.","From an expression node, walk up parents until you reach a node whose type ends in 'Statement' or is a Declaration, then anchor on that.","For expressions, use replaceNode instead of statement insertions."],"exampleFix":"// before\nmutate.insertStatementBefore(callNode.parent.test, stmt); // anchor in IfStatement.test - throws\n\n// after - walk up to the real statement\nlet anchor = callNode;\nwhile (anchor.parent && !/Statement|Declaration$/.test(anchor.parent.type) === false) { /* ... */ }\nmutate.insertStatementBefore(enclosingStatement, stmt);","handlingStrategy":"type-guard","validationCode":"const NON_STATEMENT_SLOTS = new Map([\n  ['IfStatement', ['test']], ['ForStatement', ['init', 'test', 'update']],\n  ['LabeledStatement', ['label']], ['WithStatement', ['object']],\n  ['WhileStatement', ['test']], ['DoWhileStatement', ['test']],\n  ['ForInStatement', ['left', 'right']], ['ForOfStatement', ['left', 'right']],\n]);\n\nfunction occupiesStatementSlot(node): boolean {\n  const bad = NON_STATEMENT_SLOTS.get(node.parent.type) ?? [];\n  const inBadSlot = bad.some(k => node.parent[k] === node);\n  return !inBadSlot && /Statement|Declaration$/.test(node.type);\n}\n\nif (occupiesStatementSlot(anchor)) mutate.insertStatementBefore(anchor, stmt);","typeGuard":"function isStatementNode(node): boolean {\n  return typeof node.type === 'string' &&\n    (/Statement$/.test(node.type) || /Declaration$/.test(node.type));\n}","tryCatchPattern":null,"preventionTips":["Pass only Statement/ModuleDeclaration nodes to statement mutations.","From an expression, walk up to the enclosing statement first.","Use replaceNode for expression-level changes."],"tags":["flow","flow-transform","codemod","statement-anchor","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"}