diff --git a/src/core/diff/strategies/__tests__/multi-search-replace.test.ts b/src/core/diff/strategies/__tests__/multi-search-replace.test.ts index 8aec76407e..2187b4a958 100644 --- a/src/core/diff/strategies/__tests__/multi-search-replace.test.ts +++ b/src/core/diff/strategies/__tests__/multi-search-replace.test.ts @@ -28,18 +28,57 @@ describe("MultiSearchReplaceDiffStrategy", () => { expect(strategy["validateMarkerSequencing"](diff).success).toBe(true) }) + it("validates multiple correct marker sequences with line numbers", () => { + const diff = + "<<<<<<< SEARCH\n" + + ":start_line:10\n" + + ":end_line:11\n" + + "-------\n" + + "content1\n" + + "=======\n" + + "new1\n" + + ">>>>>>> REPLACE\n\n" + + "<<<<<<< SEARCH\n" + + ":start_line:10\n" + + ":end_line:11\n" + + "-------\n" + + "content2\n" + + "=======\n" + + "new2\n" + + ">>>>>>> REPLACE" + expect(strategy["validateMarkerSequencing"](diff).success).toBe(true) + }) + it("detects separator before search", () => { const diff = "=======\n" + "content\n" + ">>>>>>> REPLACE" const result = strategy["validateMarkerSequencing"](diff) expect(result.success).toBe(false) expect(result.error).toContain("'=======' found in your diff content") + expect(result.error).toContain("Diff block is malformed") }) - it("detects replace before separator", () => { + it("detects missing separator", () => { const diff = "<<<<<<< SEARCH\n" + "content\n" + ">>>>>>> REPLACE" const result = strategy["validateMarkerSequencing"](diff) expect(result.success).toBe(false) expect(result.error).toContain("'>>>>>>> REPLACE' found in your diff content") + expect(result.error).toContain("Diff block is malformed") + }) + + it("detects two separators", () => { + const diff = "<<<<<<< SEARCH\n" + "content\n" + "=======\n" + "=======\n" + ">>>>>>> REPLACE" + const result = strategy["validateMarkerSequencing"](diff) + expect(result.success).toBe(false) + expect(result.error).toContain("'=======' found in your diff content") + expect(result.error).toContain("When removing merge conflict markers") + }) + + it("detects replace before separator (merge conflict message)", () => { + const diff = "<<<<<<< SEARCH\n" + "content\n" + ">>>>>>>" + const result = strategy["validateMarkerSequencing"](diff) + expect(result.success).toBe(false) + expect(result.error).toContain("'>>>>>>>' found in your diff content") + expect(result.error).toContain("When removing merge conflict markers") }) it("detects incomplete sequence", () => { diff --git a/src/core/diff/strategies/multi-search-replace.ts b/src/core/diff/strategies/multi-search-replace.ts index b4c0920843..8213669aa6 100644 --- a/src/core/diff/strategies/multi-search-replace.ts +++ b/src/core/diff/strategies/multi-search-replace.ts @@ -162,8 +162,10 @@ Only use a single line of '=======' between search and replacement content, beca const SEARCH = "<<<<<<< SEARCH" const SEP = "=======" const REPLACE = ">>>>>>> REPLACE" + const SEARCH_PREFIX = "<<<<<<<" + const REPLACE_PREFIX = ">>>>>>>" - const reportError = (found: string, expected: string) => ({ + const reportMergeConflictError = (found: string, expected: string) => ({ success: false, error: `ERROR: Special marker '${found}' found in your diff content at line ${state.line}:\n` + @@ -187,27 +189,62 @@ Only use a single line of '=======' between search and replacement content, beca `\\${REPLACE}\n`, }) + const reportInvalidDiffError = (found: string, expected: string) => ({ + success: false, + error: + `ERROR: Diff block is malformed: marker '${found}' found in your diff content at line ${state.line}. Expected: ${expected}\n` + + "\n" + + "CORRECT FORMAT:\n\n" + + "<<<<<<< SEARCH\n" + + ":start_line: (required) The line number of original content where the search block starts.\n" + + ":end_line: (required) The line number of original content where the search block ends.\n" + + "-------\n" + + "[exact content to find including whitespace]\n" + + "=======\n" + + "[new content to replace with]\n" + + ">>>>>>> REPLACE\n", + }) + + const lines = diffContent.split("\n") + const searchCount = lines.filter((l) => l.trim() === SEARCH).length + const sepCount = lines.filter((l) => l.trim() === SEP).length + const replaceCount = lines.filter((l) => l.trim() === REPLACE).length + + const likelyBadStructure = searchCount !== replaceCount || sepCount < searchCount + for (const line of diffContent.split("\n")) { state.line++ const marker = line.trim() switch (state.current) { case State.START: - if (marker === SEP) return reportError(SEP, SEARCH) - if (marker === REPLACE) return reportError(REPLACE, SEARCH) + if (marker === SEP) + return likelyBadStructure + ? reportInvalidDiffError(SEP, SEARCH) + : 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 + else if (marker.startsWith(SEARCH_PREFIX)) return reportMergeConflictError(marker, SEARCH) break case State.AFTER_SEARCH: - if (marker === SEARCH) return reportError(SEARCH, SEP) - if (marker === REPLACE) return reportError(REPLACE, SEP) + if (marker === SEARCH) return reportInvalidDiffError(SEARCH, 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) if (marker === SEP) state.current = State.AFTER_SEPARATOR break case State.AFTER_SEPARATOR: - if (marker === SEARCH) return reportError(SEARCH, REPLACE) - if (marker === SEP) return reportError(SEP, REPLACE) + if (marker === SEARCH) return reportInvalidDiffError(SEARCH, REPLACE) + if (marker.startsWith(SEARCH_PREFIX)) return reportMergeConflictError(marker, REPLACE) + if (marker === SEP) + return likelyBadStructure + ? reportInvalidDiffError(SEP, REPLACE) + : reportMergeConflictError(SEP, REPLACE) if (marker === REPLACE) state.current = State.START + else if (marker.startsWith(REPLACE_PREFIX)) return reportMergeConflictError(marker, REPLACE) break } }