{"record":{"id":"964cd7a6f1d62d3b","repo":"facebook/flow","slug":"invalid-mutation-tried-to-mutate-an-elements-arra","errorCode":null,"errorMessage":"Invalid Mutation: Tried to mutate an elements array with an out of bounds index. Index: ${index}, Array Size: ${array.length}","messagePattern":"Invalid Mutation: Tried to mutate an elements array with an out of bounds index\\. Index: (.+?), Array Size: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/flow-parser/oxidized-src/transform/astArrayMutationHelpers.js","lineNumber":13,"sourceCode":"/**\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * @flow strict\n * @format\n */\n\nfunction assertArrayBounds<T>(array: ReadonlyArray<T>, index: number): void {\n  if (index < 0 || index >= array.length) {\n    throw new Error(\n      `Invalid Mutation: Tried to mutate an elements array with an out of bounds index. Index: ${index}, Array Size: ${array.length}`,\n    );\n  }\n}\n\nexport function arrayIsEqual(\n  a1: ReadonlyArray<unknown>,\n  a2: ReadonlyArray<unknown>,\n): boolean {\n  if (a1 === a2) {\n    return true;\n  }\n\n  if (a1.length !== a2.length) {\n    return false;\n  }\n\n  for (let i = 0; i < a1.length; i++) {","sourceCodeStart":1,"sourceCodeEnd":31,"githubUrl":"https://github.com/facebook/flow/blob/f88ac94bcf6992f5d5a158854d94613ebb92c6e6/packages/flow-parser/oxidized-src/transform/astArrayMutationHelpers.js#L1-L31","documentation":"astArrayMutationHelpers asserts index bounds (0 <= index < array.length) before every element mutation (replaceInArray, removal, insertion helpers). An out-of-range or negative index throws with the offending index and array size, indicating the caller's positional bookkeeping no longer matches the array being mutated.","triggerScenarios":"Calling array-mutation helpers with an index computed before earlier mutations shifted/removed elements (stale index), an index taken from a different array, or an off-by-one (e.g. using array.length as the last index instead of length-1).","commonSituations":"Codemods that remove several nodes while holding precomputed indices; batched edits applied to an AST that was rebuilt mid-loop; transforms that copy an index across traversal boundaries where sibling counts differ.","solutions":["Recompute the index immediately before each mutation (e.g. via array.indexOf(node)) instead of caching it.","Mutate a snapshot plan but resolve indices at apply time against the current array, or rebuild arrays functionally (map/filter) instead of positional splice.","Check bounds before calling: if (i < 0 || i >= arr.length) skip/log.","For removals, prefer identity-based removal (find then filter) over index-based removal."],"exampleFix":"// before\nconst idx = body.indexOf(target);\nforEachOtherNode(() => replaceInArray(body, idx, newNode)); // body changed meanwhile\n\n// after\nconst idx = body.indexOf(target); // resolve at apply time\nif (idx >= 0 && idx < body.length) replaceInArray(body, idx, newNode);","handlingStrategy":"validation","validationCode":"// Bounds-check before mutating\nfunction inBounds(array, index) {\n  return Number.isInteger(index) && index >= 0 && index < array.length;\n}\nif (inBounds(body, idx)) replaceInArray(body, idx, newNode);","typeGuard":"function isValidIndex(array, index) {\n  return Number.isInteger(index) && index >= 0 && index < array.length;\n}","tryCatchPattern":null,"preventionTips":["Resolve indices at apply time (indexOf the node), never cache them across mutations.","Prefer identity-based rebuilds (map/filter) over positional splices in codemods."],"tags":["flow","parser","ast","mutation","index-out-of-bounds"],"backgroundTag":"array-index-out-of-bounds","analyzedSha":"f88ac94bcf6992f5d5a158854d94613ebb92c6e6","analyzedAt":"2026-08-20T10:41:37.992Z","contentChangedAt":"2026-08-20T10:41:37.992Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}