From 8e49af2c0e20d88b3668865332fac3ac74673fec Mon Sep 17 00:00:00 2001 From: lucaronin Date: Sun, 22 Feb 2026 13:10:26 +0100 Subject: [PATCH] fix: replace no-explicit-any with proper types - Test mocks: use { children?: React.ReactNode } and Record - Editor.tsx: use unknown[] for BlockNote block arrays - wikilinks.ts: add BlockLike/InlineItem interfaces for block processing - useMcpBridge.ts: use unknown for resolve/reject and Record - ai-chat.ts: use unknown catch + instanceof Error check - mock-tauri.ts: type messages array, use Record for args (keep one targeted eslint-disable for heterogeneous handler map) - vite.config.ts: use http.IncomingMessage/ServerResponse, typed messages Co-Authored-By: Claude Opus 4.6 --- src/App.test.tsx | 2 +- src/components/Editor.test.tsx | 6 +++--- src/components/Editor.tsx | 12 ++++++------ src/hooks/useMcpBridge.ts | 6 +++--- src/mock-tauri.ts | 5 +++-- src/utils/ai-chat.ts | 4 ++-- src/utils/wikilinks.ts | 25 ++++++++++++++++++++----- vite.config.ts | 10 +++++----- 8 files changed, 43 insertions(+), 27 deletions(-) diff --git a/src/App.test.tsx b/src/App.test.tsx index 76beef7a..4e49da70 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -83,7 +83,7 @@ vi.mock('@blocknote/react', () => ({ })) vi.mock('@blocknote/mantine', () => ({ - BlockNoteView: ({ children }: any) =>
{children}
, + BlockNoteView: ({ children }: { children?: React.ReactNode }) =>
{children}
, })) vi.mock('@blocknote/mantine/style.css', () => ({})) diff --git a/src/components/Editor.test.tsx b/src/components/Editor.test.tsx index 89aea5dc..c97e6068 100644 --- a/src/components/Editor.test.tsx +++ b/src/components/Editor.test.tsx @@ -4,13 +4,13 @@ import { describe, it, expect, vi } from 'vitest' // Hoisted mock editor — available before vi.mock factory runs. // Tests can reconfigure spies (e.g. mockTryParse.mockResolvedValue) before rendering. const mockEditor = vi.hoisted(() => ({ - tryParseMarkdownToBlocks: vi.fn(async () => [] as any[]), + tryParseMarkdownToBlocks: vi.fn(async () => [] as unknown[]), replaceBlocks: vi.fn(), insertBlocks: vi.fn(), document: [{ id: '1', type: 'paragraph', content: [], props: {}, children: [] }], insertInlineContent: vi.fn(), onMount: vi.fn((cb: () => void) => { cb(); return () => {} }), - prosemirrorView: {} as any, + prosemirrorView: {} as Record, blocksToHTMLLossy: vi.fn(() => ''), _tiptapEditor: { commands: { setContent: vi.fn() } }, })) @@ -33,7 +33,7 @@ vi.mock('@blocknote/react', () => ({ })) vi.mock('@blocknote/mantine', () => ({ - BlockNoteView: ({ children }: any) =>
{children}
, + BlockNoteView: ({ children }: { children?: React.ReactNode }) =>
{children}
, })) vi.mock('@blocknote/mantine/style.css', () => ({})) diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx index 8dcf883a..c0f3ceca 100644 --- a/src/components/Editor.tsx +++ b/src/components/Editor.tsx @@ -238,7 +238,7 @@ export const Editor = memo(function Editor({ }, }) // Cache parsed blocks per tab path for instant switching - const tabCacheRef = useRef>(new Map()) + const tabCacheRef = useRef>(new Map()) const prevActivePathRef = useRef(null) const editorMountedRef = useRef(false) const pendingSwapRef = useRef<(() => void) | null>(null) @@ -281,7 +281,7 @@ export const Editor = memo(function Editor({ const tab = tabs.find(t => t.entry.path === activeTabPath) if (!tab) return - const applyBlocks = (blocks: any[]) => { + const applyBlocks = (blocks: unknown[]) => { try { const current = editor.document if (current.length > 0 && blocks.length > 0) { @@ -317,7 +317,7 @@ export const Editor = memo(function Editor({ try { const result = editor.tryParseMarkdownToBlocks(preprocessed) - const handleBlocks = (blocks: any[]) => { + const handleBlocks = (blocks: unknown[]) => { if (prevActivePathRef.current !== targetPath) return const withWikilinks = injectWikilinks(blocks) // Only cache non-empty results to avoid poisoning the cache @@ -326,12 +326,12 @@ export const Editor = memo(function Editor({ } applyBlocks(withWikilinks) } - if (result && typeof (result as any).then === 'function') { - (result as unknown as Promise).then(handleBlocks).catch((err) => { + if (result && typeof (result as Promise).then === 'function') { + (result as Promise).then(handleBlocks).catch((err) => { console.error('Async markdown parse failed:', err) }) } else { - handleBlocks(result as any[]) + handleBlocks(result as unknown[]) } } catch (err) { console.error('Failed to parse/swap editor content:', err) diff --git a/src/hooks/useMcpBridge.ts b/src/hooks/useMcpBridge.ts index 2279b014..4a28ab31 100644 --- a/src/hooks/useMcpBridge.ts +++ b/src/hooks/useMcpBridge.ts @@ -11,8 +11,8 @@ import { useCallback, useRef, useState } from 'react' const DEFAULT_WS_URL = 'ws://localhost:9710' interface PendingRequest { - resolve: (value: any) => void - reject: (reason: any) => void + resolve: (value: unknown) => void + reject: (reason: unknown) => void } interface SearchResult { @@ -68,7 +68,7 @@ export function useMcpBridge(wsUrl = DEFAULT_WS_URL) { }) }, [wsUrl]) - const callTool = useCallback(async (tool: string, args: Record): Promise => { + const callTool = useCallback(async (tool: string, args: Record): Promise => { const ws = await ensureConnection() const id = `mcp-${++idCounterRef.current}` diff --git a/src/mock-tauri.ts b/src/mock-tauri.ts index af51761e..268fea60 100644 --- a/src/mock-tauri.ts +++ b/src/mock-tauri.ts @@ -1641,6 +1641,7 @@ index abc1234..${shortHash} 100644 let mockHasChanges = true +// eslint-disable-next-line @typescript-eslint/no-explicit-any -- mock handler map accepts heterogeneous arg types const mockHandlers: Record any> = { list_vault: () => MOCK_ENTRIES, get_note_content: (args: { path: string }) => MOCK_CONTENT[args.path] ?? '', @@ -1656,7 +1657,7 @@ const mockHandlers: Record any> = { git_push: () => { return 'Everything up-to-date' }, - ai_chat: (args: { request: { messages: any[]; model?: string; system?: string } }) => { + ai_chat: (args: { request: { messages: { role: string; content: string }[]; model?: string; system?: string } }) => { const lastMsg = args.request.messages[args.request.messages.length - 1]?.content ?? '' const lower = lastMsg.toLowerCase() let content = `I can help you with that. Could you provide more details about what you'd like to know?` @@ -1742,7 +1743,7 @@ export function updateMockContent(path: string, content: string) { } } -export async function mockInvoke(cmd: string, args?: any): Promise { +export async function mockInvoke(cmd: string, args?: Record): Promise { // Try the vault API first for commands that read vault data const apiAvailable = await checkVaultApi() if (apiAvailable) { diff --git a/src/utils/ai-chat.ts b/src/utils/ai-chat.ts index 6966e693..46efc99f 100644 --- a/src/utils/ai-chat.ts +++ b/src/utils/ai-chat.ts @@ -177,8 +177,8 @@ export async function streamChat( await readSseStream(reader, onChunk) onDone() - } catch (err: any) { - onError(err.message || 'Network error') + } catch (err: unknown) { + onError(err instanceof Error ? err.message : 'Network error') } } diff --git a/src/utils/wikilinks.ts b/src/utils/wikilinks.ts index 26f7bf2d..3d656159 100644 --- a/src/utils/wikilinks.ts +++ b/src/utils/wikilinks.ts @@ -8,21 +8,36 @@ export function preProcessWikilinks(md: string): string { return md.replace(/\[\[([^\]]+)\]\]/g, (_m, target) => `${WL_START}${target}${WL_END}`) } +// Minimal shape of a BlockNote block for wikilink processing +interface BlockLike { + content?: InlineItem[] + children?: BlockLike[] + [key: string]: unknown +} + +interface InlineItem { + type: string + text?: string + props?: Record + content?: unknown + [key: string]: unknown +} + /** Walk blocks and replace placeholder text with wikilink inline content */ -export function injectWikilinks(blocks: any[]): any[] { - return blocks.map(block => { +export function injectWikilinks(blocks: unknown[]): unknown[] { + return (blocks as BlockLike[]).map(block => { if (block.content && Array.isArray(block.content)) { block.content = expandWikilinksInContent(block.content) } if (block.children && Array.isArray(block.children)) { - block.children = injectWikilinks(block.children) + block.children = injectWikilinks(block.children) as BlockLike[] } return block }) } -function expandWikilinksInContent(content: any[]): any[] { - const result: any[] = [] +function expandWikilinksInContent(content: InlineItem[]): InlineItem[] { + const result: InlineItem[] = [] for (const item of content) { if (item.type !== 'text' || typeof item.text !== 'string' || !item.text.includes(WL_START)) { result.push(item) diff --git a/vite.config.ts b/vite.config.ts index 12e740ea..8cb6c282 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -226,7 +226,7 @@ function vaultApiPlugin(): Plugin { // --- AI Chat proxy helpers --- -function readRequestBody(req: any): Promise { +function readRequestBody(req: import('http').IncomingMessage): Promise { return new Promise((resolve) => { let body = '' req.on('data', (chunk: Buffer) => { body += chunk.toString() }) @@ -235,7 +235,7 @@ function readRequestBody(req: any): Promise { } async function forwardToAnthropic(params: { - apiKey: string; model?: string; messages: any[]; system?: string; maxTokens?: number + apiKey: string; model?: string; messages: { role: string; content: string }[]; system?: string; maxTokens?: number }): Promise { return fetch('https://api.anthropic.com/v1/messages', { method: 'POST', @@ -254,7 +254,7 @@ async function forwardToAnthropic(params: { }) } -async function streamResponseBody(source: ReadableStream, res: any): Promise { +async function streamResponseBody(source: ReadableStream, res: import('http').ServerResponse): Promise { const reader = source.getReader() const decoder = new TextDecoder() let done = false @@ -300,10 +300,10 @@ function aiChatProxyPlugin(): Plugin { } else { res.end() } - } catch (err: any) { + } catch (err: unknown) { res.statusCode = 500 res.setHeader('Content-Type', 'application/json') - res.end(JSON.stringify({ error: err.message || 'Internal server error' })) + res.end(JSON.stringify({ error: err instanceof Error ? err.message : 'Internal server error' })) } }) },