mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
More tolerant search/replace match (#6537)
Co-authored-by: Roo Code <roomote@roocode.com>
This commit is contained in:
parent
305a5da369
commit
ebfd384ac4
3 changed files with 64 additions and 13 deletions
|
|
@ -13,6 +13,51 @@ describe("MultiSearchReplaceDiffStrategy", () => {
|
|||
expect(strategy["validateMarkerSequencing"](diff).success).toBe(true)
|
||||
})
|
||||
|
||||
it("validates correct marker sequence with extra > in SEARCH", () => {
|
||||
const diff = "<<<<<<< SEARCH>\n" + "some content\n" + "=======\n" + "new content\n" + ">>>>>>> REPLACE"
|
||||
expect(strategy["validateMarkerSequencing"](diff).success).toBe(true)
|
||||
})
|
||||
|
||||
it("validates correct marker sequence with multiple > in SEARCH", () => {
|
||||
const diff = "<<<<<<< SEARCH>>\n" + "some content\n" + "=======\n" + "new content\n" + ">>>>>>> REPLACE"
|
||||
expect(strategy["validateMarkerSequencing"](diff).success).toBe(false)
|
||||
})
|
||||
|
||||
it("validates mixed cases with and without extra > in the same diff", () => {
|
||||
const diff =
|
||||
"<<<<<<< SEARCH>\n" +
|
||||
"content1\n" +
|
||||
"=======\n" +
|
||||
"new1\n" +
|
||||
">>>>>>> REPLACE\n\n" +
|
||||
"<<<<<<< SEARCH\n" +
|
||||
"content2\n" +
|
||||
"=======\n" +
|
||||
"new2\n" +
|
||||
">>>>>>> REPLACE"
|
||||
expect(strategy["validateMarkerSequencing"](diff).success).toBe(true)
|
||||
})
|
||||
|
||||
it("validates extra > with whitespace variations", () => {
|
||||
const diff1 = "<<<<<<< SEARCH> \n" + "some content\n" + "=======\n" + "new content\n" + ">>>>>>> REPLACE"
|
||||
expect(strategy["validateMarkerSequencing"](diff1).success).toBe(true)
|
||||
|
||||
const diff2 = "<<<<<<< SEARCH >\n" + "some content\n" + "=======\n" + "new content\n" + ">>>>>>> REPLACE"
|
||||
expect(strategy["validateMarkerSequencing"](diff2).success).toBe(false)
|
||||
})
|
||||
|
||||
it("validates extra > with line numbers", () => {
|
||||
const diff =
|
||||
"<<<<<<< SEARCH>\n" +
|
||||
":start_line:10\n" +
|
||||
"-------\n" +
|
||||
"content1\n" +
|
||||
"=======\n" +
|
||||
"new1\n" +
|
||||
">>>>>>> REPLACE"
|
||||
expect(strategy["validateMarkerSequencing"](diff).success).toBe(true)
|
||||
})
|
||||
|
||||
it("validates multiple correct marker sequences", () => {
|
||||
const diff =
|
||||
"<<<<<<< SEARCH\n" +
|
||||
|
|
|
|||
|
|
@ -259,7 +259,10 @@ Each file requires its own path, start_line, and diff elements.
|
|||
|
||||
const state = { current: State.START, line: 0 }
|
||||
|
||||
const SEARCH = "<<<<<<< SEARCH"
|
||||
// Pattern allows optional '>' after SEARCH to handle AI-generated diffs
|
||||
// (e.g., Sonnet 4 sometimes adds an extra '>')
|
||||
const SEARCH_PATTERN = /^<<<<<<< SEARCH>?$/
|
||||
const SEARCH = SEARCH_PATTERN.source.replace(/[\^$]/g, "") // Remove regex anchors for display
|
||||
const SEP = "======="
|
||||
const REPLACE = ">>>>>>> REPLACE"
|
||||
const SEARCH_PREFIX = "<<<<<<< "
|
||||
|
|
@ -329,7 +332,7 @@ Each file requires its own path, start_line, and diff elements.
|
|||
})
|
||||
|
||||
const lines = diffContent.split("\n")
|
||||
const searchCount = lines.filter((l) => l.trim() === SEARCH).length
|
||||
const searchCount = lines.filter((l) => SEARCH_PATTERN.test(l.trim())).length
|
||||
const sepCount = lines.filter((l) => l.trim() === SEP).length
|
||||
const replaceCount = lines.filter((l) => l.trim() === REPLACE).length
|
||||
|
||||
|
|
@ -357,12 +360,12 @@ Each file requires its own path, start_line, and diff elements.
|
|||
: reportMergeConflictError(SEP, SEARCH)
|
||||
if (marker === REPLACE) return reportInvalidDiffError(REPLACE, SEARCH)
|
||||
if (marker.startsWith(REPLACE_PREFIX)) return reportMergeConflictError(marker, SEARCH)
|
||||
if (marker === SEARCH) state.current = State.AFTER_SEARCH
|
||||
if (SEARCH_PATTERN.test(marker)) state.current = State.AFTER_SEARCH
|
||||
else if (marker.startsWith(SEARCH_PREFIX)) return reportMergeConflictError(marker, SEARCH)
|
||||
break
|
||||
|
||||
case State.AFTER_SEARCH:
|
||||
if (marker === SEARCH) return reportInvalidDiffError(SEARCH, SEP)
|
||||
if (SEARCH_PATTERN.test(marker)) return reportInvalidDiffError(SEARCH_PATTERN.source, SEP)
|
||||
if (marker.startsWith(SEARCH_PREFIX)) return reportMergeConflictError(marker, SEARCH)
|
||||
if (marker === REPLACE) return reportInvalidDiffError(REPLACE, SEP)
|
||||
if (marker.startsWith(REPLACE_PREFIX)) return reportMergeConflictError(marker, SEARCH)
|
||||
|
|
@ -370,7 +373,7 @@ Each file requires its own path, start_line, and diff elements.
|
|||
break
|
||||
|
||||
case State.AFTER_SEPARATOR:
|
||||
if (marker === SEARCH) return reportInvalidDiffError(SEARCH, REPLACE)
|
||||
if (SEARCH_PATTERN.test(marker)) return reportInvalidDiffError(SEARCH_PATTERN.source, REPLACE)
|
||||
if (marker.startsWith(SEARCH_PREFIX)) return reportMergeConflictError(marker, REPLACE)
|
||||
if (marker === SEP)
|
||||
return likelyBadStructure
|
||||
|
|
@ -456,7 +459,7 @@ Each file requires its own path, start_line, and diff elements.
|
|||
|
||||
/* Regex parts:
|
||||
1. (?:^|\n) Ensures the first marker starts at the beginning of the file or right after a newline.
|
||||
2. (?<!\\)<<<<<<< SEARCH\s*\n Matches the line "<<<<<<< SEARCH" (ignoring any trailing spaces) – the negative lookbehind makes sure it isn't escaped.
|
||||
2. (?<!\\)<<<<<<< SEARCH>?\s*\n Matches the line "<<<<<<< SEARCH" with optional '>' (ignoring any trailing spaces) – the negative lookbehind makes sure it isn't escaped.
|
||||
3. ((?:\:start_line:\s*(\d+)\s*\n))? Optionally matches a ":start_line:" line. The outer capturing group is group 1 and the inner (\d+) is group 2.
|
||||
4. ((?:\:end_line:\s*(\d+)\s*\n))? Optionally matches a ":end_line:" line. Group 3 is the whole match and group 4 is the digits.
|
||||
5. ((?<!\\)-------\s*\n)? Optionally matches the "-------" marker line (group 5).
|
||||
|
|
@ -467,7 +470,7 @@ Each file requires its own path, start_line, and diff elements.
|
|||
*/
|
||||
let matches = [
|
||||
...diffContent.matchAll(
|
||||
/(?:^|\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,
|
||||
/(?:^|\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,
|
||||
),
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -198,7 +198,10 @@ Only use a single line of '=======' between search and replacement content, beca
|
|||
}
|
||||
const state = { current: State.START, line: 0 }
|
||||
|
||||
const SEARCH = "<<<<<<< SEARCH"
|
||||
// Pattern allows optional '>' after SEARCH to handle AI-generated diffs
|
||||
// (e.g., Sonnet 4 sometimes adds an extra '>')
|
||||
const SEARCH_PATTERN = /^<<<<<<< SEARCH>?$/
|
||||
const SEARCH = SEARCH_PATTERN.source.replace(/[\^$]/g, "") // Remove regex anchors for display
|
||||
const SEP = "======="
|
||||
const REPLACE = ">>>>>>> REPLACE"
|
||||
const SEARCH_PREFIX = "<<<<<<<"
|
||||
|
|
@ -268,7 +271,7 @@ Only use a single line of '=======' between search and replacement content, beca
|
|||
})
|
||||
|
||||
const lines = diffContent.split("\n")
|
||||
const searchCount = lines.filter((l) => l.trim() === SEARCH).length
|
||||
const searchCount = lines.filter((l) => SEARCH_PATTERN.test(l.trim())).length
|
||||
const sepCount = lines.filter((l) => l.trim() === SEP).length
|
||||
const replaceCount = lines.filter((l) => l.trim() === REPLACE).length
|
||||
|
||||
|
|
@ -296,12 +299,12 @@ Only use a single line of '=======' between search and replacement content, beca
|
|||
: reportMergeConflictError(SEP, SEARCH)
|
||||
if (marker === REPLACE) return reportInvalidDiffError(REPLACE, SEARCH)
|
||||
if (marker.startsWith(REPLACE_PREFIX)) return reportMergeConflictError(marker, SEARCH)
|
||||
if (marker === SEARCH) state.current = State.AFTER_SEARCH
|
||||
if (SEARCH_PATTERN.test(marker)) state.current = State.AFTER_SEARCH
|
||||
else if (marker.startsWith(SEARCH_PREFIX)) return reportMergeConflictError(marker, SEARCH)
|
||||
break
|
||||
|
||||
case State.AFTER_SEARCH:
|
||||
if (marker === SEARCH) return reportInvalidDiffError(SEARCH, SEP)
|
||||
if (SEARCH_PATTERN.test(marker)) return reportInvalidDiffError(SEARCH_PATTERN.source, SEP)
|
||||
if (marker.startsWith(SEARCH_PREFIX)) return reportMergeConflictError(marker, SEARCH)
|
||||
if (marker === REPLACE) return reportInvalidDiffError(REPLACE, SEP)
|
||||
if (marker.startsWith(REPLACE_PREFIX)) return reportMergeConflictError(marker, SEARCH)
|
||||
|
|
@ -309,7 +312,7 @@ Only use a single line of '=======' between search and replacement content, beca
|
|||
break
|
||||
|
||||
case State.AFTER_SEPARATOR:
|
||||
if (marker === SEARCH) return reportInvalidDiffError(SEARCH, REPLACE)
|
||||
if (SEARCH_PATTERN.test(marker)) return reportInvalidDiffError(SEARCH_PATTERN.source, REPLACE)
|
||||
if (marker.startsWith(SEARCH_PREFIX)) return reportMergeConflictError(marker, REPLACE)
|
||||
if (marker === SEP)
|
||||
return likelyBadStructure
|
||||
|
|
@ -378,7 +381,7 @@ Only use a single line of '=======' between search and replacement content, beca
|
|||
|
||||
let matches = [
|
||||
...diffContent.matchAll(
|
||||
/(?:^|\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,
|
||||
/(?:^|\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,
|
||||
),
|
||||
]
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue