From 3a97b5b076ad2625f34a64a82abc7c85835073c1 Mon Sep 17 00:00:00 2001 From: Andrew Beal Date: Mon, 10 Nov 2025 21:47:18 +0000 Subject: [PATCH] refactor: improve eslint disable comments with justifications Add eslint-plugin-eslint-comments to require descriptions on all disable directives. Replace generic eslint-disable comments with specific explanations of why rules are disabled. Change `any` type to `unknown` in DependencyService and add justification for remaining `any` usage in StreamingMarkdownService. --- .../Claude/ClaudeConversationNamingService.ts | 1 + .../Gemini/GeminiConversationNamingService.ts | 1 + .../OpenAI/OpenAIConversationNamingService.ts | 1 + Helpers/Helpers.ts | 4 +- Services/DependencyService.ts | 4 +- Services/StreamingMarkdownService.ts | 2 +- Services/StreamingService.ts | 1 + Views/MainView.ts | 3 +- eslint.config.js | 35 +++++----------- package-lock.json | 42 +++++++++++++++++++ package.json | 2 + 11 files changed, 64 insertions(+), 32 deletions(-) diff --git a/AIClasses/Claude/ClaudeConversationNamingService.ts b/AIClasses/Claude/ClaudeConversationNamingService.ts index b422c93..966dd10 100644 --- a/AIClasses/Claude/ClaudeConversationNamingService.ts +++ b/AIClasses/Claude/ClaudeConversationNamingService.ts @@ -28,6 +28,7 @@ export class ClaudeConversationNamingService implements IConversationNamingServi }] }; + // eslint-disable-next-line no-restricted-globals -- requestUrl doeesn't support AbortSignal const response = await fetch(AIProviderURL.Claude, { method: 'POST', headers: { diff --git a/AIClasses/Gemini/GeminiConversationNamingService.ts b/AIClasses/Gemini/GeminiConversationNamingService.ts index a7f91d8..6f4fb2d 100644 --- a/AIClasses/Gemini/GeminiConversationNamingService.ts +++ b/AIClasses/Gemini/GeminiConversationNamingService.ts @@ -28,6 +28,7 @@ export class GeminiConversationNamingService implements IConversationNamingServi }] }; + // eslint-disable-next-line no-restricted-globals -- requestUrl doeesn't support AbortSignal const response = await fetch(`${AIProviderURL.Gemini}/${AIProviderModel.GeminiNamer}:generateContent?key=${this.apiKey}`, { method: 'POST', headers: { diff --git a/AIClasses/OpenAI/OpenAIConversationNamingService.ts b/AIClasses/OpenAI/OpenAIConversationNamingService.ts index d11ab4f..3cf54a6 100644 --- a/AIClasses/OpenAI/OpenAIConversationNamingService.ts +++ b/AIClasses/OpenAI/OpenAIConversationNamingService.ts @@ -33,6 +33,7 @@ export class OpenAIConversationNamingService implements IConversationNamingServi ] }; + // eslint-disable-next-line no-restricted-globals -- requestUrl doeesn't support AbortSignal const response = await fetch(AIProviderURL.OpenAI, { method: 'POST', headers: { diff --git a/Helpers/Helpers.ts b/Helpers/Helpers.ts index 05d9719..57167d1 100644 --- a/Helpers/Helpers.ts +++ b/Helpers/Helpers.ts @@ -2,10 +2,10 @@ import type VaultkeeperAIPlugin from "main"; export function openPluginSettings(plugin: VaultkeeperAIPlugin) { // @ts-ignore - accessing internal API - // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access + // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access -- required to open Settings menu – internal API, no alternative, low runtime risk plugin.app.setting.open(); // @ts-ignore - accessing internal API - // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access + // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access -- required to switch Settings tab – internal API, no alternative, low runtime risk plugin.app.setting.openTabById(plugin.manifest.id); } diff --git a/Services/DependencyService.ts b/Services/DependencyService.ts index 605cdfe..dea1bfb 100644 --- a/Services/DependencyService.ts +++ b/Services/DependencyService.ts @@ -1,5 +1,4 @@ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const services = new Map(); +const services = new Map(); export function RegisterSingleton(type: symbol, instance: T): void { services.set(type, instance); @@ -10,7 +9,6 @@ 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}`); diff --git a/Services/StreamingMarkdownService.ts b/Services/StreamingMarkdownService.ts index de427f9..4130c49 100644 --- a/Services/StreamingMarkdownService.ts +++ b/Services/StreamingMarkdownService.ts @@ -25,7 +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 + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- trying to explicitly define unified types gets very complex so allow use of any private readonly processor: Processor | null = null; private streamingStates: Map = new Map(); diff --git a/Services/StreamingService.ts b/Services/StreamingService.ts index 4c33923..c7b829d 100644 --- a/Services/StreamingService.ts +++ b/Services/StreamingService.ts @@ -18,6 +18,7 @@ export class StreamingService { additionalHeaders?: Record ): AsyncGenerator { try { + // eslint-disable-next-line no-restricted-globals -- requestUrl doeesn't support streaming const response = await fetch( url, { diff --git a/Views/MainView.ts b/Views/MainView.ts index 0a8007a..0ec06bc 100644 --- a/Views/MainView.ts +++ b/Views/MainView.ts @@ -36,8 +36,7 @@ export class MainView extends ItemView { return 'sparkles'; } - // ItemView requires onOpen to return Promise, but mount operations are synchronous - // eslint-disable-next-line @typescript-eslint/require-await + // eslint-disable-next-line @typescript-eslint/require-await -- mount operations are valid but synchronous async onOpen() { const container = this.contentEl; container.empty(); diff --git a/eslint.config.js b/eslint.config.js index 86a779e..e890845 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -2,6 +2,7 @@ import js from "@eslint/js"; import tseslint from "typescript-eslint"; import obsidianmd from "eslint-plugin-obsidianmd"; +import eslintComments from "eslint-plugin-eslint-comments"; export default [ // Ignore patterns @@ -23,6 +24,7 @@ export default [ files: ["**/*.ts"], plugins: { obsidianmd: obsidianmd, + "eslint-comments": eslintComments, }, languageOptions: { parser: tseslint.parser, @@ -64,6 +66,15 @@ export default [ "@typescript-eslint/no-namespace": "error", "@typescript-eslint/no-require-imports": "error", "@typescript-eslint/switch-exhaustiveness-check": "error", + "@typescript-eslint/no-explicit-any": "error", + + // ESLint directive comment rules + "eslint-comments/require-description": "error", + "eslint-comments/no-unused-disable": "error", + "eslint-comments/no-restricted-disable": [ + "error", + "@typescript-eslint/no-explicit-any" + ], // Console usage (allow warn, error, debug only) "no-console": ["error", { allow: ["warn", "error", "debug"] }], @@ -114,29 +125,5 @@ export default [ "no-redeclare": "off", "@typescript-eslint/no-redeclare": "off", } - }, - - // Allow fetch() in streaming services (requires AbortController support) - { - files: [ - "**/StreamingService.ts", - "**/ClaudeConversationNamingService.ts", - "**/GeminiConversationNamingService.ts", - "**/OpenAIConversationNamingService.ts" - ], - rules: { - "no-restricted-globals": [ - "error", - { - name: "app", - message: "Avoid using the global app object. Instead use the reference provided by your plugin instance.", - }, - { - name: "localStorage", - message: "Prefer `App#saveLocalStorage` / `App#loadLocalStorage` functions to write / read localStorage data that's unique to a vault." - } - // fetch is allowed in these files for streaming with AbortController - ] - } } ]; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index be6c07d..4042211 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,7 @@ "zod": "^4.1.12" }, "devDependencies": { + "@eslint/js": "^9.39.1", "@testing-library/svelte": "^5.2.8", "@types/express": "^5.0.5", "@types/node": "^24.10.0", @@ -46,6 +47,7 @@ "esbuild": "^0.27.0", "esbuild-svelte": "^0.9.3", "eslint": "^9.39.1", + "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-obsidianmd": "^0.1.8", "happy-dom": "^20.0.10", "obsidian": "latest", @@ -3515,6 +3517,46 @@ "eslint": ">=8" } }, + "node_modules/eslint-plugin-eslint-comments": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-3.2.0.tgz", + "integrity": "sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5", + "ignore": "^5.0.5" + }, + "engines": { + "node": ">=6.5.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" + } + }, + "node_modules/eslint-plugin-eslint-comments/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint-plugin-eslint-comments/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/eslint-plugin-import": { "version": "2.32.0", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", diff --git a/package.json b/package.json index f104887..2f92fdf 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "author": "", "license": "MIT", "devDependencies": { + "@eslint/js": "^9.39.1", "@testing-library/svelte": "^5.2.8", "@types/express": "^5.0.5", "@types/node": "^24.10.0", @@ -31,6 +32,7 @@ "esbuild": "^0.27.0", "esbuild-svelte": "^0.9.3", "eslint": "^9.39.1", + "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-obsidianmd": "^0.1.8", "happy-dom": "^20.0.10", "obsidian": "latest",