/
outbreak
/
kilocode
Обзор
Документация
Войти
/
outbreak
/
kilocode
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/services/continuedev/core/diff/streamDiff.test.ts
317 строк
10 KB
Eamon Nerbonne
More robust fix for windows
21 окт 2025, 19:58
21 окт 2025, 19:58
9860049
Код
Авторство
О чём код?
import fs from "node:fs" import path from "node:path" import { describe, expect, test } from "vitest" // @ts-expect-error no typings available import { changed, diff as myersDiff } from "myers-diff" import { streamDiff } from "../diff/streamDiff.js" import { DiffLine, DiffType } from "../index.js" import { generateLines } from "./util.js" // "modification" is an extra type used to represent an "old" + "new" diff line type MyersDiffTypes = Extract<DiffType, "new" | "old"> | "modification" const UNIFIED_DIFF_SYMBOLS = { same: "", new: "+", old: "-", } async function collectDiffs( oldLines: string[], newLines: string[], ): Promise<{ streamDiffs: DiffLine[]; myersDiffs: any }> { const streamDiffs: DiffLine[] = [] for await (const diffLine of streamDiff(oldLines, generateLines(newLines))) { streamDiffs.push(diffLine) } const myersDiffs = myersDiff(oldLines.join("\n"), newLines.join("\n")) return { streamDiffs, myersDiffs } } function getMyersDiffType(diff: any): MyersDiffTypes | undefined { if (changed(diff.rhs) && !changed(diff.lhs)) { return "new" } if (!changed(diff.rhs) && changed(diff.lhs)) { return "old" } if (changed(diff.rhs) && changed(diff.lhs)) { return "modification" } return undefined } function displayDiff(diff: DiffLine[]) { return diff.map(({ type, line }) => `${UNIFIED_DIFF_SYMBOLS[type]} ${line}`).join("\n") } async function expectDiff(file: string) { const testFilePath = path.join(__dirname, "test-examples", file + ".diff") const testFileContents = fs.readFileSync(testFilePath, "utf-8") const normalized = testFileContents.replace(/\r\n/g, "\n") const [oldText, newText, expectedDiff] = normalized.split("\n---\n").map((s) => s.replace(/^\n+/, "").trimEnd()) const oldLines = oldText.split("\n") const newLines = newText.split("\n") const { streamDiffs } = await collectDiffs(oldLines, newLines) const displayedDiff = displayDiff(streamDiffs) if (!expectedDiff || expectedDiff.trim() === "") { console.log("Expected diff was empty. Writing computed diff to the test file") // Persist with LF to keep fixtures stable cross-platform fs.writeFileSync(testFilePath, `${oldText}\n\n---\n\n${newText}\n\n---\n\n${displayedDiff}`) throw new Error("Expected diff is empty") } expect(displayedDiff).toEqual(expectedDiff) } // We use a longer `)` string here to not get // caught by the fuzzy matcher describe("streamDiff(", () => { test("no changes", async () => { const oldLines = ["first item", "second arg", "third param"] const newLines = ["first item", "second arg", "third param"] const { streamDiffs, myersDiffs } = await collectDiffs(oldLines, newLines) expect(streamDiffs).toEqual([ { type: "same", line: "first item" }, { type: "same", line: "second arg" }, { type: "same", line: "third param" }, ]) expect(myersDiffs).toEqual([]) }) test("add new line", async () => { const oldLines = ["first item", "second arg"] const newLines = ["first item", "second arg", "third param"] const { streamDiffs, myersDiffs } = await collectDiffs(oldLines, newLines) expect(streamDiffs).toEqual([ { type: "same", line: "first item" }, { type: "same", line: "second arg" }, { type: "new", line: "third param" }, ]) expect(myersDiffs.length).toEqual(1) expect(getMyersDiffType(myersDiffs[0])).toBe("new") }) test("remove line", async () => { const oldLines = ["first item", "second arg", "third param"] const newLines = ["first item", "third param"] const { streamDiffs, myersDiffs } = await collectDiffs(oldLines, newLines) expect(streamDiffs).toEqual([ { type: "same", line: "first item" }, { type: "old", line: "second arg" }, { type: "same", line: "third param" }, ]) expect(myersDiffs.length).toEqual(1) expect(getMyersDiffType(myersDiffs[0])).toBe("old") }) test("modify line", async () => { const oldLines = ["first item", "second arg", "third param"] const newLines = ["first item", "modified second arg", "third param"] const { streamDiffs, myersDiffs } = await collectDiffs(oldLines, newLines) expect(streamDiffs).toEqual([ { type: "same", line: "first item" }, { type: "old", line: "second arg" }, { type: "new", line: "modified second arg" }, { type: "same", line: "third param" }, ]) expect(myersDiffs.length).toEqual(1) expect(getMyersDiffType(myersDiffs[0])).toBe("modification") }) test("add multiple lines", async () => { const oldLines = ["first item", "fourth val"] const newLines = ["first item", "second arg", "third param", "fourth val"] const { streamDiffs, myersDiffs } = await collectDiffs(oldLines, newLines) expect(streamDiffs).toEqual([ { type: "same", line: "first item" }, { type: "new", line: "second arg" }, { type: "new", line: "third param" }, { type: "same", line: "fourth val" }, ]) // Multi-line addition expect(myersDiffs[0].rhs.add).toEqual(2) expect(getMyersDiffType(myersDiffs[0])).toBe("new") }) test("remove multiple lines", async () => { const oldLines = ["first item", "second arg", "third param", "fourth val"] const newLines = ["first item", "fourth val"] const { streamDiffs, myersDiffs } = await collectDiffs(oldLines, newLines) expect(streamDiffs).toEqual([ { type: "same", line: "first item" }, { type: "old", line: "second arg" }, { type: "old", line: "third param" }, { type: "same", line: "fourth val" }, ]) // Multi-line deletion expect(myersDiffs[0].lhs.del).toEqual(2) expect(getMyersDiffType(myersDiffs[0])).toBe("old") }) test("empty old lines", async () => { const oldLines: string[] = [] const newLines = ["first item", "second arg"] const { streamDiffs, myersDiffs } = await collectDiffs(oldLines, newLines) expect(streamDiffs).toEqual([ { type: "new", line: "first item" }, { type: "new", line: "second arg" }, ]) // Multi-line addition expect(myersDiffs[0].rhs.add).toEqual(2) expect(getMyersDiffType(myersDiffs[0])).toBe("new") }) test("empty new lines", async () => { const oldLines = ["first item", "second arg"] const newLines: string[] = [] const { streamDiffs, myersDiffs } = await collectDiffs(oldLines, newLines) expect(streamDiffs).toEqual([ { type: "old", line: "first item" }, { type: "old", line: "second arg" }, ]) // Multi-line deletion expect(myersDiffs[0].lhs.del).toEqual(2) expect(getMyersDiffType(myersDiffs[0])).toBe("old") }) test("tabs vs. spaces differences are ignored", async () => { await expectDiff("fastapi-tabs-vs-spaces.py") }) test("trailing whitespaces should match ", async () => { const oldLines = ["first item ", "second arg ", "third param "] const newLines = ["first item", "second arg", "third param "] const { streamDiffs } = await collectDiffs(oldLines, newLines) expect(streamDiffs).toEqual([ { type: "same", line: "first item " }, { type: "same", line: "second arg " }, { type: "same", line: "third param " }, ]) }) //indentation and whitespace handling test.each([false, true])( "ignores indentation changes for sufficiently long lines (trailingWhitespace: %s)", async (trailingWhitespace) => { let oldLines = [" short", " middle", " a long enough line", " short2", "indented line", "final line"] const newLines = ["short", "middle", "a long enough line", "short2", " indented line", "final line"] if (trailingWhitespace) { oldLines = oldLines.map((line) => line + " ") } const { streamDiffs } = await collectDiffs(oldLines, newLines) const expected = trailingWhitespace ? [ { type: "old", line: " short " }, { type: "new", line: "short" }, { type: "old", line: " middle " }, { type: "new", line: "middle" }, { type: "same", line: " a long enough line " }, { type: "same", line: " short2 " }, { type: "same", line: "indented line " }, { type: "same", line: "final line " }, ] : [ { type: "old", line: " short" }, { type: "new", line: "short" }, { type: "old", line: " middle" }, { type: "new", line: "middle" }, { type: "same", line: " a long enough line" }, { type: "same", line: " short2" }, { type: "same", line: "indented line" }, { type: "same", line: "final line" }, ] expect(streamDiffs).toEqual(expected) }, ) test("preserves original lines for minor reindentation in simple block", async () => { const oldLines = ["if (checkValueOf(x)) {", " doSomethingWith(x);", "}"] const newLines = ["if (checkValueOf(x)) {", " doSomethingWith(x);", "}"] const { streamDiffs } = await collectDiffs(oldLines, newLines) expect(streamDiffs).toEqual([ { type: "same", line: "if (checkValueOf(x)) {" }, { type: "same", line: " doSomethingWith(x);" }, { type: "same", line: "}" }, ]) }) test("uses new lines for nested reindentation changes", async () => { const oldLines = ["if (checkValueOf(x)) {", " doSomethingWith(x);", "}"] const newLines = [ "if (checkValueOf(x)) {", " if (reallyCheckValueOf(x)) {", " doSomethingElseWith(x);", " }", "}", ] const { streamDiffs } = await collectDiffs(oldLines, newLines) expect(streamDiffs).toEqual([ { type: "same", line: "if (checkValueOf(x)) {" }, { type: "new", line: " if (reallyCheckValueOf(x)) {" }, { type: "old", line: " doSomethingWith(x);" }, { type: "new", line: " doSomethingElseWith(x);" }, { type: "old", line: "}" }, { type: "new", line: " }" }, { type: "new", line: "}" }, ]) }) test("FastAPI example", async () => { await expectDiff("fastapi.py") }) test("FastAPI comments", async () => { await expectDiff("add-comments.py") }) test("Mock LLM example", async () => { await expectDiff("mock-llm.ts") }) })