(content: string, oldString: string, newString: string, replaceAll = false)
| 680 | } |
| 681 | |
| 682 | export function replace(content: string, oldString: string, newString: string, replaceAll = false): string { |
| 683 | if (oldString === newString) { |
| 684 | throw new Error("No changes to apply: oldString and newString are identical.") |
| 685 | } |
| 686 | if (oldString === "") { |
| 687 | throw new Error( |
| 688 | "oldString cannot be empty when editing an existing file. Provide the exact text to replace, or use write for an intentional full-file replacement.", |
| 689 | ) |
| 690 | } |
| 691 | |
| 692 | let notFound = true |
| 693 | |
| 694 | for (const replacer of [ |
| 695 | SimpleReplacer, |
| 696 | LineTrimmedReplacer, |
| 697 | BlockAnchorReplacer, |
| 698 | WhitespaceNormalizedReplacer, |
| 699 | IndentationFlexibleReplacer, |
| 700 | EscapeNormalizedReplacer, |
| 701 | TrimmedBoundaryReplacer, |
| 702 | ContextAwareReplacer, |
| 703 | MultiOccurrenceReplacer, |
| 704 | ]) { |
| 705 | for (const search of replacer(content, oldString)) { |
| 706 | const index = content.indexOf(search) |
| 707 | if (index === -1) continue |
| 708 | notFound = false |
| 709 | if (isDisproportionateMatch(search, oldString)) { |
| 710 | throw new Error( |
| 711 | "Refusing replacement because the matched span is much larger than oldString. Re-read the file and provide the full exact oldString for the intended replacement.", |
| 712 | ) |
| 713 | } |
| 714 | if (replaceAll) { |
| 715 | return content.replaceAll(search, newString) |
| 716 | } |
| 717 | const lastIndex = content.lastIndexOf(search) |
| 718 | if (index !== lastIndex) continue |
| 719 | return content.substring(0, index) + newString + content.substring(index + search.length) |
| 720 | } |
| 721 | } |
| 722 | |
| 723 | if (notFound) { |
| 724 | throw new Error( |
| 725 | "Could not find oldString in the file. It must match exactly, including whitespace, indentation, and line endings.", |
| 726 | ) |
| 727 | } |
| 728 | throw new Error("Found multiple matches for oldString. Provide more surrounding context to make the match unique.") |
| 729 | } |
| 730 | |
| 731 | function isDisproportionateMatch(search: string, oldString: string) { |
| 732 | const oldLines = oldString.split("\n").length |
no test coverage detected