diff --git a/packages/core/src/tool/edit.ts b/packages/core/src/tool/edit.ts index f0bdb488a060..1d588bc4af85 100644 --- a/packages/core/src/tool/edit.ts +++ b/packages/core/src/tool/edit.ts @@ -162,6 +162,11 @@ const layer = Layer.effectDiscard( const ending = detectLineEnding(source.text) const oldString = convertToLineEnding(input.oldString, ending) const newString = convertToLineEnding(input.newString, ending) + if (oldString === newString) { + return yield* new ToolFailure({ + message: "No changes to apply: oldString and newString are identical.", + }) + } const replacements = countOccurrences(source.text, oldString) if (replacements === 0) { return yield* new ToolFailure({ diff --git a/packages/core/test/tool-edit.test.ts b/packages/core/test/tool-edit.test.ts index a9e4cbae4952..56d0c18374ea 100644 --- a/packages/core/test/tool-edit.test.ts +++ b/packages/core/test/tool-edit.test.ts @@ -381,6 +381,41 @@ describe("EditTool", () => { ), ) + it.live("rejects an edit whose strings differ only in line ending style", () => + Effect.acquireUseRelease( + Effect.promise(() => tmpdir()), + (tmp) => { + reset() + const target = path.join(tmp.path, "crlf.txt") + return Effect.promise(() => fs.writeFile(target, "before\r\nrest\r\n")).pipe( + Effect.andThen( + withTool(tmp.path, (registry) => + executeTool( + registry, + call({ path: "crlf.txt", oldString: "before\r\nrest", newString: "before\nrest" }), + ), + ), + ), + Effect.andThen((result) => + Effect.promise(() => fs.readFile(target, "utf8")).pipe( + Effect.tap((content) => + Effect.sync(() => { + expect(result).toEqual({ + type: "error", + value: "No changes to apply: oldString and newString are identical.", + }) + expect(content).toBe("before\r\nrest\r\n") + expect(writes).toHaveLength(0) + }), + ), + ), + ), + ) + }, + (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()), + ), + ) + it.live("rejects an in-place content change after matching but before conditional commit", () => Effect.acquireUseRelease( Effect.promise(() => tmpdir()),