summaryrefslogtreecommitdiff
path: root/packages/core/src/utils/positionBasedEditProcessor.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/core/src/utils/positionBasedEditProcessor.ts')
-rw-r--r--packages/core/src/utils/positionBasedEditProcessor.ts239
1 files changed, 0 insertions, 239 deletions
diff --git a/packages/core/src/utils/positionBasedEditProcessor.ts b/packages/core/src/utils/positionBasedEditProcessor.ts
deleted file mode 100644
index 977d6859..00000000
--- a/packages/core/src/utils/positionBasedEditProcessor.ts
+++ /dev/null
@@ -1,239 +0,0 @@
-/**
- * @license
- * Copyright 2025 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-
-/**
- * Single edit operation with position information
- */
-export interface PositionedEdit {
- /**
- * Original edit operation
- */
- original: EditOperation;
-
- /**
- * Index in the original edits array
- */
- index: number;
-
- /**
- * Start position in the file content
- */
- startPos: number;
-
- /**
- * End position in the file content
- */
- endPos: number;
-
- /**
- * The replacement text
- */
- newString: string;
-}
-
-/**
- * Result of failed edit with reason
- */
-export interface FailedEdit {
- index: number;
- edit: EditOperation;
- reason: 'not_found' | 'multiple_matches' | 'empty_old_string';
-}
-
-/**
- * Result of position-based edit processing
- */
-export interface PositionBasedEditResult {
- finalContent: string;
- appliedEdits: PositionedEdit[];
- failedEdits: FailedEdit[];
-}
-
-/**
- * Edit operation interface (matches existing EditOperation)
- */
-export interface EditOperation {
- old_string: string;
- new_string: string;
-}
-
-/**
- * Efficient position-based edit processor that minimizes memory usage
- * and applies edits in optimal order to prevent position conflicts.
- */
-export class PositionBasedEditProcessor {
- /**
- * Process edits with minimal memory usage and optimal ordering
- */
- processEdits(
- content: string,
- edits: EditOperation[],
- ): PositionBasedEditResult {
- // Phase 1: Find positions for all edits
- const { validEdits, failedEdits } = this.analyzeEdits(content, edits);
-
- // Phase 2: Sort by position (highest first) to prevent position drift
- const sortedEdits = validEdits.sort((a, b) => b.startPos - a.startPos);
-
- // Phase 3: Build final content in single pass
- const finalContent = this.buildFinalContent(content, sortedEdits);
-
- return {
- finalContent,
- appliedEdits: sortedEdits,
- failedEdits,
- };
- }
-
- /**
- * Analyze all edits and categorize into valid/failed
- */
- private analyzeEdits(
- content: string,
- edits: EditOperation[],
- ): {
- validEdits: PositionedEdit[];
- failedEdits: FailedEdit[];
- } {
- const validEdits: PositionedEdit[] = [];
- const failedEdits: FailedEdit[] = [];
-
- for (let i = 0; i < edits.length; i++) {
- const edit = edits[i];
-
- // Handle empty old_string (file creation case)
- if (edit.old_string === '') {
- failedEdits.push({
- index: i,
- edit,
- reason: 'empty_old_string',
- });
- continue;
- }
-
- // Find all positions of old_string
- const positions = this.findAllPositions(content, edit.old_string);
-
- if (positions.length === 0) {
- failedEdits.push({
- index: i,
- edit,
- reason: 'not_found',
- });
- } else if (positions.length > 1) {
- failedEdits.push({
- index: i,
- edit,
- reason: 'multiple_matches',
- });
- } else {
- // Exactly one match - valid edit
- const startPos = positions[0];
- validEdits.push({
- original: edit,
- index: i,
- startPos,
- endPos: startPos + edit.old_string.length,
- newString: edit.new_string,
- });
- }
- }
-
- return { validEdits, failedEdits };
- }
-
- /**
- * Find all positions where searchString occurs in content
- */
- private findAllPositions(content: string, searchString: string): number[] {
- const positions: number[] = [];
- let index = content.indexOf(searchString);
-
- while (index !== -1) {
- positions.push(index);
- index = content.indexOf(searchString, index + 1);
- }
-
- return positions;
- }
-
- /**
- * Build final content in single pass with minimal memory allocation
- */
- private buildFinalContent(original: string, edits: PositionedEdit[]): string {
- if (edits.length === 0) {
- return original;
- }
-
- // Check for overlapping edits (should not happen with our validation, but safety check)
- if (this.hasOverlappingEdits(edits)) {
- throw new Error('Overlapping edits detected - this should not happen');
- }
-
- // Build segments array working backwards through the string
- const segments: string[] = [];
- let currentPos = original.length;
-
- // Process edits from end to beginning (edits are already sorted by startPos desc)
- for (const edit of edits) {
- // Add text after this edit position
- if (currentPos > edit.endPos) {
- segments.unshift(original.slice(edit.endPos, currentPos));
- }
-
- // Add the replacement text
- segments.unshift(edit.newString);
-
- // Update current position
- currentPos = edit.startPos;
- }
-
- // Add remaining text from beginning
- if (currentPos > 0) {
- segments.unshift(original.slice(0, currentPos));
- }
-
- // Single join operation creates final string
- return segments.join('');
- }
-
- /**
- * Check if any edits overlap (safety validation)
- */
- private hasOverlappingEdits(edits: PositionedEdit[]): boolean {
- for (let i = 0; i < edits.length - 1; i++) {
- for (let j = i + 1; j < edits.length; j++) {
- const edit1 = edits[i];
- const edit2 = edits[j];
-
- // Check if ranges overlap
- if (
- !(edit1.endPos <= edit2.startPos || edit2.endPos <= edit1.startPos)
- ) {
- return true;
- }
- }
- }
-
- return false;
- }
-
- /**
- * Get human-readable error message for failed edit reason
- */
- static getErrorMessage(reason: FailedEdit['reason']): string {
- switch (reason) {
- case 'not_found':
- return 'Old string not found in current content';
- case 'multiple_matches':
- return 'Old string found multiple times - please be more specific';
- case 'empty_old_string':
- return 'Cannot use empty old_string on existing file';
- default:
- return 'Unknown error';
- }
- }
-}