{"record":{"id":"fc349cdc2bab3752","repo":"facebook/flow","slug":"expected-to-find-a-valid-statement-parent-but-fou","errorCode":null,"errorMessage":"Expected to find a valid statement parent, but found a parent of type \"${parent.type}\".","messagePattern":"Expected to find a valid statement parent, but found a parent of type \"(.+?)\"\\.","errorType":"exception","errorClass":"InvalidStatementError","httpStatus":null,"severity":"error","filePath":"packages/flow-transform/src/transform/mutations/utils/getStatementParent.js","lineNumber":130,"sourceCode":"          type: 'array',\n          parent,\n          key: 'consequent',\n          targetIndex: getAssertedIndex('consequent', parent.consequent),\n        };\n      }\n\n      case 'BlockStatement':\n      case 'Program': {\n        return {\n          type: 'array',\n          parent,\n          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;","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/facebook/flow/blob/d1341dac899a79c027762f6b423d896045287620/packages/flow-transform/src/transform/mutations/utils/getStatementParent.js#L112-L148","documentation":"Thrown by getStatementParent() when the type of target.parent is not one of the supported statement containers (IfStatement, LabeledStatement, WithStatement, DoWhileStatement, WhileStatement, ForStatement, ForInStatement, ForOfStatement, SwitchCase, BlockStatement, Program). The switch falls through and the function rejects the mutation because it cannot determine a statement slot for the target. Typical culprits are parents like ExportNamedDeclaration (a declaration held directly in .declaration), VariableDeclarator (the target is an init expression), or ReturnStatement (the target is an argument).","triggerScenarios":"Calling RemoveStatement/InsertStatement/ReplaceStatementWithMany on the FunctionDeclaration inside `export function foo() {}` (its parent is ExportNamedDeclaration, not BlockStatement); passing an expression whose parent is a VariableDeclarator or ReturnStatement; passing a node built by a different parser whose parent chain includes non-Flow node shapes.","commonSituations":"Codemods that remove or wrap exported declarations without realizing the declaration sits in ExportNamedDeclaration.declaration; feeding hand-built or Babel/TS ASTs into flow-transform mutations; assuming every node with a Statement ancestor can be targeted directly.","solutions":["Guard before mutating: only create the mutation when target.parent.type is one of IfStatement, LabeledStatement, WithStatement, DoWhileStatement, WhileStatement, ForStatement, ForInStatement, ForOfStatement, SwitchCase, BlockStatement, Program.","For exported declarations, target the enclosing ExportNamedDeclaration or restructure the tree so the declaration moves into a BlockStatement body first.","For expression positions (VariableDeclarator.init, ReturnStatement.argument), replace the enclosing statement with a new one instead of using a statement mutation on the expression.","Ensure the AST comes from the Flow parser so parent links and node shapes match what the switch handles."],"exampleFix":"// before\nconst decl = exportedNode.declaration; // parent is ExportNamedDeclaration\nconst mutation = {kind: 'remove_statement', node: decl}; // throws\n\n// after\nconst mutation = {\n  kind: 'remove_statement',\n  node: exportedNode, // remove the whole export declaration\n};","handlingStrategy":"type-guard","validationCode":"const STATEMENT_PARENT_TYPES = new Set([\n  'IfStatement', 'LabeledStatement', 'WithStatement', 'DoWhileStatement',\n  'WhileStatement', 'ForStatement', 'ForInStatement', 'ForOfStatement',\n  'SwitchCase', 'BlockStatement', 'Program',\n]);\n\nfunction hasSupportedStatementParent(node) {\n  return node.parent != null && STATEMENT_PARENT_TYPES.has(node.parent.type);\n}","typeGuard":"/** True when the target's parent is a statement container getStatementParent supports. */\nfunction hasSupportedStatementParent(node) {\n  return (\n    node.parent != null &&\n    STATEMENT_PARENT_TYPES.has(node.parent.type) // Set of the 11 supported parent types\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 && err.message.includes('valid statement parent')) {\n    // parent.type not in the supported switch; retarget to an enclosing statement\n    console.warn('unsupported parent, retargeting:', err.message);\n  } else {\n    throw err;\n  }\n}","preventionTips":["Remember ExportNamedDeclaration.declaration is not a supported slot; target the export node itself.","Only feed Flow-parser ASTs into flow-transform mutations.","Keep the supported-parent list next to your codemod's target selection logic and assert it."],"tags":["flow-transform","ast","codemod","unsupported-parent","mutation"],"backgroundTag":"unsupported-ast-parent-type","analyzedSha":"d1341dac899a79c027762f6b423d896045287620","analyzedAt":"2026-08-17T00:07:02.212Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}