{"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":"exception","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/d1341dac899a79c027762f6b423d896045287620/packages/flow-transform/src/transform/mutations/utils/getStatementParent.js#L33-L69","documentation":"Thrown by getStatementParent() (used by the InsertStatement, RemoveStatement, and ReplaceStatementWithMany mutations) when the target node lives in a non-statement slot of its parent, such as the test of an IfStatement/WhileStatement, the init/test/update of a ForStatement, the label of a LabeledStatement, the object of a WithStatement, the left/right of a ForIn/ForOf, or the test of a SwitchCase. assertValidStatementLocation() checks each of these invalid keys and rejects the mutation because statements cannot be inserted relative to a node that is itself an expression or identifier position. The error names the exact offending slot (e.g. `IfStatement.test`) so you can see where the node actually sits.","triggerScenarios":"Creating a mutation whose target is an expression node, e.g. RemoveStatement({node: ifStatement.test}), InsertStatement({target: forStatement.init, ...}), or ReplaceStatementWithMany({target: labeledStatement.label, ...}). Any of these hits assertValidStatementLocation because the target is stored in the parent's test/init/update/label/object/left/right property rather than a statement container.","commonSituations":"Codemods that traverse to a node via a visitor (e.g. matching BinaryExpression or Identifier) and then feed that node into a statement mutation API; selecting the if-test or loop-condition because it matched first; porting Babel/jscodeshift code where replaceWith() worked on arbitrary nodes and assuming the statement APIs behave the same.","solutions":["Pass a node that actually occupies a statement position: a member of Program.body / BlockStatement.body, an IfStatement consequent/alternate, a loop body, or a SwitchCase.consequent entry.","Before building the mutation, check which property of target.parent holds the target; if it is test/init/update/label/object/left/right, target the enclosing statement instead.","If you intended to operate on the expression itself, use an expression-level transform (replace the parent statement with a new one containing the desired expression) rather than a statement insertion/removal mutation.","In a codemod pipeline, type-narrow traversed nodes to Statement/ModuleDeclaration before constructing mutations."],"exampleFix":"// before\nconst mutation = {\n  kind: 'remove_statement',\n  node: ifStatement.test, // BinaryExpression in a non-statement slot\n};\n\n// after\nconst mutation = {\n  kind: 'remove_statement',\n  node: ifStatement, // remove the whole statement, or target a body/consequent member\n};","handlingStrategy":"type-guard","validationCode":"import {FlowVisitorKeys} from 'flow-ast';\n\nconst STATEMENT_CONTAINER_SLOTS = {\n  IfStatement: ['consequent', 'alternate'],\n  LabeledStatement: ['body'],\n  WithStatement: ['body'],\n  DoWhileStatement: ['body'],\n  WhileStatement: ['body'],\n  ForStatement: ['body'],\n  ForInStatement: ['body'],\n  ForOfStatement: ['body'],\n  SwitchCase: ['consequent'],\n  BlockStatement: ['body'],\n  Program: ['body'],\n};\n\nfunction isStatementPosition(node) {\n  const parent = node.parent;\n  if (parent == null) return false;\n  const slots = STATEMENT_CONTAINER_SLOTS[parent.type];\n  if (slots == null) return false;\n  return slots.some(key => {\n    const v = parent[key];\n    return v === node || (Array.isArray(v) && v.includes(node));\n  });\n}","typeGuard":"/** True when `node` sits in a statement slot of its parent and statement mutations are safe. */\nfunction isStatementPosition(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    case 'SwitchCase':\n      return parent.consequent.includes(node);\n    case 'BlockStatement':\n    case 'Program':\n      return parent.body.includes(node);\n    default:\n      return false;\n  }\n}","tryCatchPattern":"import {InvalidStatementError} from 'flow-transform/src/transform/Errors';\n\ntry {\n  applyMutation(ast, mutation);\n} catch (err) {\n  if (err instanceof InvalidStatementError && /Attempted to insert/.test(err.message)) {\n    // target is in a non-statement slot; retarget or skip\n    console.warn('skipping non-statement target:', err.message);\n  } else {\n    throw err;\n  }\n}","preventionTips":["Type-narrow traversal results to Statement/ModuleDeclaration before building statement mutations.","Log target.parent.type and the owning key when composing mutations during codemod development.","Keep a mapping of parent type -> valid statement slots and assert against it in tests."],"tags":["flow-transform","ast","codemod","mutation","invalid-argument"],"backgroundTag":"invalid-ast-node-position","analyzedSha":"d1341dac899a79c027762f6b423d896045287620","analyzedAt":"2026-08-17T00:07:02.212Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}