fix: replace no-explicit-any with proper types

- Test mocks: use { children?: React.ReactNode } and Record<string, unknown>
- 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<string, unknown>
- ai-chat.ts: use unknown catch + instanceof Error check
- mock-tauri.ts: type messages array, use Record<string, unknown> 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 <noreply@anthropic.com>
This commit is contained in:
lucaronin
2026-02-22 13:10:26 +01:00
parent a77d5ff451
commit 8e49af2c0e
8 changed files with 43 additions and 27 deletions

View File

@@ -83,7 +83,7 @@ vi.mock('@blocknote/react', () => ({
}))
vi.mock('@blocknote/mantine', () => ({
BlockNoteView: ({ children }: any) => <div data-testid="blocknote-view">{children}</div>,
BlockNoteView: ({ children }: { children?: React.ReactNode }) => <div data-testid="blocknote-view">{children}</div>,
}))
vi.mock('@blocknote/mantine/style.css', () => ({}))

View File

@@ -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<string, unknown>,
blocksToHTMLLossy: vi.fn(() => ''),
_tiptapEditor: { commands: { setContent: vi.fn() } },
}))
@@ -33,7 +33,7 @@ vi.mock('@blocknote/react', () => ({
}))
vi.mock('@blocknote/mantine', () => ({
BlockNoteView: ({ children }: any) => <div data-testid="blocknote-view">{children}</div>,
BlockNoteView: ({ children }: { children?: React.ReactNode }) => <div data-testid="blocknote-view">{children}</div>,
}))
vi.mock('@blocknote/mantine/style.css', () => ({}))

View File

@@ -238,7 +238,7 @@ export const Editor = memo(function Editor({
},
})
// Cache parsed blocks per tab path for instant switching
const tabCacheRef = useRef<Map<string, any[]>>(new Map())
const tabCacheRef = useRef<Map<string, unknown[]>>(new Map())
const prevActivePathRef = useRef<string | null>(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<any[]>).then(handleBlocks).catch((err) => {
if (result && typeof (result as Promise<unknown[]>).then === 'function') {
(result as Promise<unknown[]>).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)

View File

@@ -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 <T>(tool: string, args: Record<string, any>): Promise<T> => {
const callTool = useCallback(async <T>(tool: string, args: Record<string, unknown>): Promise<T> => {
const ws = await ensureConnection()
const id = `mcp-${++idCounterRef.current}`

View File

@@ -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<string, (args: any) => any> = {
list_vault: () => MOCK_ENTRIES,
get_note_content: (args: { path: string }) => MOCK_CONTENT[args.path] ?? '',
@@ -1656,7 +1657,7 @@ const mockHandlers: Record<string, (args: any) => 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<T>(cmd: string, args?: any): Promise<T> {
export async function mockInvoke<T>(cmd: string, args?: Record<string, unknown>): Promise<T> {
// Try the vault API first for commands that read vault data
const apiAvailable = await checkVaultApi()
if (apiAvailable) {

View File

@@ -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')
}
}

View File

@@ -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<string, string>
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)

View File

@@ -226,7 +226,7 @@ function vaultApiPlugin(): Plugin {
// --- AI Chat proxy helpers ---
function readRequestBody(req: any): Promise<string> {
function readRequestBody(req: import('http').IncomingMessage): Promise<string> {
return new Promise((resolve) => {
let body = ''
req.on('data', (chunk: Buffer) => { body += chunk.toString() })
@@ -235,7 +235,7 @@ function readRequestBody(req: any): Promise<string> {
}
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<Response> {
return fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
@@ -254,7 +254,7 @@ async function forwardToAnthropic(params: {
})
}
async function streamResponseBody(source: ReadableStream<Uint8Array>, res: any): Promise<void> {
async function streamResponseBody(source: ReadableStream<Uint8Array>, res: import('http').ServerResponse): Promise<void> {
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' }))
}
})
},