facebook/flow · error · InvalidRemovalError

Tried to remove ${node.type} from parent of type ${node.pare

Error message

Tried to remove ${node.type} from parent of type ${node.parent.type}.
However ${node.type} can only be safely removed from parent of type ArrayExpression | ObjectExpression | CallExpression | OptionalCallExpression | NewExpression.

What it means

The SpreadElement case of removeNodeMutation supports removal from ArrayExpression elements, ObjectExpression properties, and call, new, and optional-call arguments. Any other parent (e.g. a JSXSpreadAttribute) hits the default and throws InvalidRemovalError with the allowlist. Each allowed slot is an array key the engine can splice; other positions are rejected because removing the spread there changes program shape unsafely.

Source

Thrown at packages/flow-transform/src/transform/mutations/RemoveNode.js:258

      // SpreadElement can be the child of a number of usecases
      case 'SpreadElement':
        switch (node.parent.type) {
          case 'ArrayExpression':
            return 'elements';

          case 'ObjectExpression':
            return 'properties';

          // $FlowFixMe[incompatible-type]
          // $FlowFixMe[invalid-compare]
          case 'OptionalCallExpression':
          case 'CallExpression':
          case 'NewExpression':
            return 'arguments';

          default:
            throw new InvalidRemovalError(
              getErrorMessage([
                'ArrayExpression',
                'ObjectExpression',
                'CallExpression',
                'OptionalCallExpression',
                'NewExpression',
              ]),
            );
        }

      default:
        throw new InvalidRemovalError(
          `Cannot perform a remove mutation on node of type ${node.type}`,
        );
    }
  })();

  const targetIndex = (() => {

View on GitHub (pinned to d1341dac89)

Solutions

  1. Guard on node.parent.type before requesting removal and only allow ArrayExpression, ObjectExpression, CallExpression, OptionalCallExpression, and NewExpression
  2. For JSX spreads, operate on the JSXSpreadAttribute (or replace the whole opening element) instead of the SpreadElement
  3. Use replaceNodeMutation on the enclosing node for unsupported positions

Example fix

// before
if (node.type === 'SpreadElement') mutations.push(removeNode(node)); // JSX parent throws

// after
const OK = new Set(['ArrayExpression','ObjectExpression','CallExpression','OptionalCallExpression','NewExpression']);
if (node.type === 'SpreadElement' && OK.has(node.parent.type)) {
  mutations.push(removeNode(node));
}
Defensive patterns

Strategy: type-guard

Validate before calling

const SPREAD_REMOVE_PARENTS = new Set(['ArrayExpression', 'ObjectExpression', 'CallExpression', 'OptionalCallExpression', 'NewExpression']);
function canRemoveSpreadElement(node) {
  return node.type === 'SpreadElement' && SPREAD_REMOVE_PARENTS.has(node.parent.type);
}

Type guard

const isRemovableSpreadElement = (node) =>
  node.type === 'SpreadElement' && ['ArrayExpression', 'ObjectExpression', 'CallExpression', 'OptionalCallExpression', 'NewExpression'].includes(node.parent && node.parent.type);

Prevention

When it happens

Trigger: removeNodeMutation(spreadElement) where the parent is a JSXSpreadAttribute, or any type not in the allowlist. Note a spread inside an ArrayPattern is a RestElement, not a SpreadElement, so pattern cleanup should not match these nodes.

Common situations: Codemods stripping spread props from JSX (like <Foo {...props} />); generic 'remove all spreads' visitors that do not distinguish SpreadElement from RestElement and JSXSpreadAttribute.

Related errors


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