{"record":{"id":"3add287daa726148","repo":"facebook/flow","slug":"cannot-perform-a-remove-mutation-on-node-of-type","errorCode":null,"errorMessage":"Cannot perform a remove mutation on node of type ${node.type}","messagePattern":"Cannot perform a remove mutation on node of type (.+?)","errorType":"exception","errorClass":"InvalidRemovalError","httpStatus":null,"severity":"error","filePath":"packages/flow-transform/src/transform/mutations/RemoveNode.js","lineNumber":270,"sourceCode":"          case 'OptionalCallExpression':\n          case 'CallExpression':\n          case 'NewExpression':\n            return 'arguments';\n\n          default:\n            throw new InvalidRemovalError(\n              getErrorMessage([\n                'ArrayExpression',\n                'ObjectExpression',\n                'CallExpression',\n                'OptionalCallExpression',\n                'NewExpression',\n              ]),\n            );\n        }\n\n      default:\n        throw new InvalidRemovalError(\n          `Cannot perform a remove mutation on node of type ${node.type}`,\n        );\n    }\n  })();\n\n  const targetIndex = (() => {\n    // $FlowExpectedError[prop-missing]\n    const arr = node.parent[key];\n    const idx = arr.indexOf(node);\n    // $FlowFixMe[invalid-compare]\n    if (idx === -1) {\n      throw new InvalidRemovalError(\n        `Could not find target in array of \\`${node.parent.type}.${key}\\`.`,\n      );\n    }\n    return idx;\n  })();\n","sourceCodeStart":252,"sourceCodeEnd":288,"githubUrl":"https://github.com/facebook/flow/blob/d1341dac899a79c027762f6b423d896045287620/packages/flow-transform/src/transform/mutations/RemoveNode.js#L252-L288","documentation":"removeNodeMutation is allowlisted per node type: it has explicit cases (statements, properties, params, Identifiers, Rest and SpreadElements, JSX attributes, ObjectType members) and every unlisted type falls through to this default InvalidRemovalError. The message names the offending node.type, which is the fastest diagnostic: that type has no supported removal semantics at all. This is by design: removal is only implemented where the result is guaranteed to remain valid, printable code.","triggerScenarios":"removeNodeMutation(node) where node.type is e.g. Literal, CallExpression, BinaryExpression, Program, or any type-annotation node: anything without a dedicated removal case.","commonSituations":"Generic 'delete this node' codemod logic applied to arbitrary matched nodes; porting a codemod from jscodeshift, where remove() works on any Node, and assuming parity here.","solutions":["Use replaceNodeMutation (or replace-with) to swap the node for what should remain, instead of removing it","If the goal is to drop a statement or property, remove at the level that is allowlisted (e.g. the VariableDeclarator rather than the Literal init)","Check the switch in RemoveNode.js for the current allowlist before writing the codemod"],"exampleFix":"// before\nmutations.push(removeNode(callNode)); // 'CallExpression' has no removal case\n\n// after\nmutations.push(replaceWith(callNode, {type: 'VoidLiteral'})); // or drop the enclosing statement","handlingStrategy":"type-guard","validationCode":"// mirror the allowlist in RemoveNode.js (keep in sync on upgrades)\nconst REMOVABLE_TYPES = new Set(['Property', 'SpreadElement', 'RestElement', 'Identifier', 'ImportDeclaration', 'JSXAttribute', 'ObjectTypeProperty', 'ObjectTypeSpreadProperty', 'ObjectTypeIndexer', 'ObjectTypeCallProperty', 'ObjectTypeInternalSlot']);\nfunction isRemovable(node) {\n  return REMOVABLE_TYPES.has(node.type) && parentAllowsRemoval(node);\n}","typeGuard":"const isRemovableNodeType = (node, allowed) => allowed.has(node.type);","tryCatchPattern":"try {\n  mutations.push(removeNodeMutation(node));\n} catch (e) {\n  if (e.message.startsWith('Cannot perform a remove mutation')) {\n    mutations.push(replaceWith(node, fallbackReplacement)); // removal unsupported: replace instead\n  } else throw e;\n}","preventionTips":["Prefer replaceNodeMutation when removal semantics are unclear for a node type","Maintain an allowlist set of removable types mirroring RemoveNode.js and guard your visitor","Remove at the owning level (declarator, property, statement) rather than leaf values"],"tags":["flow-transform","removal","unsupported-node"],"backgroundTag":"unsupported-ast-node-removal","analyzedSha":"d1341dac899a79c027762f6b423d896045287620","analyzedAt":"2026-08-17T00:07:02.212Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}