From 4b9f703aa2e224d570fd92f944d004a3d91ff9df Mon Sep 17 00:00:00 2001 From: Andrew Beal Date: Mon, 10 Nov 2025 20:37:13 +0000 Subject: [PATCH] fix: improve TypeScript type safety across services and mocks - Add explicit type assertions and casts to resolve any types - Update return types from any to Error | boolean - Add eslint-disable comments for unavoidable any types - Improve mock type safety replacing any with unknown - Add proper type parameters to callback functions --- Services/AIFunctionService.ts | 4 +-- Services/DependencyService.ts | 4 ++- Services/FileSystemService.ts | 6 ++-- Services/StreamingMarkdownService.ts | 3 +- __mocks__/obsidian.ts | 52 ++++++++++++++-------------- 5 files changed, 36 insertions(+), 33 deletions(-) diff --git a/Services/AIFunctionService.ts b/Services/AIFunctionService.ts index 13dd482..274f37d 100644 --- a/Services/AIFunctionService.ts +++ b/Services/AIFunctionService.ts @@ -98,7 +98,7 @@ export class AIFunctionService { return new AIFunctionResponse(functionCall.name, {}, functionCall.toolId) default: { - const error = `Unknown function request ${functionCall.name}` + const error = `Unknown function request ${functionCall.name as string}` console.error(error); return new AIFunctionResponse( functionCall.name, @@ -143,7 +143,7 @@ export class AIFunctionService { } private async writeVaultFile(filePath: string, content: string): Promise { - const result: boolean | unknown = await this.fileSystemService.writeFile(normalizePath(filePath), content); + const result: boolean | Error = await this.fileSystemService.writeFile(normalizePath(filePath), content); return typeof result === "boolean" ? { success: result } : { success: false, error: result }; } diff --git a/Services/DependencyService.ts b/Services/DependencyService.ts index 8f6ae6d..605cdfe 100644 --- a/Services/DependencyService.ts +++ b/Services/DependencyService.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line @typescript-eslint/no-explicit-any const services = new Map(); export function RegisterSingleton(type: symbol, instance: T): void { @@ -9,6 +10,7 @@ export function RegisterTransient(type: symbol, factory: () => T): void { } export function Resolve(type: symbol): T { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const service = services.get(type); if (!service) { throw new Error(`Service not found for type: ${type.description}`); @@ -16,7 +18,7 @@ export function Resolve(type: symbol): T { if (typeof service === 'function') { // It's a transient factory, return a new instance - return service(); + return (service as () => T)(); } // It's a singleton, return the existing instance diff --git a/Services/FileSystemService.ts b/Services/FileSystemService.ts index 8fb8080..818d9e0 100644 --- a/Services/FileSystemService.ts +++ b/Services/FileSystemService.ts @@ -28,7 +28,7 @@ export class FileSystemService { return null; } - public async writeFile(filePath: string, content: string, allowAccessToPluginRoot: boolean = false): Promise { + public async writeFile(filePath: string, content: string, allowAccessToPluginRoot: boolean = false): Promise { try { const file: TAbstractFile | null = this.vaultService.getAbstractFileByPath(filePath, allowAccessToPluginRoot); if (file == null || !(file instanceof TFile)) { @@ -40,7 +40,7 @@ export class FileSystemService { } catch (error) { console.error("Error writing file:", error); - return error; + return error instanceof Error ? error : new Error(String(error)); } } @@ -75,7 +75,7 @@ export class FileSystemService { if (file && file instanceof TFile) { const content = await this.vaultService.read(file, allowAccessToPluginRoot); if (isValidJson(content) === true) { - return JSON.parse(content); + return JSON.parse(content) as object; } } return null; diff --git a/Services/StreamingMarkdownService.ts b/Services/StreamingMarkdownService.ts index a41cfab..de427f9 100644 --- a/Services/StreamingMarkdownService.ts +++ b/Services/StreamingMarkdownService.ts @@ -25,6 +25,7 @@ export class StreamingMarkdownService { private readonly htmlService: HTMLService = Resolve(Services.HTMLService); private readonly fileSystemService: FileSystemService = Resolve(Services.FileSystemService); + // eslint-disable-next-line @typescript-eslint/no-explicit-any private readonly processor: Processor | null = null; private streamingStates: Map = new Map(); @@ -164,7 +165,7 @@ export class StreamingMarkdownService { .replace(/\r\n/g, "\n") .replace(/\r/g, "\n") // Convert LaTeX delimiters - .replace(/\\\[([\s\S]*?)\\\]/g, (match, math) => { + .replace(/\\\[([\s\S]*?)\\\]/g, (_match: string, math: string) => { // Ensure math blocks are on their own lines return "\n$$\n" + math.trim() + "\n$$\n"; }) diff --git a/__mocks__/obsidian.ts b/__mocks__/obsidian.ts index 8b6634b..500ff86 100644 --- a/__mocks__/obsidian.ts +++ b/__mocks__/obsidian.ts @@ -1,8 +1,8 @@ import { vi } from 'vitest'; export class Plugin { - app: any; - manifest: any; + app: unknown; + manifest: unknown; constructor() { this.app = {}; this.manifest = {}; @@ -16,10 +16,10 @@ export class Plugin { } export class PluginSettingTab { - app: any; - plugin: any; - containerEl: any; - constructor(app: any, plugin: any) { + app: unknown; + plugin: unknown; + containerEl: unknown; + constructor(app: unknown, plugin: unknown) { this.app = app; this.plugin = plugin; this.containerEl = { createEl: vi.fn(), empty: vi.fn() }; @@ -29,25 +29,25 @@ export class PluginSettingTab { } export class ItemView { - app: any; - leaf: any; - containerEl: any = { createEl: vi.fn(), empty: vi.fn() }; + app: unknown; + leaf: unknown; + containerEl: unknown = { createEl: vi.fn(), empty: vi.fn() }; constructor() { this.app = {}; this.leaf = {}; } getViewType() { return 'test-view'; } - getDisplayText() { return 'Test View'; } + getDisplayText() { return 'Test view'; } onOpen() { return Promise.resolve(); } onClose() { return Promise.resolve(); } } export class Modal { - app: any; - containerEl: any; - titleEl: any; - contentEl: any; - constructor(app: any) { + app: unknown; + containerEl: unknown; + titleEl: unknown; + contentEl: unknown; + constructor(app: unknown) { this.app = app; this.containerEl = { createEl: vi.fn(), empty: vi.fn() }; this.titleEl = { createEl: vi.fn(), empty: vi.fn() }; @@ -58,7 +58,7 @@ export class Modal { } export class Notice { - constructor(message: string) {} + constructor() {} hide() {} } @@ -68,20 +68,20 @@ export class TFile { basename: string = ''; extension: string = ''; stat: { ctime: number; mtime: number; size: number } = { ctime: Date.now(), mtime: Date.now(), size: 0 }; - parent: any = null; - vault: any; + parent: unknown = null; + vault: unknown; } export class TFolder { path: string = ''; name: string = ''; - children: any[] = []; - parent: any = null; - vault: any; + children: unknown[] = []; + parent: unknown = null; + vault: unknown; } export interface TAbstractFile { - vault: any; + vault: unknown; path: string; name: string; parent: TFolder | null; @@ -102,7 +102,7 @@ export const requestUrl = vi.fn(() => Promise.resolve({ export const setIcon = vi.fn(); export class Vault { - adapter: any; + adapter: unknown; constructor() { this.adapter = { @@ -131,15 +131,15 @@ export class FileManager { } export class WorkspaceLeaf { - view: any; + view: unknown; getViewState() { return {}; } setViewState() { return Promise.resolve(); } } export class Setting { - settingEl: any; + settingEl: unknown; - constructor(containerEl: any) { + constructor() { this.settingEl = { createEl: vi.fn(), empty: vi.fn() }; }