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");
});