mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-04-01 06:42:26 +00:00
fix(opencode): preserve original line endings in 'edit' tool (#9443)
Co-authored-by: LukeParkerDev <10430890+Hona@users.noreply.github.com>
This commit is contained in:
@@ -451,6 +451,189 @@ describe("tool.edit", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("line endings", () => {
|
||||
const old = "alpha\nbeta\ngamma"
|
||||
const next = "alpha\nbeta-updated\ngamma"
|
||||
const alt = "alpha\nbeta\nomega"
|
||||
|
||||
const normalize = (text: string, ending: "\n" | "\r\n") => {
|
||||
const normalized = text.replaceAll("\r\n", "\n")
|
||||
if (ending === "\n") return normalized
|
||||
return normalized.replaceAll("\n", "\r\n")
|
||||
}
|
||||
|
||||
const count = (content: string) => {
|
||||
const crlf = content.match(/\r\n/g)?.length ?? 0
|
||||
const lf = content.match(/\n/g)?.length ?? 0
|
||||
return {
|
||||
crlf,
|
||||
lf: lf - crlf,
|
||||
}
|
||||
}
|
||||
|
||||
const expectLf = (content: string) => {
|
||||
const counts = count(content)
|
||||
expect(counts.crlf).toBe(0)
|
||||
expect(counts.lf).toBeGreaterThan(0)
|
||||
}
|
||||
|
||||
const expectCrlf = (content: string) => {
|
||||
const counts = count(content)
|
||||
expect(counts.lf).toBe(0)
|
||||
expect(counts.crlf).toBeGreaterThan(0)
|
||||
}
|
||||
|
||||
type Input = {
|
||||
content: string
|
||||
oldString: string
|
||||
newString: string
|
||||
replaceAll?: boolean
|
||||
}
|
||||
|
||||
const apply = async (input: Input) => {
|
||||
await using tmp = await tmpdir({
|
||||
init: async (dir) => {
|
||||
await Bun.write(path.join(dir, "test.txt"), input.content)
|
||||
},
|
||||
})
|
||||
|
||||
return await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const edit = await EditTool.init()
|
||||
const filePath = path.join(tmp.path, "test.txt")
|
||||
FileTime.read(ctx.sessionID, filePath)
|
||||
await edit.execute(
|
||||
{
|
||||
filePath,
|
||||
oldString: input.oldString,
|
||||
newString: input.newString,
|
||||
replaceAll: input.replaceAll,
|
||||
},
|
||||
ctx,
|
||||
)
|
||||
return await Bun.file(filePath).text()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
test("preserves LF with LF multi-line strings", async () => {
|
||||
const content = normalize(old + "\n", "\n")
|
||||
const output = await apply({
|
||||
content,
|
||||
oldString: normalize(old, "\n"),
|
||||
newString: normalize(next, "\n"),
|
||||
})
|
||||
expect(output).toBe(normalize(next + "\n", "\n"))
|
||||
expectLf(output)
|
||||
})
|
||||
|
||||
test("preserves CRLF with CRLF multi-line strings", async () => {
|
||||
const content = normalize(old + "\n", "\r\n")
|
||||
const output = await apply({
|
||||
content,
|
||||
oldString: normalize(old, "\r\n"),
|
||||
newString: normalize(next, "\r\n"),
|
||||
})
|
||||
expect(output).toBe(normalize(next + "\n", "\r\n"))
|
||||
expectCrlf(output)
|
||||
})
|
||||
|
||||
test("preserves LF when old/new use CRLF", async () => {
|
||||
const content = normalize(old + "\n", "\n")
|
||||
const output = await apply({
|
||||
content,
|
||||
oldString: normalize(old, "\r\n"),
|
||||
newString: normalize(next, "\r\n"),
|
||||
})
|
||||
expect(output).toBe(normalize(next + "\n", "\n"))
|
||||
expectLf(output)
|
||||
})
|
||||
|
||||
test("preserves CRLF when old/new use LF", async () => {
|
||||
const content = normalize(old + "\n", "\r\n")
|
||||
const output = await apply({
|
||||
content,
|
||||
oldString: normalize(old, "\n"),
|
||||
newString: normalize(next, "\n"),
|
||||
})
|
||||
expect(output).toBe(normalize(next + "\n", "\r\n"))
|
||||
expectCrlf(output)
|
||||
})
|
||||
|
||||
test("preserves LF when newString uses CRLF", async () => {
|
||||
const content = normalize(old + "\n", "\n")
|
||||
const output = await apply({
|
||||
content,
|
||||
oldString: normalize(old, "\n"),
|
||||
newString: normalize(next, "\r\n"),
|
||||
})
|
||||
expect(output).toBe(normalize(next + "\n", "\n"))
|
||||
expectLf(output)
|
||||
})
|
||||
|
||||
test("preserves CRLF when newString uses LF", async () => {
|
||||
const content = normalize(old + "\n", "\r\n")
|
||||
const output = await apply({
|
||||
content,
|
||||
oldString: normalize(old, "\r\n"),
|
||||
newString: normalize(next, "\n"),
|
||||
})
|
||||
expect(output).toBe(normalize(next + "\n", "\r\n"))
|
||||
expectCrlf(output)
|
||||
})
|
||||
|
||||
test("preserves LF with mixed old/new line endings", async () => {
|
||||
const content = normalize(old + "\n", "\n")
|
||||
const output = await apply({
|
||||
content,
|
||||
oldString: "alpha\nbeta\r\ngamma",
|
||||
newString: "alpha\r\nbeta\nomega",
|
||||
})
|
||||
expect(output).toBe(normalize(alt + "\n", "\n"))
|
||||
expectLf(output)
|
||||
})
|
||||
|
||||
test("preserves CRLF with mixed old/new line endings", async () => {
|
||||
const content = normalize(old + "\n", "\r\n")
|
||||
const output = await apply({
|
||||
content,
|
||||
oldString: "alpha\r\nbeta\ngamma",
|
||||
newString: "alpha\nbeta\r\nomega",
|
||||
})
|
||||
expect(output).toBe(normalize(alt + "\n", "\r\n"))
|
||||
expectCrlf(output)
|
||||
})
|
||||
|
||||
test("replaceAll preserves LF for multi-line blocks", async () => {
|
||||
const blockOld = "alpha\nbeta"
|
||||
const blockNew = "alpha\nbeta-updated"
|
||||
const content = normalize(blockOld + "\n" + blockOld + "\n", "\n")
|
||||
const output = await apply({
|
||||
content,
|
||||
oldString: normalize(blockOld, "\n"),
|
||||
newString: normalize(blockNew, "\n"),
|
||||
replaceAll: true,
|
||||
})
|
||||
expect(output).toBe(normalize(blockNew + "\n" + blockNew + "\n", "\n"))
|
||||
expectLf(output)
|
||||
})
|
||||
|
||||
test("replaceAll preserves CRLF for multi-line blocks", async () => {
|
||||
const blockOld = "alpha\nbeta"
|
||||
const blockNew = "alpha\nbeta-updated"
|
||||
const content = normalize(blockOld + "\n" + blockOld + "\n", "\r\n")
|
||||
const output = await apply({
|
||||
content,
|
||||
oldString: normalize(blockOld, "\r\n"),
|
||||
newString: normalize(blockNew, "\r\n"),
|
||||
replaceAll: true,
|
||||
})
|
||||
expect(output).toBe(normalize(blockNew + "\n" + blockNew + "\n", "\r\n"))
|
||||
expectCrlf(output)
|
||||
})
|
||||
})
|
||||
|
||||
describe("concurrent editing", () => {
|
||||
test("serializes concurrent edits to same file", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
|
||||
Reference in New Issue
Block a user