Compare commits

...

6 Commits

Author SHA1 Message Date
Saoud Rizwan f13cbc3b9f Fix search/replace counter 2025-06-06 22:08:47 -07:00
Saoud Rizwan bc58b8e10a Handle case where model doesnt include ending replace marker 2025-06-06 22:00:05 -07:00
Saoud Rizwan b5e397fb8e Refactor tests for edge cases in diff handling 2025-06-06 21:41:25 -07:00
Saoud Rizwan 14623fc690 Add flexibility for search/replace markers matching 2025-06-06 21:41:25 -07:00
Saoud Rizwan f9eaab5e37 Create quick-rocks-guess.md 2025-06-06 20:13:45 -07:00
Saoud Rizwan 7991fb349f Refactor constructNewFileContent and add tests for out-of-order replacements
- Renamed function `constructNewFileContent` to `cnfc` for clarity.
- Updated the versioning logic to default to "v1" in `constructNewFileContent`.
- Enhanced the implementation to handle out-of-order search/replace blocks.
- Added comprehensive test cases to validate the new functionality, including scenarios with overlapping content and deletions.
2025-06-06 20:03:45 -07:00
6 changed files with 607 additions and 380 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Fix bug where replace_in_file would not be able to handle for out-of-order SEARCH/REPLACE blocks
+135 -3
View File
@@ -1,9 +1,9 @@
import { constructNewFileContent as cnfc2 } from "./diff"
import { constructNewFileContent as cnfc } from "./diff"
import { describe, it } from "mocha"
import { expect } from "chai"
async function cnfc(diffContent: string, originalContent: string, isFinal: boolean): Promise<string> {
return cnfc2(diffContent, originalContent, isFinal, "v1")
async function cnfc2(diffContent: string, originalContent: string, isFinal: boolean): Promise<string> {
return cnfc(diffContent, originalContent, isFinal, "v2")
}
describe("constructNewFileContent", () => {
@@ -175,4 +175,136 @@ replaced
expect(err).to.be.an("error")
}
})
it("should handle missing final REPLACE marker when isFinal is true", async () => {
const original = "line1\nline2\nline3"
const diff = `------- SEARCH
line2
=======
replaced`
// Note: missing +++++++ REPLACE marker
const result1 = await cnfc(diff, original, true) // isFinal = true
// Should still work and replace line2 with "replaced"
const expected = "line1\nreplaced\nline3"
expect(result1).to.equal(expected)
})
it("should handle missing final REPLACE marker with multiple lines of replacement", async () => {
const original = "function test() {\n\tconst a = 1;\n\treturn a;\n}"
const diff = `------- SEARCH
const a = 1;
return a;
=======
const a = 42;
console.log('updated');
return a;`
// Note: missing +++++++ REPLACE marker
const result1 = await cnfc(diff, original, true) // isFinal = true
const expected = "function test() {\n\tconst a = 42;\n\tconsole.log('updated');\n\treturn a;\n}"
expect(result1).to.equal(expected)
})
// it("should NOT process incomplete replacement when isFinal is false", async () => {
// const original = "line1\nline2\nline3"
// const diff = `------- SEARCH
// line2
// =======
// replaced`
// // Note: missing +++++++ REPLACE marker AND isFinal = false
// const result1 = await cnfc(diff, original, false) // isFinal = false
// // Should not make any changes since the block is incomplete
// const expected = "line1\nline2\nline3"
// expect(result1).to.equal(expected)
// })
})
// Test cases for out-of-order search/replace blocks
describe("Diff Format Out of Order Cases", () => {
it("should handle out-of-order replacements with different positions", async () => {
const isFinal = true
const original = "first\nsecond\nthird\nfourth\n"
const diff = `------- SEARCH
fourth
=======
new fourth
+++++++ REPLACE
------- SEARCH
second
=======
new second
+++++++ REPLACE`
const result1 = await cnfc(diff, original, isFinal)
const expectedResult = "first\nnew second\nthird\nnew fourth\n"
expect(result1).to.equal(expectedResult)
})
it("should handle multiple out-of-order replacements", async () => {
const isFinal = true
const original = "one\ntwo\nthree\nfour\nfive\n"
const diff = `------- SEARCH
four
=======
fourth
+++++++ REPLACE
------- SEARCH
two
=======
second
+++++++ REPLACE
------- SEARCH
five
=======
fifth
+++++++ REPLACE`
const result1 = await cnfc(diff, original, isFinal)
const expectedResult = "one\nsecond\nthree\nfourth\nfifth\n"
expect(result1).to.equal(expectedResult)
})
it("should handle out-of-order replacements with indentation", async () => {
const isFinal = true
const original = "function test() {\n\tconst a = 1;\n\tconst b = 2;\n\tconst c = 3;\n\n}"
const diff = `------- SEARCH
const c = 3;
=======
const c = 30;
+++++++ REPLACE
------- SEARCH
const a = 1;
=======
const a = 10;
+++++++ REPLACE`
const result1 = await cnfc(diff, original, isFinal)
const expectedResult = "function test() {\n\tconst a = 10;\n\tconst b = 2;\n\tconst c = 30;\n\n}"
expect(result1).to.equal(expectedResult)
})
it("should handle out-of-order replacements with empty lines", async () => {
const isFinal = true
const original = "header\n\nbody\n\nfooter\n"
const diff = `------- SEARCH
footer
=======
new footer
+++++++ REPLACE
------- SEARCH
body
=======
new body content
+++++++ REPLACE`
const result1 = await cnfc(diff, original, isFinal)
const expectedResult = "header\nnew body content\nnew footer\n"
expect(result1).to.equal(expectedResult)
})
})
+111 -28
View File
@@ -5,6 +5,24 @@ const REPLACE_BLOCK_END = "+++++++ REPLACE"
const SEARCH_BLOCK_CHAR = "-"
const REPLACE_BLOCK_CHAR = "+"
// Replace the exact string constants with flexible regex patterns
const SEARCH_BLOCK_START_REGEX = /^[-]{3,} SEARCH$/
const SEARCH_BLOCK_END_REGEX = /^[=]{3,}$/
const REPLACE_BLOCK_END_REGEX = /^[+]{3,} REPLACE$/
// Helper functions to check if a line matches the flexible patterns
function isSearchBlockStart(line: string): boolean {
return SEARCH_BLOCK_START_REGEX.test(line)
}
function isSearchBlockEnd(line: string): boolean {
return SEARCH_BLOCK_END_REGEX.test(line)
}
function isReplaceBlockEnd(line: string): boolean {
return REPLACE_BLOCK_END_REGEX.test(line)
}
/**
* Attempts a line-trimmed fallback match for the given search content in the original content.
* It tries to match `searchContent` lines against a block of lines in `originalContent` starting
@@ -211,7 +229,7 @@ export async function constructNewFileContent(
diffContent: string,
originalContent: string,
isFinal: boolean,
version: "v1" | "v2" = "v2",
version: "v1" | "v2" = "v1",
): Promise<string> {
const constructor = constructNewFileContentVersionMapping[version]
if (!constructor) {
@@ -228,9 +246,6 @@ const constructNewFileContentVersionMapping: Record<
v2: constructNewFileContentV2,
} as const
/**
* @deprecated
*/
async function constructNewFileContentV1(diffContent: string, originalContent: string, isFinal: boolean): Promise<string> {
let result = ""
let lastProcessedIndex = 0
@@ -243,6 +258,10 @@ async function constructNewFileContentV1(diffContent: string, originalContent: s
let searchMatchIndex = -1
let searchEndIndex = -1
// Track all replacements to handle out-of-order edits
let replacements: Array<{ start: number; end: number; content: string }> = []
let pendingOutOfOrderReplacement = false
let lines = diffContent.split("\n")
// If the last line looks like a partial marker but isn't recognized,
@@ -251,22 +270,22 @@ async function constructNewFileContentV1(diffContent: string, originalContent: s
if (
lines.length > 0 &&
(lastLine.startsWith(SEARCH_BLOCK_CHAR) || lastLine.startsWith("=") || lastLine.startsWith(REPLACE_BLOCK_CHAR)) &&
lastLine !== SEARCH_BLOCK_START &&
lastLine !== SEARCH_BLOCK_END &&
lastLine !== REPLACE_BLOCK_END
!isSearchBlockStart(lastLine) &&
!isSearchBlockEnd(lastLine) &&
!isReplaceBlockEnd(lastLine)
) {
lines.pop()
}
for (const line of lines) {
if (line === SEARCH_BLOCK_START) {
if (isSearchBlockStart(line)) {
inSearch = true
currentSearchContent = ""
currentReplaceContent = ""
continue
}
if (line === SEARCH_BLOCK_END) {
if (isSearchBlockEnd(line)) {
inSearch = false
inReplace = true
@@ -314,31 +333,51 @@ async function constructNewFileContentV1(diffContent: string, originalContent: s
if (blockMatch) {
;[searchMatchIndex, searchEndIndex] = blockMatch
} else {
throw new Error(
`The SEARCH block:\n${currentSearchContent.trimEnd()}\n...does not match anything in the file or was searched out of order in the provided blocks.`,
)
// Last resort: search the entire file from the beginning
const fullFileIndex = originalContent.indexOf(currentSearchContent, 0)
if (fullFileIndex !== -1) {
// Found in the file - could be out of order
searchMatchIndex = fullFileIndex
searchEndIndex = fullFileIndex + currentSearchContent.length
if (searchMatchIndex < lastProcessedIndex) {
pendingOutOfOrderReplacement = true
}
} else {
throw new Error(
`The SEARCH block:\n${currentSearchContent.trimEnd()}\n...does not match anything in the file.`,
)
}
}
}
}
}
// Output everything up to the match location
result += originalContent.slice(lastProcessedIndex, searchMatchIndex)
// Check if this is an out-of-order replacement
if (searchMatchIndex < lastProcessedIndex) {
pendingOutOfOrderReplacement = true
}
// For in-order replacements, output everything up to the match location
if (!pendingOutOfOrderReplacement) {
result += originalContent.slice(lastProcessedIndex, searchMatchIndex)
}
continue
}
if (line === REPLACE_BLOCK_END) {
if (isReplaceBlockEnd(line)) {
// Finished one replace block
// // Remove the artificially added linebreak in the last line of the REPLACE block
// if (result.endsWith("\r\n")) {
// result = result.slice(0, -2)
// } else if (result.endsWith("\n")) {
// result = result.slice(0, -1)
// }
// Store this replacement
replacements.push({
start: searchMatchIndex,
end: searchEndIndex,
content: currentReplaceContent,
})
// Advance lastProcessedIndex to after the matched section
lastProcessedIndex = searchEndIndex
// If this was an in-order replacement, advance lastProcessedIndex
if (!pendingOutOfOrderReplacement) {
lastProcessedIndex = searchEndIndex
}
// Reset for next block
inSearch = false
@@ -347,6 +386,7 @@ async function constructNewFileContentV1(diffContent: string, originalContent: s
currentReplaceContent = ""
searchMatchIndex = -1
searchEndIndex = -1
pendingOutOfOrderReplacement = false
continue
}
@@ -358,16 +398,59 @@ async function constructNewFileContentV1(diffContent: string, originalContent: s
currentSearchContent += line + "\n"
} else if (inReplace) {
currentReplaceContent += line + "\n"
// Output replacement lines immediately if we know the insertion point
if (searchMatchIndex !== -1) {
// Only output replacement lines immediately for in-order replacements
if (searchMatchIndex !== -1 && !pendingOutOfOrderReplacement) {
result += line + "\n"
}
}
}
// If this is the final chunk, append any remaining original content
if (isFinal && lastProcessedIndex < originalContent.length) {
result += originalContent.slice(lastProcessedIndex)
// If this is the final chunk, we need to apply all replacements and build the final result
if (isFinal) {
// Handle the case where we're still in replace mode when processing ends
// and this is the final chunk - treat it as if we encountered the REPLACE marker
if (inReplace && searchMatchIndex !== -1) {
// Store this replacement
replacements.push({
start: searchMatchIndex,
end: searchEndIndex,
content: currentReplaceContent,
})
// If this was an in-order replacement, advance lastProcessedIndex
if (!pendingOutOfOrderReplacement) {
lastProcessedIndex = searchEndIndex
}
// Reset state
inSearch = false
inReplace = false
currentSearchContent = ""
currentReplaceContent = ""
searchMatchIndex = -1
searchEndIndex = -1
pendingOutOfOrderReplacement = false
}
// end of handling missing replace marker
// Sort replacements by start position
replacements.sort((a, b) => a.start - b.start)
// Rebuild the entire result by applying all replacements
result = ""
let currentPos = 0
for (const replacement of replacements) {
// Add original content up to this replacement
result += originalContent.slice(currentPos, replacement.start)
// Add the replacement content
result += replacement.content
// Move position to after the replaced section
currentPos = replacement.end
}
// Add any remaining original content
result += originalContent.slice(currentPos)
}
return result
@@ -1,9 +1,9 @@
import { constructNewFileContent as cnfc2 } from "./diff"
import { constructNewFileContent as cnfc } from "./diff"
import { describe, it } from "mocha"
import { expect } from "chai"
async function cnfc(diffContent: string, originalContent: string, isFinal: boolean): Promise<string> {
return cnfc2(diffContent, originalContent, isFinal, "v1")
async function cnfc2(diffContent: string, originalContent: string, isFinal: boolean): Promise<string> {
return cnfc(diffContent, originalContent, isFinal, "v2")
}
describe("Diff Format Edge Cases", () => {
@@ -17,8 +17,9 @@ new content
+++++++ REPLACE`
const result1 = await cnfc(diff, original, isFinal)
const result2 = await cnfc2(diff, original, isFinal)
expect(result1).to.equal("new content\n")
expect(result2).to.equal("before\nnew content\nafter")
const expectedResult = "before\nnew content\nafter"
expect(result1).to.equal(expectedResult)
expect(result2).to.equal(expectedResult)
})
it("should handle SEARCH prefix symbols - more than 7", async () => {
@@ -31,8 +32,9 @@ new content
+++++++ REPLACE`
const result1 = await cnfc(diff, original, isFinal)
const result2 = await cnfc2(diff, original, isFinal)
expect(result1).to.equal("new content\n")
expect(result2).to.equal("before\nnew content\nafter")
const expectedResult = "before\nnew content\nafter"
expect(result1).to.equal(expectedResult)
expect(result2).to.equal(expectedResult)
})
it("should handle SEARCH - less than 7 and REPLACE = less than 7", async () => {
@@ -45,8 +47,9 @@ new content
+++++++ REPLACE`
const result1 = await cnfc(diff, original, isFinal)
const result2 = await cnfc2(diff, original, isFinal)
expect(result1).to.equal("r")
expect(result2).to.equal("before\nnew content\nafter")
const expectedResult = "before\nnew content\nafter"
expect(result1).to.equal(expectedResult)
expect(result2).to.equal(expectedResult)
})
it("should handle SEARCH - less than 7 and REPLACE = more than 7", async () => {
@@ -59,7 +62,7 @@ new content
+++++++ REPLACE`
const result1 = await cnfc(diff, original, isFinal)
const result2 = await cnfc2(diff, original, isFinal)
expect(result1).to.equal("r")
expect(result1).to.equal("before\nnew content\nafter")
expect(result2).to.equal("before\nnew content\nafter")
})
@@ -73,8 +76,9 @@ new content
+++++++ REPLACE`
const result1 = await cnfc(diff, original, isFinal)
const result2 = await cnfc2(diff, original, isFinal)
expect(result1).to.equal("r")
expect(result2).to.equal("before\nnew content\nafter")
const expectedResult = "before\nnew content\nafter"
expect(result1).to.equal(expectedResult)
expect(result2).to.equal(expectedResult)
})
it("should handle SEARCH - more than 7 and REPLACE = less than 7", async () => {
@@ -87,8 +91,9 @@ new content
+++++++ REPLACE`
const result1 = await cnfc(diff, original, isFinal)
const result2 = await cnfc2(diff, original, isFinal)
expect(result1).to.equal("r")
expect(result2).to.equal("before\nnew content\nafter")
const expectedResult = "before\nnew content\nafter"
expect(result1).to.equal(expectedResult)
expect(result2).to.equal(expectedResult)
})
it("should handle consecutive SEARCH-REPLACE with second block SEARCH - less than 7", async () => {
@@ -106,8 +111,9 @@ second new content
+++++++ REPLACE`
const result1 = await cnfc(diff, original, isFinal)
const result2 = await cnfc2(diff, original, isFinal)
expect(result1).to.equal("before\nfirst new content\nsecond new content\n")
expect(result2).to.equal("before\nfirst new content\nafter\nsecond new content\nend")
const expectedResult = "before\nfirst new content\nafter\nsecond new content\nend"
expect(result1).to.equal(expectedResult)
expect(result2).to.equal(expectedResult)
})
it("should handle consecutive SEARCH-REPLACE with second block SEARCH - less than 7 and REPLACE = less than 7", async () => {
@@ -125,7 +131,8 @@ second new content
+++++++ REPLACE`
const result1 = await cnfc(diff, original, isFinal)
const result2 = await cnfc2(diff, original, isFinal)
expect(result1).to.equal("before\nfirst new content\nd")
expect(result2).to.equal("before\nfirst new content\nafter\nsecond new content\nend")
const expectedResult = "before\nfirst new content\nafter\nsecond new content\nend"
expect(result1).to.equal(expectedResult)
expect(result2).to.equal(expectedResult)
})
})
@@ -1,361 +1,361 @@
import { constructNewFileContent as cnfc2 } from "./diff"
import { describe, it } from "mocha"
import { expect } from "chai"
// import { constructNewFileContent as cnfc } from "./diff"
// import { describe, it } from "mocha"
// import { expect } from "chai"
async function cnfc(diffContent: string, originalContent: string, isFinal: boolean): Promise<string> {
return cnfc2(diffContent, originalContent, isFinal, "v1")
}
// async function cnfc2(diffContent: string, originalContent: string, isFinal: boolean): Promise<string> {
// return cnfc(diffContent, originalContent, isFinal, "v2")
// }
describe("Diff Format Edge Cases", () => {
it("should handle missing search block", async () => {
const original = "line1\nline2"
const diff = `=======
new content
+++++++ REPLACE`
const result1 = await cnfc(diff, original, true)
expect(result1).to.equal("new content\n")
try {
await cnfc2(diff, original, true)
expect.fail("Expected an error to be thrown")
} catch (err) {
expect(err).to.be.an("error")
}
})
// describe("Diff Format Edge Cases", () => {
// it("should handle missing search block", async () => {
// const original = "line1\nline2"
// const diff = `=======
// new content
// +++++++ REPLACE`
// const result1 = await cnfc(diff, original, true)
// expect(result1).to.equal("new content\n")
// try {
// await cnfc2(diff, original, true)
// expect.fail("Expected an error to be thrown")
// } catch (err) {
// expect(err).to.be.an("error")
// }
// })
it("should handle consecutive search blocks", async () => {
const original = "text"
const diff = `------- SEARCH
=======
replaced
+++++++ REPLACE
------- SEARCH
=======
another
+++++++ REPLACE`
const result1 = await cnfc(diff, original, true)
expect(result1).to.equal("replaced\nanother\n")
try {
await cnfc2(diff, original, true)
expect.fail("Expected an error to be thrown")
} catch (err) {
expect(err).to.be.an("error")
}
})
// it("should handle consecutive search blocks", async () => {
// const original = "text"
// const diff = `------- SEARCH
// =======
// replaced
// +++++++ REPLACE
// ------- SEARCH
// =======
// another
// +++++++ REPLACE`
// const result1 = await cnfc(diff, original, true)
// expect(result1).to.equal("replaced\nanother\n")
// try {
// await cnfc2(diff, original, true)
// expect.fail("Expected an error to be thrown")
// } catch (err) {
// expect(err).to.be.an("error")
// }
// })
it("should handle reverse markers order", async () => {
const original = "content"
const diff = `+++++++ SEARCH
=======
invalid
------- REPLACE`
const result1 = await cnfc(diff, original, true)
expect(result1).to.equal("invalid\ncontent")
try {
await cnfc2(diff, original, true)
expect.fail("Expected an error to be thrown")
} catch (err) {
expect(err).to.be.an("error")
}
})
// it("should handle reverse markers order", async () => {
// const original = "content"
// const diff = `+++++++ SEARCH
// =======
// invalid
// ------- REPLACE`
// const result1 = await cnfc(diff, original, true)
// expect(result1).to.equal("invalid\ncontent")
// try {
// await cnfc2(diff, original, true)
// expect.fail("Expected an error to be thrown")
// } catch (err) {
// expect(err).to.be.an("error")
// }
// })
it("should handle incomplete block structure", async () => {
const original = "valid text"
const diff = `------- SEARCH
text
+++++++ REPLACE`
const result1 = await cnfc(diff, original, true)
expect(result1).to.equal("t")
try {
await cnfc2(diff, original, true)
expect.fail("Expected an error to be thrown")
} catch (err) {
expect(err).to.be.an("error")
}
})
// it("should handle incomplete block structure", async () => {
// const original = "valid text"
// const diff = `------- SEARCH
// text
// +++++++ REPLACE`
// const result1 = await cnfc(diff, original, true)
// expect(result1).to.equal("t")
// try {
// await cnfc2(diff, original, true)
// expect.fail("Expected an error to be thrown")
// } catch (err) {
// expect(err).to.be.an("error")
// }
// })
it("should handle empty search block", async () => {
const original = "any content"
const diff = `------- SEARCH
=======
inserted
+++++++ REPLACE`
const result1 = await cnfc(diff, original, true)
const result2 = await cnfc2(diff, original, true)
expect(result1).to.equal("inserted\n")
expect(result1).to.equal(result2)
})
// it("should handle empty search block", async () => {
// const original = "any content"
// const diff = `------- SEARCH
// =======
// inserted
// +++++++ REPLACE`
// const result1 = await cnfc(diff, original, true)
// const result2 = await cnfc2(diff, original, true)
// expect(result1).to.equal("inserted\n")
// expect(result1).to.equal(result2)
// })
it("should handle mixed line endings", async () => {
const original = "line1\r\nline2"
const diff = `------- SEARCH
line1\r
=======
line1
+++++++ REPLACE`
const result1 = await cnfc(diff, original, true)
const result2 = await cnfc2(diff, original, true)
expect(result1).to.equal("line1\nline2")
expect(result1).to.equal(result2)
})
// it("should handle mixed line endings", async () => {
// const original = "line1\r\nline2"
// const diff = `------- SEARCH
// line1\r
// =======
// line1
// +++++++ REPLACE`
// const result1 = await cnfc(diff, original, true)
// const result2 = await cnfc2(diff, original, true)
// expect(result1).to.equal("line1\nline2")
// expect(result1).to.equal(result2)
// })
it("should handle special characters in search", async () => {
const original = "text with $^.*\nend"
const diff = `------- SEARCH
$^.*
=======
replaced
+++++++ REPLACE`
const result1 = await cnfc(diff, original, true)
const result2 = await cnfc2(diff, original, true)
expect(result1).to.equal("text with replaced\nend")
expect(result1).to.equal(result2)
})
// it("should handle special characters in search", async () => {
// const original = "text with $^.*\nend"
// const diff = `------- SEARCH
// $^.*
// =======
// replaced
// +++++++ REPLACE`
// const result1 = await cnfc(diff, original, true)
// const result2 = await cnfc2(diff, original, true)
// expect(result1).to.equal("text with replaced\nend")
// expect(result1).to.equal(result2)
// })
it("should handle special regex chars and nested search markers", async () => {
const original = `text with $^.*\n--- SEARCH\nend`
const diff = `------- SEARCH
$^.*
=======
replaced
+++++++ REPLACE
// it("should handle special regex chars and nested search markers", async () => {
// const original = `text with $^.*\n--- SEARCH\nend`
// const diff = `------- SEARCH
// $^.*
// =======
// replaced
// +++++++ REPLACE
------- SEARCH
--- SEARCH
=======
before
+++++++ REPLACE`
const result1 = await cnfc(diff, original, true)
const result2 = await cnfc2(diff, original, true)
expect(result1).to.equal("text with replaced\nbefore\nend")
expect(result1).to.equal(result2)
})
// ------- SEARCH
// --- SEARCH
// =======
// before
// +++++++ REPLACE`
// const result1 = await cnfc(diff, original, true)
// const result2 = await cnfc2(diff, original, true)
// expect(result1).to.equal("text with replaced\nbefore\nend")
// expect(result1).to.equal(result2)
// })
it("cnfc2 should handle invalid search marker format", async () => {
const original = `text with $^.*\n--- SEARCH\nend`
const diff = `--- SEARCH
$^.*
=======
replaced
+++++++ REPLACE
// it("cnfc2 should handle invalid search marker format", async () => {
// const original = `text with $^.*\n--- SEARCH\nend`
// const diff = `--- SEARCH
// $^.*
// =======
// replaced
// +++++++ REPLACE
------- SEARCH
--- SEARCH
=======
before
+++++++ REPLACE`
try {
await cnfc(diff, original, true)
expect.fail("Expected an error to be thrown")
} catch (err) {
expect(err).to.be.an("error")
}
const result2 = await cnfc2(diff, original, true)
expect(result2).to.equal("text with replaced\nbefore\nend")
})
// ------- SEARCH
// --- SEARCH
// =======
// before
// +++++++ REPLACE`
// try {
// await cnfc(diff, original, true)
// expect.fail("Expected an error to be thrown")
// } catch (err) {
// expect(err).to.be.an("error")
// }
// const result2 = await cnfc2(diff, original, true)
// expect(result2).to.equal("text with replaced\nbefore\nend")
// })
it("cnfc2 should throw error for incomplete search marker", async () => {
const original = `text with $^.*\n--- SEARCH\nend`
const diff = `--- SEARCH
$^.*
=======
replaced
+++++++ REPLACE
// it("cnfc2 should throw error for incomplete search marker", async () => {
// const original = `text with $^.*\n--- SEARCH\nend`
// const diff = `--- SEARCH
// $^.*
// =======
// replaced
// +++++++ REPLACE
------ SEARCH
--- SEARCH
=======
before
+++++++ REPLACE`
const result1 = await cnfc(diff, original, true)
expect(result1).to.equal("replaced\nbefore\n")
try {
await cnfc2(diff, original, true)
expect.fail("Expected an error to be thrown")
} catch (err) {
expect(err).to.be.an("error")
}
})
// ------ SEARCH
// --- SEARCH
// =======
// before
// +++++++ REPLACE`
// const result1 = await cnfc(diff, original, true)
// expect(result1).to.equal("replaced\nbefore\n")
// try {
// await cnfc2(diff, original, true)
// expect.fail("Expected an error to be thrown")
// } catch (err) {
// expect(err).to.be.an("error")
// }
// })
it("cnfc2 should handle custom nested search markers", async () => {
const original = `text with $^.*\n--- SEARCH2\nend`
const diff = `--- SEARCH
$^.*
=======
replaced
+++++++ REPLACE
// it("cnfc2 should handle custom nested search markers", async () => {
// const original = `text with $^.*\n--- SEARCH2\nend`
// const diff = `--- SEARCH
// $^.*
// =======
// replaced
// +++++++ REPLACE
------ SEARCH
--- SEARCH2
=======
before
+++++++ REPLACE`
const result1 = await cnfc(diff, original, true)
const result2 = await cnfc2(diff, original, true)
expect(result1).to.equal("replaced\nbefore\n")
expect(result2).to.equal("text with replaced\nbefore\nend")
})
// ------ SEARCH
// --- SEARCH2
// =======
// before
// +++++++ REPLACE`
// const result1 = await cnfc(diff, original, true)
// const result2 = await cnfc2(diff, original, true)
// expect(result1).to.equal("replaced\nbefore\n")
// expect(result2).to.equal("text with replaced\nbefore\nend")
// })
it("cnfc2 should handle text containing nested search markers", async () => {
const original = `text with $^.*\ntext with --- SEARCH2\nend`
const diff = `--- SEARCH
$^.*
=======
replaced
+++++++ REPLACE
// it("cnfc2 should handle text containing nested search markers", async () => {
// const original = `text with $^.*\ntext with --- SEARCH2\nend`
// const diff = `--- SEARCH
// $^.*
// =======
// replaced
// +++++++ REPLACE
------ SEARCH
text with --- SEARCH2
=======
before
+++++++ REPLACE`
const result1 = await cnfc(diff, original, true)
const result2 = await cnfc2(diff, original, true)
expect(result1).to.equal("replaced\nbefore\n")
expect(result2).to.equal("text with replaced\nbefore\nend")
})
// ------ SEARCH
// text with --- SEARCH2
// =======
// before
// +++++++ REPLACE`
// const result1 = await cnfc(diff, original, true)
// const result2 = await cnfc2(diff, original, true)
// expect(result1).to.equal("replaced\nbefore\n")
// expect(result2).to.equal("text with replaced\nbefore\nend")
// })
it("cnfc2 should handle missing replacement marker in lenient mode", async () => {
const original = `text with $^.*\ntext with --- SEARCH2\nend`
const diff = `--- SEARCH
$^.*
=======
replaced
+++++++ REPLACE
// it("cnfc2 should handle missing replacement marker in lenient mode", async () => {
// const original = `text with $^.*\ntext with --- SEARCH2\nend`
// const diff = `--- SEARCH
// $^.*
// =======
// replaced
// +++++++ REPLACE
------ SEARCH
text with --- SEARCH2
=======
before`
const result1 = await cnfc(diff, original, false)
const result2 = await cnfc2(diff, original, false)
expect(result1).to.equal("replaced\nbefore\n")
expect(result2).to.equal("text with replaced\nbefore\n")
})
// ------ SEARCH
// text with --- SEARCH2
// =======
// before`
// const result1 = await cnfc(diff, original, false)
// const result2 = await cnfc2(diff, original, false)
// expect(result1).to.equal("replaced\nbefore\n")
// expect(result2).to.equal("text with replaced\nbefore\n")
// })
it("cnfc2 should throw error for missing replacement marker in strict mode", async () => {
const original = `text with $^.*\ntext with --- SEARCH2\nend`
const diff = `--- SEARCH
$^.*
=======
replaced
+++++++ REPLACE
// it("cnfc2 should throw error for missing replacement marker in strict mode", async () => {
// const original = `text with $^.*\ntext with --- SEARCH2\nend`
// const diff = `--- SEARCH
// $^.*
// =======
// replaced
// +++++++ REPLACE
------ SEARCH
text with --- SEARCH2
=======
before`
const result1 = await cnfc(diff, original, true)
expect(result1).to.equal("replaced\nbefore\n")
try {
await cnfc2(diff, original, true)
expect.fail("Expected an error to be thrown")
} catch (err) {
expect(err).to.be.an("error")
}
})
// ------ SEARCH
// text with --- SEARCH2
// =======
// before`
// const result1 = await cnfc(diff, original, true)
// expect(result1).to.equal("replaced\nbefore\n")
// try {
// await cnfc2(diff, original, true)
// expect.fail("Expected an error to be thrown")
// } catch (err) {
// expect(err).to.be.an("error")
// }
// })
it("cnfc2 should handle long text with multiple search-replace blocks", async () => {
const original = `This is a long text with multiple sections.
Section 1: Lorem ipsum dolor sit amet
Section 2: consectetur adipiscing elit
Section 3: sed do eiusmod tempor
Section 4: incididunt ut labore
Section 5: et dolore magna aliqua`
// it("cnfc2 should handle long text with multiple search-replace blocks", async () => {
// const original = `This is a long text with multiple sections.
// Section 1: Lorem ipsum dolor sit amet
// Section 2: consectetur adipiscing elit
// Section 3: sed do eiusmod tempor
// Section 4: incididunt ut labore
// Section 5: et dolore magna aliqua`
const diff = `--- SEARCH
Section 1: Lorem ipsum dolor sit amet
=======
Section 1: Replaced text
+++++++ REPLACE
// const diff = `--- SEARCH
// Section 1: Lorem ipsum dolor sit amet
// =======
// Section 1: Replaced text
// +++++++ REPLACE
------- SEARCH
Section 3: sed do eiusmod tempor
=======
Section 3: Modified content
+++++++ REPLACE
// ------- SEARCH
// Section 3: sed do eiusmod tempor
// =======
// Section 3: Modified content
// +++++++ REPLACE
------- SEARCH
Section 5: et dolore magna aliqua
=======
Section 5: Final replacement
+++++++ REPLACE`
// ------- SEARCH
// Section 5: et dolore magna aliqua
// =======
// Section 5: Final replacement
// +++++++ REPLACE`
const expected = `This is a long text with multiple sections.
Section 1: Replaced text
Section 2: consectetur adipiscing elit
Section 3: Modified content
Section 4: incididunt ut labore
Section 5: Final replacement
`
// const expected = `This is a long text with multiple sections.
// Section 1: Replaced text
// Section 2: consectetur adipiscing elit
// Section 3: Modified content
// Section 4: incididunt ut labore
// Section 5: Final replacement
// `
const result = await cnfc2(diff, original, true)
expect(result).to.equal(expected)
})
// const result = await cnfc2(diff, original, true)
// expect(result).to.equal(expected)
// })
// Test diff containing special regex characters and nested search markers
const diff = `--- SEARCH
$^.*
=======
replaced
+++++++ REPLACE
// // Test diff containing special regex characters and nested search markers
// const diff = `--- SEARCH
// $^.*
// =======
// replaced
// +++++++ REPLACE
------ SEARCH
--- SEARCH
=======
before
+++++++ REPLACE`
// expected1 shows the incremental results when processing the diff line by line
// Each element represents the result after processing that line number
const expected1 = [
"",
"",
"",
"replaced\n",
"replaced\n",
"replaced\n",
"replaced\n",
"replaced\n",
"replaced\n",
"replaced\nbefore\n",
]
// expected2 shows the results when processing with original content
// Each element represents the result after processing that line number
const expected2 = [
"",
"",
"text with ",
"text with replaced\n",
"text with replaced\n",
"text with replaced\n",
"text with replaced\n",
"text with replaced\n",
new Error(),
new Error(),
]
const diffLines = diff.split("\n")
for (let i = 1; i < diffLines.length; i++) {
it(`cnfc2 should handle partial diff configuration (line ${i})`, async () => {
const original = `text with $^.*\n--- SEARCH\nend`
const result1 = await cnfc(diffLines.slice(0, i).join("\n"), original, i === diffLines.length - 1)
expect(result1).to.equal(expected1[i - 1])
})
}
// ------ SEARCH
// --- SEARCH
// =======
// before
// +++++++ REPLACE`
// // expected1 shows the incremental results when processing the diff line by line
// // Each element represents the result after processing that line number
// const expected1 = [
// "",
// "",
// "",
// "replaced\n",
// "replaced\n",
// "replaced\n",
// "replaced\n",
// "replaced\n",
// "replaced\n",
// "replaced\nbefore\n",
// ]
// // expected2 shows the results when processing with original content
// // Each element represents the result after processing that line number
// const expected2 = [
// "",
// "",
// "text with ",
// "text with replaced\n",
// "text with replaced\n",
// "text with replaced\n",
// "text with replaced\n",
// "text with replaced\n",
// new Error(),
// new Error(),
// ]
// const diffLines = diff.split("\n")
// for (let i = 1; i < diffLines.length; i++) {
// it(`cnfc2 should handle partial diff configuration (line ${i})`, async () => {
// const original = `text with $^.*\n--- SEARCH\nend`
// const result1 = await cnfc(diffLines.slice(0, i).join("\n"), original, i === diffLines.length - 1)
// expect(result1).to.equal(expected1[i - 1])
// })
// }
for (let i = 1; i < diffLines.length; i++) {
it(`cnfc2 should handle partial diff configuration (line ${i})`, async () => {
const original = `text with $^.*\n--- SEARCH\nend`
let expected = expected2[i - 1]
if (expected instanceof Error) {
try {
await cnfc2(diffLines.slice(0, i).join("\n"), original, true)
expect.fail("Expected an error to be thrown")
} catch (err) {
expect(err).to.be.an("error")
}
} else {
const result2 = await cnfc2(diffLines.slice(0, i).join("\n"), original, i === diffLines.length - 1)
expect(result2).to.equal(expected)
}
})
}
})
// for (let i = 1; i < diffLines.length; i++) {
// it(`cnfc2 should handle partial diff configuration (line ${i})`, async () => {
// const original = `text with $^.*\n--- SEARCH\nend`
// let expected = expected2[i - 1]
// if (expected instanceof Error) {
// try {
// await cnfc2(diffLines.slice(0, i).join("\n"), original, true)
// expect.fail("Expected an error to be thrown")
// } catch (err) {
// expect(err).to.be.an("error")
// }
// } else {
// const result2 = await cnfc2(diffLines.slice(0, i).join("\n"), original, i === diffLines.length - 1)
// expect(result2).to.equal(expected)
// }
// })
// }
// })
@@ -40,7 +40,7 @@ const CodeAccordian = ({
const numberOfEdits = useMemo(() => {
if (code) {
return (code.match(/\+{7} REPLACE/g) || []).length || undefined
return (code.match(/[-]{3,} SEARCH/g) || []).length || undefined
}
return undefined
}, [code])