From 053671e235471d1b153ac076a514d985a4252399 Mon Sep 17 00:00:00 2001 From: Joe Manley Date: Thu, 20 Feb 2025 12:50:17 -0800 Subject: [PATCH] Appease ellipsis --- .../__tests__/custom-instructions.test.ts | 39 ++++++++++++++++++ .../prompts/sections/custom-instructions.ts | 40 +++++++++---------- 2 files changed, 57 insertions(+), 22 deletions(-) diff --git a/src/core/prompts/sections/__tests__/custom-instructions.test.ts b/src/core/prompts/sections/__tests__/custom-instructions.test.ts index 8eef1b9161..e590a5ca3b 100644 --- a/src/core/prompts/sections/__tests__/custom-instructions.test.ts +++ b/src/core/prompts/sections/__tests__/custom-instructions.test.ts @@ -5,6 +5,45 @@ import fs from "fs/promises" jest.mock("fs/promises") const mockedFs = jest.mocked(fs) +describe("safeReadFile", () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + it("should read and trim file content", async () => { + mockedFs.readFile.mockResolvedValue(" content with spaces ") + const result = await loadRuleFiles("/fake/path") + expect(mockedFs.readFile).toHaveBeenCalled() + expect(result).toBe( + "\n# Rules from .clinerules:\ncontent with spaces\n" + + "\n# Rules from .cursorrules:\ncontent with spaces\n" + + "\n# Rules from .windsurfrules:\ncontent with spaces\n", + ) + }) + + it("should handle ENOENT error", async () => { + mockedFs.readFile.mockRejectedValue({ code: "ENOENT" }) + const result = await loadRuleFiles("/fake/path") + expect(result).toBe("") + }) + + it("should handle EISDIR error", async () => { + mockedFs.readFile.mockRejectedValue({ code: "EISDIR" }) + const result = await loadRuleFiles("/fake/path") + expect(result).toBe("") + }) + + it("should throw on unexpected errors", async () => { + const error = new Error("Permission denied") as NodeJS.ErrnoException + error.code = "EPERM" + mockedFs.readFile.mockRejectedValue(error) + + await expect(async () => { + await loadRuleFiles("/fake/path") + }).rejects.toThrow() + }) +}) + describe("loadRuleFiles", () => { beforeEach(() => { jest.clearAllMocks() diff --git a/src/core/prompts/sections/custom-instructions.ts b/src/core/prompts/sections/custom-instructions.ts index 893bd807d8..240dfcc47e 100644 --- a/src/core/prompts/sections/custom-instructions.ts +++ b/src/core/prompts/sections/custom-instructions.ts @@ -1,21 +1,27 @@ import fs from "fs/promises" import path from "path" +async function safeReadFile(filePath: string): Promise { + try { + const content = await fs.readFile(filePath, "utf-8") + return content.trim() + } catch (err) { + const errorCode = (err as NodeJS.ErrnoException).code + if (!errorCode || !["ENOENT", "EISDIR"].includes(errorCode)) { + throw err + } + return "" + } +} + export async function loadRuleFiles(cwd: string): Promise { const ruleFiles = [".clinerules", ".cursorrules", ".windsurfrules"] let combinedRules = "" for (const file of ruleFiles) { - try { - const content = await fs.readFile(path.join(cwd, file), "utf-8") - if (content.trim()) { - combinedRules += `\n# Rules from ${file}:\n${content.trim()}\n` - } - } catch (err) { - const errorCode = (err as NodeJS.ErrnoException).code - if (!errorCode || !["ENOENT", "EISDIR"].includes(errorCode)) { - throw err - } + const content = await safeReadFile(path.join(cwd, file)) + if (content) { + combinedRules += `\n# Rules from ${file}:\n${content}\n` } } @@ -34,18 +40,8 @@ export async function addCustomInstructions( // Load mode-specific rules if mode is provided let modeRuleContent = "" if (mode) { - try { - const modeRuleFile = `.clinerules-${mode}` - const content = await fs.readFile(path.join(cwd, modeRuleFile), "utf-8") - if (content.trim()) { - modeRuleContent = content.trim() - } - } catch (err) { - const errorCode = (err as NodeJS.ErrnoException).code - if (!errorCode || !["ENOENT", "EISDIR"].includes(errorCode)) { - throw err - } - } + const modeRuleFile = `.clinerules-${mode}` + modeRuleContent = await safeReadFile(path.join(cwd, modeRuleFile)) } // Add language preference if provided