diff --git a/src/core/tools/__tests__/readFileTool.spec.ts b/src/core/tools/__tests__/readFileTool.spec.ts index 44be1d3b92..51488d3587 100644 --- a/src/core/tools/__tests__/readFileTool.spec.ts +++ b/src/core/tools/__tests__/readFileTool.spec.ts @@ -470,7 +470,7 @@ describe("read_file tool XML output structure", () => { it("should handle empty files correctly", async () => { // Setup mockedCountFileLines.mockResolvedValue(0) - mockedExtractTextFromFile.mockResolvedValue("") + mockedExtractTextFromFile.mockResolvedValue("This file is empty") mockProvider.getState.mockResolvedValue({ maxReadFileLine: -1 }) // Execute @@ -518,5 +518,70 @@ describe("read_file tool XML output structure", () => { `\n${testFilePath}Access to ${testFilePath} is blocked by the .rooignore file settings. You must try to continue in the task without using this file, or ask the user to update the .rooignore file.\n`, ) }) + + it("should handle empty files with range reads", async () => { + // Setup + mockedCountFileLines.mockResolvedValue(0) + mockedExtractTextFromFile.mockResolvedValue("This file is empty") + mockProvider.getState.mockResolvedValue({ maxReadFileLine: -1 }) + + // Create args with range manually since the helper doesn't support it + const argsContent = `${testFilePath}1-5` + + // Create a tool use object directly + const toolUse: ReadFileToolUse = { + type: "tool_use", + name: "read_file", + params: { args: argsContent }, + partial: false, + } + + // Execute the tool directly + await readFileTool( + mockCline, + toolUse, + mockCline.ask, + vi.fn(), + (result: ToolResponse) => { + toolResult = result + }, + (param: ToolParamName, content?: string) => content ?? "", + ) + + // Verify - should show empty file notice instead of trying to read range + expect(toolResult).toBe( + `\n${testFilePath}\nFile is empty\n\n`, + ) + }) + + it("should handle empty files in definitions-only mode", async () => { + // Setup + mockedCountFileLines.mockResolvedValue(0) + mockedExtractTextFromFile.mockResolvedValue("This file is empty") + mockProvider.getState.mockResolvedValue({ maxReadFileLine: 0 }) + + // Execute + const result = await executeReadFileTool({}, { totalLines: 0, maxReadFileLine: 0 }) + + // Verify - should show empty file notice instead of trying to parse definitions + expect(result).toBe( + `\n${testFilePath}\nFile is empty\n\n`, + ) + }) + + it("should handle empty files when maxReadFileLine exceeds file length", async () => { + // Setup + mockedCountFileLines.mockResolvedValue(0) + mockedExtractTextFromFile.mockResolvedValue("This file is empty") + mockProvider.getState.mockResolvedValue({ maxReadFileLine: 10 }) + + // Execute + const result = await executeReadFileTool({}, { totalLines: 0, maxReadFileLine: 10 }) + + // Verify - should show empty file notice + expect(result).toBe( + `\n${testFilePath}\nFile is empty\n\n`, + ) + }) }) }) diff --git a/src/core/tools/readFileTool.ts b/src/core/tools/readFileTool.ts index 6de8dd5642..4ca97b1edd 100644 --- a/src/core/tools/readFileTool.ts +++ b/src/core/tools/readFileTool.ts @@ -452,6 +452,14 @@ export async function readFileTool( // Handle range reads (bypass maxReadFileLine) if (fileResult.lineRanges && fileResult.lineRanges.length > 0) { + // Check if file is empty first + if (totalLines === 0) { + updateFileResult(relPath, { + xmlContent: `${relPath}\nFile is empty\n`, + }) + continue + } + const rangeResults: string[] = [] for (const range of fileResult.lineRanges) { const content = addLineNumbers( @@ -469,6 +477,14 @@ export async function readFileTool( // Handle definitions-only mode if (maxReadFileLine === 0) { + // Check if file is empty first + if (totalLines === 0) { + updateFileResult(relPath, { + xmlContent: `${relPath}\nFile is empty\n`, + }) + continue + } + try { const defResult = await parseSourceCodeDefinitionsForFile(fullPath, cline.rooIgnoreController) if (defResult) { @@ -491,6 +507,14 @@ export async function readFileTool( // Handle files exceeding line threshold if (maxReadFileLine > 0 && totalLines > maxReadFileLine) { + // Check if file is empty first + if (totalLines === 0) { + updateFileResult(relPath, { + xmlContent: `${relPath}\nFile is empty\n`, + }) + continue + } + const content = addLineNumbers(await readLines(fullPath, maxReadFileLine - 1, 0)) const lineRangeAttr = ` lines="1-${maxReadFileLine}"` let xmlInfo = `\n${content}\n` @@ -519,9 +543,13 @@ export async function readFileTool( // Handle normal file read const content = await extractTextFromFile(fullPath) const lineRangeAttr = ` lines="1-${totalLines}"` - let xmlInfo = totalLines > 0 ? `\n${content}\n` : `` - if (totalLines === 0) { + // Check if file is empty (either totalLines is 0 or content indicates empty file) + const isEmptyFile = totalLines === 0 || content === "This file is empty" + + let xmlInfo = isEmptyFile ? `` : `\n${content}\n` + + if (isEmptyFile) { xmlInfo += `File is empty\n` } diff --git a/src/integrations/misc/__tests__/read-file-tool.spec.ts b/src/integrations/misc/__tests__/read-file-tool.spec.ts index fabc5bc829..5745c9c41d 100644 --- a/src/integrations/misc/__tests__/read-file-tool.spec.ts +++ b/src/integrations/misc/__tests__/read-file-tool.spec.ts @@ -144,4 +144,19 @@ describe("read_file tool with maxReadFileLine setting", () => { expect(readLines).toHaveBeenCalledWith(filePath, maxReadFileLine - 1, 0) expect(addLineNumbers).toHaveBeenCalled() }) + + // Test for empty file handling + it("should return 'This file is empty' message for empty files", async () => { + // Mock extractTextFromFile to simulate empty file behavior + ;(extractTextFromFile as Mock).mockResolvedValue("This file is empty") + + const filePath = path.resolve("/test", "emptyFile.txt") + + // Test the mocked behavior + const result = await extractTextFromFile(filePath) + + // Should return the empty file message + expect(result).toBe("This file is empty") + expect(extractTextFromFile).toHaveBeenCalledWith(filePath) + }) }) diff --git a/src/integrations/misc/extract-text.ts b/src/integrations/misc/extract-text.ts index 8c7e7408a6..17b99fd781 100644 --- a/src/integrations/misc/extract-text.ts +++ b/src/integrations/misc/extract-text.ts @@ -67,7 +67,12 @@ export async function extractTextFromFile(filePath: string): Promise { const isBinary = await isBinaryFile(filePath).catch(() => false) if (!isBinary) { - return addLineNumbers(await fs.readFile(filePath, "utf8")) + const content = await fs.readFile(filePath, "utf8") + // Check if file is empty and provide clear feedback + if (content === "") { + return "This file is empty" + } + return addLineNumbers(content) } else { throw new Error(`Cannot read text for file type: ${fileExtension}`) }