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.
This commit is contained in:
Andrew Beal 2025-11-10 21:47:18 +00:00
parent 6b5eafa4a5
commit 3a97b5b076
11 changed files with 64 additions and 32 deletions

View file

@ -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: {

View file

@ -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: {

View file

@ -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: {

View file

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

View file

@ -1,5 +1,4 @@
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const services = new Map<symbol, any>();
const services = new Map<symbol, unknown>();
export function RegisterSingleton<T>(type: symbol, instance: T): void {
services.set(type, instance);
@ -10,7 +9,6 @@ export function RegisterTransient<T>(type: symbol, factory: () => T): void {
}
export function Resolve<T>(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}`);

View file

@ -25,7 +25,7 @@ export class StreamingMarkdownService {
private readonly htmlService: HTMLService = Resolve<HTMLService>(Services.HTMLService);
private readonly fileSystemService: FileSystemService = Resolve<FileSystemService>(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<any, any, any, any, any> | null = null;
private streamingStates: Map<string, IStreamingState> = new Map<string, IStreamingState>();

View file

@ -18,6 +18,7 @@ export class StreamingService {
additionalHeaders?: Record<string, string>
): AsyncGenerator<IStreamChunk, void, unknown> {
try {
// eslint-disable-next-line no-restricted-globals -- requestUrl doeesn't support streaming
const response = await fetch(
url,
{

View file

@ -36,8 +36,7 @@ export class MainView extends ItemView {
return 'sparkles';
}
// ItemView requires onOpen to return Promise<void>, 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();

View file

@ -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
]
}
}
];

42
package-lock.json generated
View file

@ -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",

View file

@ -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",