facebook/flow · error · InvalidRemovalError

Could not find target in array of `${node.parent.type}.${key

Error message

Could not find target in array of `${node.parent.type}.${key}`.

What it means

After computing which array key holds the node, RemoveNode does parent[key].indexOf(node) to find the splice index; a -1 result means the parent pointer claims a relationship the array no longer reflects, and this InvalidRemovalError is thrown. Like the 'direct child' invariants elsewhere, it indicates the AST was mutated outside the mutation API or the node is a copy whose parent points at the original array. The message includes the parent type and key to narrow the search.

Source

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

                'NewExpression',
              ]),
            );
        }

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

  const targetIndex = (() => {
    // $FlowExpectedError[prop-missing]
    const arr = node.parent[key];
    const idx = arr.indexOf(node);
    // $FlowFixMe[invalid-compare]
    if (idx === -1) {
      throw new InvalidRemovalError(
        `Could not find target in array of \`${node.parent.type}.${key}\`.`,
      );
    }
    return idx;
  })();

  return {
    type: 'array',
    parent: node.parent,
    key,
    targetIndex,
  };
}

export function performRemoveNodeMutation(
  mutationContext: MutationContext,
  mutation: RemoveNodeMutation,
): ESNode {

View on GitHub (pinned to d1341dac89)

Solutions

  1. Do all structural changes through the mutation API: never splice parent arrays by hand during a transform
  2. Re-parse or re-traverse to get a pristine AST before each new batch of mutations
  3. When cloning nodes, clear their parent pointers so stale links fail fast instead of mismatching

Example fix

// before
program.body.splice(2, 1); // raw splice
mutations.push(removeNode(stmt)); // stmt no longer in body -> throws

// after
mutations.push(removeNode(program.body[2])); // engine owns the splice
Defensive patterns

Strategy: try-catch

Validate before calling

function isNodeInParentArray(node, key) {
  const arr = node.parent && node.parent[key];
  return Array.isArray(arr) && arr.indexOf(node) !== -1;
}

Type guard

const isInParentArray = (node, key) =>
  node.parent != null && Array.isArray(node.parent[key]) &&
  node.parent[key].includes(node);

Try / catch

try {
  mutations.push(removeNodeMutation(node));
} catch (e) {
  if (e.message.includes('Could not find target in array')) {
    // stale tree: re-parse the original source and re-locate the node instead of continuing
    throw new Error('AST out of sync with mutation API; re-parse before mutating');
  }
  throw e;
}

Prevention

When it happens

Trigger: Splicing an array manually (e.g. program.body.splice(...) inside a visitor) and then calling removeNodeMutation on a node from that region; removing a cloned node whose .parent still references the original tree's parent.

Common situations: Mixing direct AST manipulation with the mutation API in one pass; deep-cloning subtrees but keeping inherited parent links; running a second mutation batch over a tree already mutated by raw code.

Related errors


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