diff --git a/.jules/bolt.md b/.jules/bolt.md index b8cd366..0261fba 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -9,3 +9,6 @@ ## 2026-04-08 - [Fast Dense Integer Set Tracking] **Learning:** When keeping track of seen integer IDs that are dense and bounded (e.g. from 0 to N), using `new Set()` incurs heavy allocation and insertion overhead compared to a fixed-size byte array. **Action:** Replace `Set` with `new Uint8Array(maxIndex)` and use `array[id] = 1` to track presence, which is ~15x faster and avoids garbage collection pauses in hot paths. (Benchmark context: `N=100,000` IDs, `bun` version 1.2.14, Linux x86_64, Intel Xeon 2.30GHz, 4 cores, 8GB RAM, averaged over 100 iterations comparing `Set` addition vs `new Uint8Array(maxIndex)` indexed assignment `array[id] = 1`). +## 2026-04-30 - [Fast Safe Array Push] +**Learning:** Using the array spread operator (`array.push(...items)`) to merge arrays is convenient but dangerous when the source array is potentially unbounded (e.g., parsing results, large file system traversals). Node.js passes each element as an individual argument to the underlying function, which can trigger a `RangeError: Maximum call stack size exceeded` if the array exceeds ~65,535 items in V8. +**Action:** Replace `array.push(...items)` with a manual `for` loop (`for (let i = 0; i < items.length; i++) array.push(items[i]);`) in paths dealing with dynamically generated or unconstrained array sizes. This safely handles arrays of any length with consistent memory overhead. diff --git a/language-server/src/core/ripgrep-service.ts b/language-server/src/core/ripgrep-service.ts index a2df023..734b045 100644 --- a/language-server/src/core/ripgrep-service.ts +++ b/language-server/src/core/ripgrep-service.ts @@ -129,7 +129,11 @@ export class RipgrepService { try { const batchResults = await this.runRgBatch(baseArgs, batches[batchIndex], remaining, token); if (batchResults.length > 0) { - allResults.push(...batchResults); + // ⚡ Bolt: Fast safe array push + // Prevents 'Maximum Call Stack Size Exceeded' for very large arrays. + for (let i = 0; i < batchResults.length; i++) { + allResults.push(batchResults[i]); + } } } catch { // Ignore batch failure diff --git a/language-server/src/core/search-engine.ts b/language-server/src/core/search-engine.ts index 39e03d7..51dbde3 100644 --- a/language-server/src/core/search-engine.ts +++ b/language-server/src/core/search-engine.ts @@ -282,7 +282,11 @@ export class SearchEngine implements ISearchProvider { } // Append items - this.items.push(...items); + // ⚡ Bolt: Fast safe array push + // Prevents 'Maximum Call Stack Size Exceeded' for very large arrays. + for (let i = 0; i < items.length; i++) { + this.items.push(items[i]); + } const CHUNK_SIZE = 500; for (let i = 0; i < items.length; i += CHUNK_SIZE) { @@ -392,7 +396,11 @@ export class SearchEngine implements ISearchProvider { if (indices && indices.length > 0) { if (!normalizedRemovedPaths.has(normalized)) { normalizedRemovedPaths.add(normalized); - indicesToRemove.push(...indices); + // ⚡ Bolt: Fast safe array push + // Prevents 'Maximum Call Stack Size Exceeded' for very large arrays. + for (let i = 0; i < indices.length; i++) { + indicesToRemove.push(indices[i]); + } } } } @@ -974,7 +982,12 @@ export class SearchEngine implements ISearchProvider { return; } - allResults.push(...providerResults); + // ⚡ Bolt: Fast safe array push + // Prevents 'Maximum Call Stack Size Exceeded' for very large arrays. + for (let i = 0; i < providerResults.length; i++) { + allResults.push(providerResults[i]); + } + if (onResult) { for (const result of providerResults) { if (token?.isCancellationRequested) { diff --git a/language-server/src/core/workspace-indexer.ts b/language-server/src/core/workspace-indexer.ts index e37b158..92c7509 100644 --- a/language-server/src/core/workspace-indexer.ts +++ b/language-server/src/core/workspace-indexer.ts @@ -227,7 +227,11 @@ export class WorkspaceIndexer { if (this.cancellationToken.cancelled) throw new CancellationError(); await this.indexFiles((items) => { - fileItems.push(...items); + // ⚡ Bolt: Fast safe array push + // Prevents 'Maximum Call Stack Size Exceeded' for very large arrays. + for (let i = 0; i < items.length; i++) { + fileItems.push(items[i]); + } this.fireItemsAdded(items); }); diff --git a/language-server/src/indexer-client.ts b/language-server/src/indexer-client.ts index 3ac688c..bbecf4b 100644 --- a/language-server/src/indexer-client.ts +++ b/language-server/src/indexer-client.ts @@ -46,7 +46,11 @@ export class LspIndexerEnvironment implements IndexerEnvironment { nodir: true, }); - results.push(...files); + // ⚡ Bolt: Fast safe array push + // Prevents 'Maximum Call Stack Size Exceeded' for very large arrays. + for (let i = 0; i < files.length; i++) { + results.push(files[i]); + } } return results; } @@ -58,7 +62,11 @@ export class LspIndexerEnvironment implements IndexerEnvironment { for (const folder of this.workspaceFolders) { try { const files = await this.execRgFiles(folder, excludes); - results.push(...files); + // ⚡ Bolt: Fast safe array push + // Prevents 'Maximum Call Stack Size Exceeded' for very large arrays. + for (let i = 0; i < files.length; i++) { + results.push(files[i]); + } } catch { return []; }