diff --git a/Services/AIFunctionService.ts b/Services/AIFunctionService.ts index ba2c7b8..d663a45 100644 --- a/Services/AIFunctionService.ts +++ b/Services/AIFunctionService.ts @@ -47,7 +47,7 @@ export class AIFunctionService { } private async searchVaultFiles(searchTerms: string[]): Promise { - let results: { searchTerm: string, results: object[] }[] = []; + const results: { searchTerm: string, results: object[] }[] = []; for (const searchTerm of searchTerms) { const matches: ISearchMatch[] = searchTerm.trim() === "" ? [] : await this.fileSystemService.searchVaultFiles(searchTerm); diff --git a/Services/ConversationNamingService.ts b/Services/ConversationNamingService.ts index 84b398b..6b3b2b2 100644 --- a/Services/ConversationNamingService.ts +++ b/Services/ConversationNamingService.ts @@ -55,7 +55,7 @@ export class ConversationNamingService { } private async validateName(generatedName: string): Promise { - let cleanedTitle = generatedName.trim().replace(/^["']|["']$/g, "").split(/\s+/).slice(0, 6).join(" "); + const cleanedTitle = generatedName.trim().replace(/^["']|["']$/g, "").split(/\s+/).slice(0, 6).join(" "); let index = 1; let availableTitle = cleanedTitle; diff --git a/Services/FileSystemService.ts b/Services/FileSystemService.ts index bc33ca2..1300d86 100644 --- a/Services/FileSystemService.ts +++ b/Services/FileSystemService.ts @@ -36,7 +36,7 @@ export class FileSystemService { await this.vaultService.create(filePath, content, allowAccessToPluginRoot); return true; } - await this.vaultService.modify(file as TFile, content, allowAccessToPluginRoot); + await this.vaultService.modify(file, content, allowAccessToPluginRoot); return true; } catch (error) { diff --git a/Services/InputService.ts b/Services/InputService.ts index 50122fe..8aa2425 100644 --- a/Services/InputService.ts +++ b/Services/InputService.ts @@ -143,8 +143,8 @@ export class InputService { return null; // Selection is not collapsed, not at a single cursor position } - let node = range.startContainer; - let offset = range.startOffset; + const node = range.startContainer; + const offset = range.startOffset; // If we're in a text node if (node.nodeType === Node.TEXT_NODE) { diff --git a/Services/VaultService.ts b/Services/VaultService.ts index 361461e..9fafba1 100644 --- a/Services/VaultService.ts +++ b/Services/VaultService.ts @@ -155,7 +155,7 @@ export class VaultService { } let files: TFile[] = []; - for (let child of dir.children) { + for (const child of dir.children) { if (child instanceof TFile) { if (!this.isExclusion(child.path, allowAccessToPluginRoot)) { files.push(child); @@ -179,7 +179,7 @@ export class VaultService { } let folders: TFolder[] = []; - for (let child of dir.children) { + for (const child of dir.children) { if (!(child instanceof TFolder)) { continue; } diff --git a/eslint.config.js b/eslint.config.js index e9a5cc8..86a779e 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -2,46 +2,73 @@ import js from "@eslint/js"; import tseslint from "typescript-eslint"; import obsidianmd from "eslint-plugin-obsidianmd"; -import sdl from "@microsoft/eslint-plugin-sdl"; -import importPlugin from "eslint-plugin-import"; export default [ // Ignore patterns { - ignores: ["node_modules/**", "main.js"] + ignores: ["node_modules/**", "main.js", "__tests__/**", "*.test.ts", "esbuild.config.mjs", "version-bump.mjs", "vitest.config.ts"] }, // Base ESLint recommended rules js.configs.recommended, - // TypeScript configuration for .ts files + // TypeScript recommended rules (type-checked rules disabled for non-TS files) + ...tseslint.configs.recommendedTypeChecked.map(config => ({ + ...config, + files: ["**/*.ts"], + })), + + // Project-specific configuration { files: ["**/*.ts"], plugins: { - "@typescript-eslint": tseslint.plugin, - "obsidianmd": obsidianmd, - "@microsoft/sdl": sdl, - "import": importPlugin, + obsidianmd: obsidianmd, }, languageOptions: { parser: tseslint.parser, parserOptions: { project: true, sourceType: "module" + }, + globals: { + console: "readonly", + AsyncGenerator: "readonly", + require: "readonly", + process: "readonly", + createEl: "readonly", + join: "readonly", + __dirname: "readonly", + document: "readonly", + window: "readonly", + performance: "readonly", + requestAnimationFrame: "readonly", + cancelAnimationFrame: "readonly", + setTimeout: "readonly", + clearTimeout: "readonly", + NodeJS: "readonly", + Fuzzysort: "readonly", } }, rules: { - // Base rules - "no-unused-vars": "off", + // Obsidian plugin recommended rules + ...obsidianmd.configs.recommended, + + // Override TypeScript recommended to match Obsidian's style + "@typescript-eslint/ban-ts-comment": "off", + "@typescript-eslint/no-empty-function": "off", "no-prototype-builtins": "off", - "no-self-compare": "warn", - "no-eval": "error", - "no-implied-eval": "error", - "prefer-const": "off", - "no-implicit-globals": "error", + + // Enable additional TypeScript rules for PR issues + "@typescript-eslint/require-await": "error", + "@typescript-eslint/no-floating-promises": "error", + "@typescript-eslint/no-namespace": "error", + "@typescript-eslint/no-require-imports": "error", + "@typescript-eslint/switch-exhaustiveness-check": "error", + + // Console usage (allow warn, error, debug only) "no-console": ["error", { allow: ["warn", "error", "debug"] }], - // Restricted globals (with fetch removed for streaming) + // Restricted globals "no-restricted-globals": [ "error", { @@ -51,6 +78,10 @@ export default [ { name: "localStorage", message: "Prefer `App#saveLocalStorage` / `App#loadLocalStorage` functions to write / read localStorage data that's unique to a vault." + }, + { + name: "fetch", + message: "Use the built-in `requestUrl` function instead of `fetch` for network requests." } ], @@ -79,59 +110,33 @@ export default [ }, ], - "no-alert": "error", - "no-undef": "error", + // Allow namespace merging with enums (TypeScript pattern) + "no-redeclare": "off", + "@typescript-eslint/no-redeclare": "off", + } + }, - // TypeScript rules - "@typescript-eslint/ban-ts-comment": "off", - "@typescript-eslint/no-deprecated": "error", - "@typescript-eslint/no-unused-vars": ["warn", { args: "none" }], - "@typescript-eslint/require-await": "off", - "@typescript-eslint/no-explicit-any": ["error", { fixToUnknown: true }], - "@typescript-eslint/no-empty-function": "off", - - // Security rules - "@microsoft/sdl/no-document-write": "error", - "@microsoft/sdl/no-inner-html": "error", - - // Import rules - "import/no-nodejs-modules": "error", - "import/no-extraneous-dependencies": "error", - - // Obsidian plugin rules - Commands - "obsidianmd/commands/no-command-in-command-id": "error", - "obsidianmd/commands/no-command-in-command-name": "error", - "obsidianmd/commands/no-default-hotkeys": "error", - "obsidianmd/commands/no-plugin-id-in-command-id": "error", - "obsidianmd/commands/no-plugin-name-in-command-name": "error", - - // Obsidian plugin rules - Settings Tab - "obsidianmd/settings-tab/no-manual-html-headings": "error", - "obsidianmd/settings-tab/no-problematic-settings-headings": "error", - - // Obsidian plugin rules - Vault - "obsidianmd/vault/iterate": "error", - - // Obsidian plugin rules - General - "obsidianmd/detach-leaves": "error", - "obsidianmd/hardcoded-config-path": "error", - "obsidianmd/no-forbidden-elements": "error", - "obsidianmd/no-plugin-as-component": "error", - "obsidianmd/no-sample-code": "error", - "obsidianmd/no-tfile-tfolder-cast": "error", - "obsidianmd/no-view-references-in-plugin": "error", - "obsidianmd/no-static-styles-assignment": "error", - "obsidianmd/object-assign": "error", - "obsidianmd/platform": "error", - "obsidianmd/prefer-file-manager-trash-file": "warn", - "obsidianmd/prefer-abstract-input-suggest": "error", - "obsidianmd/regex-lookbehind": "error", - "obsidianmd/sample-names": "error", - "obsidianmd/validate-manifest": "error", - "obsidianmd/validate-license": "error", - - // Obsidian plugin rules - UI - "obsidianmd/ui/sentence-case": ["error", { enforceCamelCaseLower: true }], + // 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