diff --git a/eslint.config.mjs b/eslint.config.mjs index ebc239cb..39b1a2f2 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -92,7 +92,7 @@ export default [ // exemptions"). Remaining source files (≤5 violations each) were fixed in // this PR. "@typescript-eslint/no-unsafe-assignment": "off", // enabled for tests below; follow-up PR for production - "@typescript-eslint/no-unsafe-call": "off", // 679 violations + "@typescript-eslint/no-unsafe-call": "off", // 107 violations // --- Medium: promise / method ergonomics --- // Enabled in the TS-only block below. diff --git a/src/chatUtils.toolMarkers.test.ts b/src/chatUtils.toolMarkers.test.ts index 3d3e3033..7593afa6 100644 --- a/src/chatUtils.toolMarkers.test.ts +++ b/src/chatUtils.toolMarkers.test.ts @@ -1,7 +1,6 @@ import { AI_SENDER, USER_SENDER } from "@/constants"; import { ChatMessage } from "@/types/message"; import { updateChatMemory } from "./chatUtils"; -import type MemoryManager from "@/LLMProviders/memoryManager"; jest.mock("@/logger"); @@ -45,8 +44,8 @@ describe("updateChatMemory with tool call markers", () => { }, ]; - const memoryManager: any = new MockMemoryManager(); - await updateChatMemory(messages, memoryManager as MemoryManager); + const memoryManager = new MockMemoryManager(); + await updateChatMemory(messages, memoryManager as never); expect(memoryManager.getMemory().saved).toHaveLength(1); expect(memoryManager.getMemory().saved[0].input).toBe("find my piano notes"); diff --git a/src/commands/customCommandUtils.test.ts b/src/commands/customCommandUtils.test.ts index b4d3f49e..ac1e9db7 100644 --- a/src/commands/customCommandUtils.test.ts +++ b/src/commands/customCommandUtils.test.ts @@ -736,8 +736,12 @@ describe("parseCustomCommandFile", () => { }); it("uses EMPTY_COMMAND defaults if frontmatter is missing", async () => { - (window.app.vault as any).read.mockResolvedValue("Prompt content only, no frontmatter."); - (window.app.metadataCache as any).getFileCache.mockReturnValue({}); + (window.app.vault as unknown as { read: jest.Mock }).read.mockResolvedValue( + "Prompt content only, no frontmatter." + ); + ( + window.app.metadataCache as unknown as { getFileCache: jest.Mock } + ).getFileCache.mockReturnValue({}); const { parseCustomCommandFile } = await import("@/commands/customCommandUtils"); const result = await parseCustomCommandFile(mockFile); expect(result).toEqual({ diff --git a/src/commands/customCommandUtils.xmlescape.test.ts b/src/commands/customCommandUtils.xmlescape.test.ts index ba498d2e..8a600767 100644 --- a/src/commands/customCommandUtils.xmlescape.test.ts +++ b/src/commands/customCommandUtils.xmlescape.test.ts @@ -109,7 +109,9 @@ describe("XML Escaping in processPrompt", () => { }); (getNotesFromPath as jest.Mock).mockReturnValue([]); - jest.requireMock("@/utils").extractTemplateNoteFiles.mockReturnValue([mockNote]); + ( + jest.requireMock("@/utils") as unknown as { extractTemplateNoteFiles: jest.Mock } + ).extractTemplateNoteFiles.mockReturnValue([mockNote]); (getFileContent as jest.Mock).mockResolvedValue("Content with & and < and >"); const result = await processPrompt(customPrompt, "", mockVault, mockActiveNote); @@ -133,7 +135,9 @@ describe("XML Escaping in processPrompt", () => { }); (getNotesFromPath as jest.Mock).mockReturnValue([]); - jest.requireMock("@/utils").getNotesFromTags.mockReturnValue([mockNote]); + ( + jest.requireMock("@/utils") as unknown as { getNotesFromTags: jest.Mock } + ).getNotesFromTags.mockReturnValue([mockNote]); (getFileName as jest.Mock).mockReturnValue(mockNote.basename); (getFileContent as jest.Mock).mockResolvedValue("Content: & "); diff --git a/src/components/chat-components/utils/lexicalTextUtils.test.ts b/src/components/chat-components/utils/lexicalTextUtils.test.ts index b17bdd42..bed8fe69 100644 --- a/src/components/chat-components/utils/lexicalTextUtils.test.ts +++ b/src/components/chat-components/utils/lexicalTextUtils.test.ts @@ -2,6 +2,8 @@ import { parseTextForPills } from "./lexicalTextUtils"; import { TFile } from "obsidian"; import { mockTFolder } from "@/__tests__/mockObsidian"; +const MockTFile = TFile as unknown as jest.Mock; + // Mock dependencies jest.mock("@/logger", () => ({ logInfo: jest.fn(), @@ -68,7 +70,7 @@ describe("parseTextForPills", () => { }); // Mock TFile constructor to set properties - (TFile as any).mockImplementation(function (this: any) { + MockTFile.mockImplementation(function (this: any) { this.basename = "Valid Note"; this.path = "Valid Note.md"; }); @@ -277,7 +279,7 @@ describe("parseTextForPills", () => { return null; }); - (TFile as any).mockImplementation(function (this: any) { + MockTFile.mockImplementation(function (this: any) { this.basename = "Test Note"; this.path = "Test Note.md"; }); diff --git a/src/core/ChatManager.test.ts b/src/core/ChatManager.test.ts index 98e4c841..4f49183d 100644 --- a/src/core/ChatManager.test.ts +++ b/src/core/ChatManager.test.ts @@ -82,12 +82,28 @@ const createContextResult = (content = "Hello with context") => ({ contextEnvelope: undefined, }); +type MockChainManager = { + memoryManager: { clearChatMemory: jest.Mock }; + runChain: jest.Mock; +}; +type MockPlugin = { + app: { + workspace: { getActiveFile: jest.Mock }; + vault?: { adapter?: { stat: jest.Mock } }; + }; + projectManager: { + getCurrentChainManager: jest.Mock; + getCurrentProjectId: jest.Mock; + getCachedMessages: jest.Mock; + }; +}; + describe("ChatManager", () => { let chatManager: ChatManager; let mockMessageRepo: jest.Mocked; - let mockChainManager: any; - let mockFileParserManager: any; - let mockPlugin: any; + let mockChainManager: MockChainManager; + let mockFileParserManager: object; + let mockPlugin: MockPlugin; let mockContextManager: jest.Mocked; // Helper function to create mock messages @@ -148,9 +164,9 @@ describe("ChatManager", () => { chatManager = new ChatManager( mockMessageRepo, - mockChainManager as ChainManager, + mockChainManager as unknown as ChainManager, mockFileParserManager as FileParserManager, - mockPlugin as CopilotPlugin + mockPlugin as unknown as CopilotPlugin ); }); diff --git a/src/core/ChatPersistenceManager.test.ts b/src/core/ChatPersistenceManager.test.ts index 11b65166..5095ddaf 100644 --- a/src/core/ChatPersistenceManager.test.ts +++ b/src/core/ChatPersistenceManager.test.ts @@ -92,9 +92,31 @@ jest.mock("@/utils", () => ({ }), })); +type MockApp = { + vault: { + getAbstractFileByPath: jest.Mock; + createFolder: jest.Mock; + create: jest.Mock; + modify: jest.Mock; + read: jest.Mock; + getMarkdownFiles: jest.Mock; + adapter: { + exists: jest.Mock; + read: jest.Mock; + write: jest.Mock; + list: jest.Mock; + stat: jest.Mock; + }; + }; + metadataCache: { getFileCache: jest.Mock }; + fileManager: { processFrontMatter: jest.Mock }; +}; + +type MockMessageRepo = { getDisplayMessages: jest.Mock }; + describe("ChatPersistenceManager", () => { - let mockApp: any; - let mockMessageRepo: any; + let mockApp: MockApp; + let mockMessageRepo: MockMessageRepo; let persistenceManager: ChatPersistenceManager; beforeEach(() => { @@ -133,8 +155,8 @@ describe("ChatPersistenceManager", () => { // Create persistence manager persistenceManager = new ChatPersistenceManager( - mockApp as App, - mockMessageRepo as MessageRepository + mockApp as unknown as App, + mockMessageRepo as unknown as MessageRepository ); }); @@ -393,8 +415,8 @@ Nature's quiet song`); } as unknown as ChainManager; persistenceManager = new ChatPersistenceManager( - mockApp as App, - mockMessageRepo as MessageRepository, + mockApp as unknown as App, + mockMessageRepo as unknown as MessageRepository, chainManager ); const mockFile = mockTFile({ @@ -1122,8 +1144,8 @@ ${formattedContent}`; // Recreate persistence manager with the updated mock const testPersistenceManager = new ChatPersistenceManager( - mockApp as App, - mockMessageRepo as MessageRepository + mockApp as unknown as App, + mockMessageRepo as unknown as MessageRepository ); const originalMessages: ChatMessage[] = [ diff --git a/src/search/v3/FilterRetriever.test.ts b/src/search/v3/FilterRetriever.test.ts index db0d82c9..4e861e2f 100644 --- a/src/search/v3/FilterRetriever.test.ts +++ b/src/search/v3/FilterRetriever.test.ts @@ -27,8 +27,17 @@ jest.mock("@/search/searchUtils", () => ({ getMatchingPatterns: jest.fn().mockReturnValue({ inclusions: null, exclusions: null }), })); +type MockApp = { + vault: { + getAbstractFileByPath: jest.Mock; + cachedRead: jest.Mock; + getMarkdownFiles: jest.Mock; + }; + metadataCache: { getFileCache: jest.Mock }; +}; + describe("FilterRetriever", () => { - let mockApp: any; + let mockApp: MockApp; beforeEach(() => { const obsidianMock = getObsidianMock(); @@ -55,7 +64,7 @@ describe("FilterRetriever", () => { mockApp.vault.cachedRead.mockResolvedValue("Note content here"); mockApp.metadataCache.getFileCache.mockReturnValue({ tags: [{ tag: "#test" }] }); - const retriever = new FilterRetriever(mockApp as App, { + const retriever = new FilterRetriever(mockApp as unknown as App, { salientTerms: [], maxK: 30, }); @@ -74,7 +83,7 @@ describe("FilterRetriever", () => { const extractNoteFiles = jest.requireMock("@/utils").extractNoteFiles as jest.Mock; extractNoteFiles.mockReturnValueOnce([]); - const retriever = new FilterRetriever(mockApp as App, { + const retriever = new FilterRetriever(mockApp as unknown as App, { salientTerms: [], maxK: 30, }); @@ -101,7 +110,7 @@ describe("FilterRetriever", () => { }); mockApp.vault.cachedRead.mockResolvedValue("Alpha content"); - const retriever = new FilterRetriever(mockApp as App, { + const retriever = new FilterRetriever(mockApp as unknown as App, { salientTerms: ["#project"], maxK: 30, }); @@ -131,7 +140,7 @@ describe("FilterRetriever", () => { }); mockApp.vault.cachedRead.mockResolvedValue("Beta content"); - const retriever = new FilterRetriever(mockApp as App, { + const retriever = new FilterRetriever(mockApp as unknown as App, { salientTerms: ["#project"], maxK: 30, }); @@ -143,7 +152,7 @@ describe("FilterRetriever", () => { }); it("should return empty when no tag terms present", async () => { - const retriever = new FilterRetriever(mockApp as App, { + const retriever = new FilterRetriever(mockApp as unknown as App, { salientTerms: ["keyword1", "keyword2"], maxK: 30, }); @@ -168,7 +177,7 @@ describe("FilterRetriever", () => { }); mockApp.vault.cachedRead.mockResolvedValue("Content"); - const retriever = new FilterRetriever(mockApp as App, { + const retriever = new FilterRetriever(mockApp as unknown as App, { salientTerms: ["#daily"], maxK: 3, }); @@ -193,7 +202,7 @@ describe("FilterRetriever", () => { }); mockApp.vault.cachedRead.mockResolvedValue("Content"); - const retriever = new FilterRetriever(mockApp as App, { + const retriever = new FilterRetriever(mockApp as unknown as App, { salientTerms: ["#daily"], maxK: 3, returnAll: true, @@ -224,7 +233,7 @@ describe("FilterRetriever", () => { }); mockApp.vault.cachedRead.mockResolvedValue("Shared content"); - const retriever = new FilterRetriever(mockApp as App, { + const retriever = new FilterRetriever(mockApp as unknown as App, { salientTerms: ["#tag1"], maxK: 30, }); @@ -252,7 +261,7 @@ describe("FilterRetriever", () => { mockApp.vault.cachedRead.mockResolvedValue("Recent content"); mockApp.metadataCache.getFileCache.mockReturnValue({ tags: [] }); - const retriever = new FilterRetriever(mockApp as App, { + const retriever = new FilterRetriever(mockApp as unknown as App, { salientTerms: [], maxK: 30, timeRange: { @@ -289,7 +298,7 @@ describe("FilterRetriever", () => { mockApp.vault.cachedRead.mockResolvedValue("Content"); mockApp.metadataCache.getFileCache.mockReturnValue({ tags: [] }); - const retriever = new FilterRetriever(mockApp as App, { + const retriever = new FilterRetriever(mockApp as unknown as App, { salientTerms: [], maxK: 30, timeRange: { @@ -306,14 +315,14 @@ describe("FilterRetriever", () => { }); it("should report hasTimeRange correctly", () => { - const withTime = new FilterRetriever(mockApp as App, { + const withTime = new FilterRetriever(mockApp as unknown as App, { salientTerms: [], maxK: 30, timeRange: { startTime: 100, endTime: 200 }, }); expect(withTime.hasTimeRange()).toBe(true); - const withoutTime = new FilterRetriever(mockApp as App, { + const withoutTime = new FilterRetriever(mockApp as unknown as App, { salientTerms: [], maxK: 30, }); diff --git a/src/tests/projectContextCache.test.ts b/src/tests/projectContextCache.test.ts index c64d2faa..27e0060f 100644 --- a/src/tests/projectContextCache.test.ts +++ b/src/tests/projectContextCache.test.ts @@ -65,7 +65,7 @@ jest.mock("@/cache/fileCache", () => { .mockImplementation( (file, additionalContext) => `key-${file.path}-${additionalContext || ""}` ), - get: jest.fn().mockImplementation(async (key) => { + get: jest.fn().mockImplementation(async (key: string) => { // Return mock content based on key if (key.includes("pdf")) return "Mock PDF content"; if (key.includes("doc")) return "Mock document content"; @@ -477,7 +477,7 @@ describe("ProjectContextCache", () => { } as unknown as ProjectConfig; // Mock cache non-existence for this isolated project - mockApp.vault.adapter.exists.mockImplementation((path) => { + mockApp.vault.adapter.exists.mockImplementation((path: string) => { if (path.includes("isolated-test-project-id")) { return Promise.resolve(false); } diff --git a/src/tools/FileTreeTools.test.ts b/src/tools/FileTreeTools.test.ts index 6c220dc0..a6d88b26 100644 --- a/src/tools/FileTreeTools.test.ts +++ b/src/tools/FileTreeTools.test.ts @@ -168,7 +168,7 @@ describe("FileTreeTools", () => { it("should exclude files based on patterns", async () => { // Mock shouldIndexFile to exclude all files in projects folder - (searchUtils.shouldIndexFile as jest.Mock).mockImplementation((file) => { + (searchUtils.shouldIndexFile as jest.Mock).mockImplementation((file: { path: string }) => { return !file.path.includes("projects"); });