Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 16 additions & 15 deletions src/core/tools/__tests__/writeToFileTool.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { fileExistsAtPath } from "../../../utils/fs"
import { detectCodeOmission } from "../../../integrations/editor/detect-omission"
import { isPathOutsideWorkspace } from "../../../utils/pathUtils"
import { getReadablePath } from "../../../utils/path"
import { unescapeHtmlEntities } from "../../../utils/text-normalization"
import { everyLineHasLineNumbers, stripLineNumbers } from "../../../integrations/misc/extract-text"
import { ToolUse, ToolResponse } from "../../../shared/tools"
import { writeToFileTool } from "../writeToFileTool"
Expand Down Expand Up @@ -54,10 +53,6 @@ vi.mock("../../../utils/path", () => ({
getReadablePath: vi.fn().mockReturnValue("test/path.txt"),
}))

vi.mock("../../../utils/text-normalization", () => ({
unescapeHtmlEntities: vi.fn().mockImplementation((content) => content),
}))

vi.mock("../../../integrations/misc/extract-text", () => ({
everyLineHasLineNumbers: vi.fn().mockReturnValue(false),
stripLineNumbers: vi.fn().mockImplementation((content) => content),
Expand Down Expand Up @@ -104,7 +99,6 @@ describe("writeToFileTool", () => {
const mockedDetectCodeOmission = detectCodeOmission as MockedFunction<typeof detectCodeOmission>
const mockedIsPathOutsideWorkspace = isPathOutsideWorkspace as MockedFunction<typeof isPathOutsideWorkspace>
const mockedGetReadablePath = getReadablePath as MockedFunction<typeof getReadablePath>
const mockedUnescapeHtmlEntities = unescapeHtmlEntities as MockedFunction<typeof unescapeHtmlEntities>
const mockedEveryLineHasLineNumbers = everyLineHasLineNumbers as MockedFunction<typeof everyLineHasLineNumbers>
const mockedStripLineNumbers = stripLineNumbers as MockedFunction<typeof stripLineNumbers>
const mockedPathResolve = path.resolve as MockedFunction<typeof path.resolve>
Expand All @@ -124,7 +118,6 @@ describe("writeToFileTool", () => {
mockedDetectCodeOmission.mockReturnValue(false)
mockedIsPathOutsideWorkspace.mockReturnValue(false)
mockedGetReadablePath.mockReturnValue("test/path.txt")
mockedUnescapeHtmlEntities.mockImplementation((content) => content)
mockedEveryLineHasLineNumbers.mockReturnValue(false)
mockedStripLineNumbers.mockImplementation((content) => content)

Expand Down Expand Up @@ -288,20 +281,28 @@ describe("writeToFileTool", () => {
expect(mockCline.diffViewProvider.update).toHaveBeenCalledWith("", true)
})

it("unescapes HTML entities for non-Claude models", async () => {
mockCline.api.getModel.mockReturnValue({ id: "gpt-4" })
it("preserves C# generics and angle brackets in content", async () => {
const csharpContent = `public class Service<T> where T : class
{
public void Configure<TOptions>(TOptions options)
{
builder.Services.Configure<BlazorServerSettings>(builder.Configuration.GetSection("BlazorServerSettings"));
}
}`

await executeWriteFileTool({ content: "&lt;test&gt;" })
await executeWriteFileTool({ content: csharpContent, line_count: "7" })

expect(mockedUnescapeHtmlEntities).toHaveBeenCalledWith("&lt;test&gt;")
// Verify the content is passed through unchanged (no HTML entity conversion)
expect(mockCline.diffViewProvider.update).toHaveBeenCalledWith(csharpContent, true)
})

it("skips HTML unescaping for Claude models", async () => {
mockCline.api.getModel.mockReturnValue({ id: "claude-3" })
it("preserves HTML entities in content without unescaping", async () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These test cases for C# generics and HTML entities look good. Consider adding a test case for mixed content (e.g., actual HTML with entities alongside C# code) to ensure we're not breaking legitimate HTML entity usage in edge cases?

const contentWithEntities = "This has &lt;angle&gt; brackets and &amp; ampersand"

await executeWriteFileTool({ content: "&lt;test&gt;" })
await executeWriteFileTool({ content: contentWithEntities })

expect(mockedUnescapeHtmlEntities).not.toHaveBeenCalled()
// Verify the content is passed through with HTML entities preserved
expect(mockCline.diffViewProvider.update).toHaveBeenCalledWith(contentWithEntities, true)
})

it("strips line numbers from numbered content", async () => {
Expand Down
5 changes: 0 additions & 5 deletions src/core/tools/writeToFileTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { stripLineNumbers, everyLineHasLineNumbers } from "../../integrations/mi
import { getReadablePath } from "../../utils/path"
import { isPathOutsideWorkspace } from "../../utils/pathUtils"
import { detectCodeOmission } from "../../integrations/editor/detect-omission"
import { unescapeHtmlEntities } from "../../utils/text-normalization"
import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types"
import { EXPERIMENT_IDS, experiments } from "../../shared/experiments"

Expand Down Expand Up @@ -83,10 +82,6 @@ export async function writeToFileTool(
newContent = newContent.split("\n").slice(0, -1).join("\n")
}

if (!cline.api.getModel().id.includes("claude")) {
newContent = unescapeHtmlEntities(newContent)
}

// Determine if the path is outside the workspace
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good fix removing the HTML entity unescaping. However, I noticed that applyDiffTool.ts (line 29) and multiApplyDiffTool.ts (line 418) still use unescapeHtmlEntities() for non-Claude models. Should we remove it from those tools as well for consistency? This could affect users working with C# or other languages with angle brackets across different tools.

const fullPath = relPath ? path.resolve(cline.cwd, removeClosingTag("path", relPath)) : ""
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding a comment here explaining why we don't unescape HTML entities (to preserve code exactly as written, especially for languages like C# with generics). This would help prevent future developers from re-introducing this logic thinking it was an oversight.

const isOutsideWorkspace = isPathOutsideWorkspace(fullPath)
Expand Down
Loading