facebook/flow · error · InvalidStatementError

Expected to find a valid statement parent, but found a paren

Error message

Expected to find a valid statement parent, but found a parent of type "${parent.type}".

What it means

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).

Source

Thrown at packages/flow-transform/src/transform/mutations/utils/getStatementParent.js:130

          type: 'array',
          parent,
          key: 'consequent',
          targetIndex: getAssertedIndex('consequent', parent.consequent),
        };
      }

      case 'BlockStatement':
      case 'Program': {
        return {
          type: 'array',
          parent,
          key: 'body',
          targetIndex: getAssertedIndex('body', parent.body),
        };
      }
    }

    throw new InvalidStatementError(
      `Expected to find a valid statement parent, but found a parent of type "${parent.type}".`,
    );
  })();

  if (
    // array insertions are already validated by the getAssertedIndex function
    result.targetIndex == null &&
    // $FlowExpectedError[prop-missing]
    // $FlowFixMe[invalid-compare]
    result.parent[result.key] !== target
  ) {
    throw new InvalidStatementError(
      `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.',
    );
  }

  return result;

View on GitHub (pinned to d1341dac89)

Solutions

  1. 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.
  2. For exported declarations, target the enclosing ExportNamedDeclaration or restructure the tree so the declaration moves into a BlockStatement body first.
  3. For expression positions (VariableDeclarator.init, ReturnStatement.argument), replace the enclosing statement with a new one instead of using a statement mutation on the expression.
  4. Ensure the AST comes from the Flow parser so parent links and node shapes match what the switch handles.

Example fix

// before
const decl = exportedNode.declaration; // parent is ExportNamedDeclaration
const mutation = {kind: 'remove_statement', node: decl}; // throws

// after
const mutation = {
  kind: 'remove_statement',
  node: exportedNode, // remove the whole export declaration
};
Defensive patterns

Strategy: type-guard

Validate before calling

const STATEMENT_PARENT_TYPES = new Set([
  'IfStatement', 'LabeledStatement', 'WithStatement', 'DoWhileStatement',
  'WhileStatement', 'ForStatement', 'ForInStatement', 'ForOfStatement',
  'SwitchCase', 'BlockStatement', 'Program',
]);

function hasSupportedStatementParent(node) {
  return node.parent != null && STATEMENT_PARENT_TYPES.has(node.parent.type);
}

Type guard

/** True when the target's parent is a statement container getStatementParent supports. */
function hasSupportedStatementParent(node) {
  return (
    node.parent != null &&
    STATEMENT_PARENT_TYPES.has(node.parent.type) // Set of the 11 supported parent types
  );
}

Try / catch

import {InvalidStatementError} from 'flow-transform/src/transform/Errors';

try {
  applyMutation(ast, mutation);
} catch (err) {
  if (err instanceof InvalidStatementError && err.message.includes('valid statement parent')) {
    // parent.type not in the supported switch; retarget to an enclosing statement
    console.warn('unsupported parent, retargeting:', err.message);
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of facebook/flow@d1341dac89 (2026-08-17). Data as JSON: /api/errors/fc349cdc2bab3752. Report an issue: GitHub.