diff --git a/packages/core/src/tool/plugin/patch.ts b/packages/core/src/tool/plugin/patch.ts index b25b681dfc5..c77e01d8b7f 100644 --- a/packages/core/src/tool/plugin/patch.ts +++ b/packages/core/src/tool/plugin/patch.ts @@ -257,9 +257,20 @@ export const Plugin = { }), { discard: true }, ) + const formatTargets = prepared.reduce((result, change) => { + if (change.type !== "delete") { + result.add( + (change.type === "update" ? change.moveTarget : undefined)?.absolute ?? change.target.absolute, + ) + } + if (change.type === "delete" || (change.type === "update" && change.moveTarget)) { + result.delete(change.target.absolute) + } + return result + }, new Set()) const formatted = new Map() yield* Effect.forEach( - [...new Set(applied.filter((item) => item.type !== "delete").map((item) => item.target))], + formatTargets, (target) => Effect.gen(function* () { const current = yield* FileMutation.readText(environment.files, target).pipe( diff --git a/packages/core/test/tool-patch.test.ts b/packages/core/test/tool-patch.test.ts index 30e95e5486b..dddd3a6ab1a 100644 --- a/packages/core/test/tool-patch.test.ts +++ b/packages/core/test/tool-patch.test.ts @@ -323,6 +323,60 @@ describe("PatchTool", () => { }), ) + it.live("does not format a file deleted after an update", () => + withTempTool((directory, registry) => + Effect.gen(function* () { + const target = path.join(directory, "removed.txt") + const formatted: string[] = [] + formatFile = (file) => + Effect.sync(() => { + formatted.push(file) + return false + }) + yield* Effect.promise(() => fs.writeFile(target, "before\n")) + expect( + yield* executeTool( + registry, + call( + "*** Begin Patch\n*** Update File: removed.txt\n@@\n-before\n+after\n*** Delete File: removed.txt\n*** End Patch", + ), + ), + ).toMatchObject({ status: "completed" }) + expect(yield* exists(target)).toBe(false) + expect(formatted).toEqual([]) + }), + ), + ) + + it.live("formats only the destination of a file moved after an update", () => + withTempTool((directory, registry) => + Effect.gen(function* () { + const source = path.join(directory, "old.txt") + const destination = path.join(directory, "moved.txt") + const formatted: string[] = [] + formatFile = (file) => + Effect.promise(async () => { + formatted.push(file) + await fs.writeFile(file, (await fs.readFile(file, "utf8")).toUpperCase()) + return true + }) + yield* Effect.promise(() => fs.writeFile(source, "before\n")) + const settled = yield* executeTool( + registry, + call( + "*** Begin Patch\n*** Update File: old.txt\n@@\n-before\n+updated\n*** Update File: old.txt\n*** Move to: moved.txt\n@@\n-updated\n+after\n*** End Patch", + ), + ) + expect(settled.status).toBe("completed") + if (settled.status !== "completed") return + expect(settled.output.files[1]?.patch).toContain("+AFTER") + expect(yield* exists(source)).toBe(false) + expect(yield* Effect.promise(() => fs.readFile(destination, "utf8"))).toBe("AFTER\n") + expect(formatted).toEqual([destination]) + }), + ), + ) + it.live("moves and updates a file", () => Effect.acquireUseRelease( Effect.promise(() => tmpdir()),