Don't break if an end_line is passed into a diff (#2743)

This commit is contained in:
Matt Rubens 2025-04-18 02:23:55 -04:00 committed by GitHub
parent 87af3b3424
commit 06882f5687
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 31 additions and 8 deletions

View file

@ -159,6 +159,25 @@ function helloWorld() {
}
})
it("should replace matching content when end_line is passed in", async () => {
const originalContent = 'function hello() {\n console.log("hello")\n}\n'
const diffContent = `test.ts
<<<<<<< SEARCH
:start_line:1
:end_line:1
-------
function hello() {
=======
function helloWorld() {
>>>>>>> REPLACE`
const result = await strategy.applyDiff(originalContent, diffContent)
expect(result.success).toBe(true)
if (result.success) {
expect(result.content).toBe('function helloWorld() {\n console.log("hello")\n}\n')
}
})
it("should match content with different surrounding whitespace", async () => {
const originalContent = "\nfunction example() {\n return 42;\n}\n\n"
const diffContent = `test.ts

View file

@ -186,6 +186,7 @@ Only use a single line of '=======' between search and replacement content, beca
.replace(/^\\=======/gm, "=======")
.replace(/^\\>>>>>>>/gm, ">>>>>>>")
.replace(/^\\-------/gm, "-------")
.replace(/^\\:end_line:/gm, ":end_line:")
.replace(/^\\:start_line:/gm, ":start_line:")
}
@ -322,25 +323,28 @@ Only use a single line of '=======' between search and replacement content, beca
3. ((?:\:start_line:\s*(\d+)\s*\n))?
Optionally matches a :start_line: line. The outer capturing group is group1 and the inner (\d+) is group2.
4. ((?<!\\)-------\s*\n)?
4. ((?:\:end_line:\s*(\d+)\s*\n))?
Optionally matches a :end_line: line. Group3 is the whole match and group4 is the digits.
5. ((?<!\\)-------\s*\n)?
Optionally matches the ------- marker line (group5).
5. ([\s\S]*?)(?:\n)?
6. ([\s\S]*?)(?:\n)?
Nongreedy match for the search content (group6) up to the next marker.
6. (?:(?<=\n)(?<!\\)=======\s*\n)
7. (?:(?<=\n)(?<!\\)=======\s*\n)
Matches the ======= marker on its own line.
7. ([\s\S]*?)(?:\n)?
8. ([\s\S]*?)(?:\n)?
Nongreedy match for the replace content (group7).
8. (?:(?<=\n)(?<!\\)>>>>>>> REPLACE)(?=\n|$)
9. (?:(?<=\n)(?<!\\)>>>>>>> REPLACE)(?=\n|$)
Matches the final >>>>>>> REPLACE marker on its own line (and requires a following newline or the end of file).
*/
let matches = [
...diffContent.matchAll(
/(?:^|\n)(?<!\\)<<<<<<< SEARCH\s*\n((?:\:start_line:\s*(\d+)\s*\n))?((?<!\\)-------\s*\n)?([\s\S]*?)(?:\n)?(?:(?<=\n)(?<!\\)=======\s*\n)([\s\S]*?)(?:\n)?(?:(?<=\n)(?<!\\)>>>>>>> REPLACE)(?=\n|$)/g,
/(?:^|\n)(?<!\\)<<<<<<< SEARCH\s*\n((?:\:start_line:\s*(\d+)\s*\n))?((?:\:end_line:\s*(\d+)\s*\n))?((?<!\\)-------\s*\n)?([\s\S]*?)(?:\n)?(?:(?<=\n)(?<!\\)=======\s*\n)([\s\S]*?)(?:\n)?(?:(?<=\n)(?<!\\)>>>>>>> REPLACE)(?=\n|$)/g,
),
]
@ -359,8 +363,8 @@ Only use a single line of '=======' between search and replacement content, beca
const replacements = matches
.map((match) => ({
startLine: Number(match[2] ?? 0),
searchContent: match[4],
replaceContent: match[5],
searchContent: match[6],
replaceContent: match[7],
}))
.sort((a, b) => a.startLine - b.startLine)