Skip to content

Commit 3ef81cd

Browse files
authored
fix: Refactor the function constructNewFileContent using a state switching mechanism, and fix the issue of inaccurate SEARCH-REPLACE delimiters generated by some large models through lookahead processing (RooCodeInc#2334)
* Fix the chat context menu removing UTF8 characters causing pure UTF8 character filenames not to display in the menu * fix: Refactor the function constructNewFileContent using a state switching mechanism, and fix the issue of inaccurate SEARCH-REPLACE delimiters generated by some large models through lookahead processing * Merge diff.ts with diff2.ts; Mark the original constructNewFileContent as @deprecated. * Add detailed comments to explain test cases for nested markers
1 parent b5f4460 commit 3ef81cd

File tree

5 files changed

+1056
-1
lines changed

5 files changed

+1056
-1
lines changed

.changeset/modern-seas-relax.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"claude-dev": patch
3+
---
4+
5+
Refactor the function constructNewFileContent using a state switching mechanism, and fix the issue of inaccurate SEARCH-REPLACE delimiters generated by some large models through lookahead processing
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import { constructNewFileContent as cnfc2 } from "./diff"
2+
import { describe, it } from "mocha"
3+
import { expect } from "chai"
4+
5+
async function cnfc(diffContent: string, originalContent: string, isFinal: boolean): Promise<string> {
6+
return cnfc2(diffContent, originalContent, isFinal, "v1")
7+
}
8+
9+
describe("constructNewFileContent", () => {
10+
const testCases = [
11+
{
12+
name: "empty file",
13+
original: "",
14+
diff: `<<<<<<< SEARCH
15+
=======
16+
new content
17+
>>>>>>> REPLACE`,
18+
expected: "new content\n",
19+
isFinal: true,
20+
},
21+
{
22+
name: "full file replacement",
23+
original: "old content",
24+
diff: `<<<<<<< SEARCH
25+
=======
26+
new content
27+
>>>>>>> REPLACE`,
28+
expected: "new content\n",
29+
isFinal: true,
30+
},
31+
{
32+
name: "exact match replacement",
33+
original: "line1\nline2\nline3",
34+
diff: `<<<<<<< SEARCH
35+
line2
36+
=======
37+
replaced
38+
>>>>>>> REPLACE`,
39+
expected: "line1\nreplaced\nline3",
40+
isFinal: true,
41+
},
42+
{
43+
name: "line-trimmed match replacement",
44+
original: "line1\n line2 \nline3",
45+
diff: `<<<<<<< SEARCH
46+
line2
47+
=======
48+
replaced
49+
>>>>>>> REPLACE`,
50+
expected: "line1\nreplaced\nline3",
51+
isFinal: true,
52+
},
53+
{
54+
name: "block anchor match replacement",
55+
original: "line1\nstart\nmiddle\nend\nline5",
56+
diff: `<<<<<<< SEARCH
57+
start
58+
middle
59+
end
60+
=======
61+
replaced
62+
>>>>>>> REPLACE`,
63+
expected: "line1\nreplaced\nline5",
64+
isFinal: true,
65+
},
66+
{
67+
name: "incremental processing",
68+
original: "line1\nline2\nline3",
69+
diff: [
70+
`<<<<<<< SEARCH
71+
line2
72+
=======`,
73+
"replaced\n",
74+
">>>>>>> REPLACE",
75+
].join("\n"),
76+
expected: "line1\nreplaced\n\nline3",
77+
isFinal: true,
78+
},
79+
{
80+
name: "final chunk with remaining content",
81+
original: "line1\nline2\nline3",
82+
diff: `<<<<<<< SEARCH
83+
line2
84+
=======
85+
replaced
86+
>>>>>>> REPLACE`,
87+
expected: "line1\nreplaced\nline3",
88+
isFinal: true,
89+
},
90+
{
91+
name: "multiple ordered replacements",
92+
original: "First\nSecond\nThird\nFourth",
93+
diff: `<<<<<<< SEARCH
94+
First
95+
=======
96+
1st
97+
>>>>>>> REPLACE
98+
99+
<<<<<<< SEARCH
100+
Third
101+
=======
102+
3rd
103+
>>>>>>> REPLACE`,
104+
expected: "1st\nSecond\n3rd\nFourth",
105+
isFinal: true,
106+
},
107+
{
108+
name: "replace then delete",
109+
original: "line1\nline2\nline3\nline4",
110+
diff: `<<<<<<< SEARCH
111+
line2
112+
=======
113+
replaced
114+
>>>>>>> REPLACE
115+
116+
<<<<<<< SEARCH
117+
line4
118+
=======
119+
>>>>>>> REPLACE`,
120+
expected: "line1\nreplaced\nline3\n",
121+
isFinal: true,
122+
},
123+
{
124+
name: "delete then replace",
125+
original: "line1\nline2\nline3\nline4",
126+
diff: `<<<<<<< SEARCH
127+
line1
128+
=======
129+
>>>>>>> REPLACE
130+
131+
<<<<<<< SEARCH
132+
line3
133+
=======
134+
replaced
135+
>>>>>>> REPLACE`,
136+
expected: "line2\nreplaced\nline4",
137+
isFinal: true,
138+
},
139+
]
140+
//.filter(({name}) => name === "multiple ordered replacements")
141+
//.filter(({name}) => name === "delete then replace")
142+
testCases.forEach(({ name, original, diff, expected, isFinal }) => {
143+
it(`should handle ${name} case correctly`, async () => {
144+
const result1 = await cnfc(diff, original, isFinal)
145+
const result2 = await cnfc2(diff, original, isFinal)
146+
const equal = result1 === result2
147+
const equal2 = result1 === expected
148+
// Verify both implementations produce same result
149+
expect(result1).to.equal(result2)
150+
151+
// Verify result matches expected
152+
expect(result1).to.equal(expected)
153+
})
154+
})
155+
156+
it("should throw error when no match found", async () => {
157+
const original = "line1\nline2\nline3"
158+
const diff = `<<<<<<< SEARCH
159+
non-existent
160+
=======
161+
replaced
162+
>>>>>>> REPLACE`
163+
164+
try {
165+
await cnfc(diff, original, true)
166+
expect.fail("Expected an error to be thrown")
167+
} catch (err) {
168+
expect(err).to.be.an("error")
169+
}
170+
171+
try {
172+
await cnfc2(diff, original, true)
173+
expect.fail("Expected an error to be thrown")
174+
} catch (err) {
175+
expect(err).to.be.an("error")
176+
}
177+
})
178+
})

0 commit comments

Comments
 (0)