From c476604d092729e5215a3ada936be61ccd713f50 Mon Sep 17 00:00:00 2001 From: johnny1093 <46250921+jsmorabito@users.noreply.github.com> Date: Sat, 11 Jul 2026 09:43:37 -0400 Subject: [PATCH] lint: replace remaining any-typed casts with proper types Resolves the rest of the no-unsafe-* family by giving TypeScript enough information instead of suppressing it: - Extend the Obsidian module augmentation (types.ts) with App.commands.removeCommand and Vault.getConfig so util.tsx and main.ts no longer need @ts-ignore around them. - Cast event.target to the concrete HTMLElement subtype at each DOM event handler that reads .value/.checked/.offsetWidth (ChangeableText, MacroBuilder, commandComponent) instead of @ts-ignore/@ts-expect-error. - Cast JSON.parse() results to their known shape (MacroBuilder's macro clone, locales.test.ts's loadJson, main.ts's loadSettings) instead of letting `any` flow through. - Drop menuManager's unusual `this: (_item: MenuItem) => void` parameter and the .call(this, ...) it required - the returned closure never reads `this`, so this was both unnecessary and, under this project's non-strict bind/call/apply typing, exactly why the arguments passed through untyped. Calling the method directly is equivalent and fully typed. - Same root cause for MacroBuilderModal's this.close.bind(this) and executeMacro.test.ts's .bind() - replaced with equivalent non-bind forms. - confirmDeleteModal/mobileModifyModal: inline the h(...) call into render() instead of round-tripping through an intermediately VNode-typed field, which is where preact's h() lost prop typing. - vitest.config.ts-style import.meta.dirname swapped for fileURLToPath(import.meta.url) in eslint.config.mts, since the project's TS lib doesn't type import.meta.dirname. Type-level only; no behavior change beyond what's already covered by the two preceding commits. --- eslint.config.mts | 6 +++++- src/__tests__/executeMacro.test.ts | 4 +++- src/__tests__/locales.test.ts | 4 +++- src/main.ts | 12 ++++++------ src/manager/commands/menuManager.ts | 7 ++----- src/types.ts | 5 +++++ src/ui/components/ChangeableText.tsx | 3 +-- src/ui/components/MacroBuilder.tsx | 7 ++++--- src/ui/components/MacroBuilderModal.ts | 2 +- src/ui/components/commandComponent.tsx | 9 +++++---- src/ui/components/settingTabComponent.tsx | 16 ++++++++-------- src/ui/confirmDeleteModal.ts | 6 ++---- src/ui/mobileModifyModal.ts | 15 ++++++++------- src/util.tsx | 3 +-- 14 files changed, 54 insertions(+), 45 deletions(-) diff --git a/eslint.config.mts b/eslint.config.mts index 6e3f799..df9b4ba 100644 --- a/eslint.config.mts +++ b/eslint.config.mts @@ -2,6 +2,10 @@ import tseslint from 'typescript-eslint'; import obsidianmd from 'eslint-plugin-obsidianmd'; import globals from 'globals'; import { defineConfig, globalIgnores } from 'eslint/config'; +import { fileURLToPath } from 'url'; +import path from 'path'; + +const dirname = path.dirname(fileURLToPath(import.meta.url)); export default defineConfig( globalIgnores([ @@ -22,7 +26,7 @@ export default defineConfig( projectService: { allowDefaultProject: ['eslint.config.mts', 'manifest.json'], }, - tsconfigRootDir: import.meta.dirname, + tsconfigRootDir: dirname, extraFileExtensions: ['.json'], }, }, diff --git a/src/__tests__/executeMacro.test.ts b/src/__tests__/executeMacro.test.ts index 07d39ef..e3d19ed 100644 --- a/src/__tests__/executeMacro.test.ts +++ b/src/__tests__/executeMacro.test.ts @@ -12,7 +12,9 @@ function buildPlugin(macros: Macro[]): CommanderPlugin { commands: { executeCommandById }, }, } as unknown as CommanderPlugin; - plugin.executeMacro = CommanderPluginClass.prototype.executeMacro.bind(plugin); + plugin.executeMacro = CommanderPluginClass.prototype.executeMacro.bind( + plugin + ) as CommanderPlugin["executeMacro"]; return plugin; } diff --git a/src/__tests__/locales.test.ts b/src/__tests__/locales.test.ts index 683ae6a..a9b7d34 100644 --- a/src/__tests__/locales.test.ts +++ b/src/__tests__/locales.test.ts @@ -8,7 +8,9 @@ const __dirname = path.dirname(__filename); const LOCALE_DIR = path.resolve(__dirname, "../../locale"); function loadJson(file: string): Record { - return JSON.parse(readFileSync(path.join(LOCALE_DIR, file), "utf-8")); + return JSON.parse( + readFileSync(path.join(LOCALE_DIR, file), "utf-8") + ) as Record; } // Extract all {{placeholder}} tokens from a string diff --git a/src/main.ts b/src/main.ts index deb3cdd..b76f460 100644 --- a/src/main.ts +++ b/src/main.ts @@ -142,7 +142,11 @@ export default class CommanderPlugin extends Plugin { } private async loadSettings(): Promise { - const data = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); + const data = Object.assign( + {}, + DEFAULT_SETTINGS, + await this.loadData() + ) as CommanderSettings; this.settings = data; } @@ -151,11 +155,7 @@ export default class CommanderPlugin extends Plugin { } public listActiveToolbarCommands(): string[] { - //@ts-ignore - const activeCommands = this.app.vault.getConfig( - "mobileToolbarCommands" - ); - return activeCommands; + return this.app.vault.getConfig("mobileToolbarCommands") as string[]; } public getCommands(): Command[] { diff --git a/src/manager/commands/menuManager.ts b/src/manager/commands/menuManager.ts index 0178abd..cb2977f 100644 --- a/src/manager/commands/menuManager.ts +++ b/src/manager/commands/menuManager.ts @@ -33,7 +33,6 @@ abstract class Base extends CommandManagerBase { public reorder(): void {} protected addRemovableCommand( - this: (_item: MenuItem) => void, command: Command, cmdPair: CommandIconPair, plugin: CommanderPlugin, @@ -197,8 +196,7 @@ export class EditorMenuCommandManager extends Base { continue; menu.addItem( - this.addRemovableCommand.call( - this, + this.addRemovableCommand( command, cmdPair, plugin, @@ -253,8 +251,7 @@ export class FileMenuCommandManager extends Base { } menu.addItem( - this.addRemovableCommand.call( - this, + this.addRemovableCommand( command, cmdPair, plugin, diff --git a/src/types.ts b/src/types.ts index 848583a..c054042 100644 --- a/src/types.ts +++ b/src/types.ts @@ -83,6 +83,7 @@ declare module "obsidian" { [id: string]: Command; }; executeCommandById: (id: string) => void; + removeCommand: (id: string) => void; }; plugins: { manifests: { @@ -131,4 +132,8 @@ declare module "obsidian" { interface WorkspaceLeaf { containerEl: HTMLElement; } + + interface Vault { + getConfig(key: string): unknown; + } } diff --git a/src/ui/components/ChangeableText.tsx b/src/ui/components/ChangeableText.tsx index 2dd0b4d..f115427 100644 --- a/src/ui/components/ChangeableText.tsx +++ b/src/ui/components/ChangeableText.tsx @@ -45,8 +45,7 @@ export default function ChangeableText({ ) : ( { - /* @ts-ignore */ - setWidth(target?.offsetWidth); + setWidth((target as HTMLElement | null)?.offsetWidth ?? 0); setShowInput(true); }} aria-label={ariaLabel} diff --git a/src/ui/components/MacroBuilder.tsx b/src/ui/components/MacroBuilder.tsx index de6cef4..ab122de 100644 --- a/src/ui/components/MacroBuilder.tsx +++ b/src/ui/components/MacroBuilder.tsx @@ -23,7 +23,7 @@ export default function ({ const [icon, setIcon] = useState(macro.icon || "star"); const [startup, setStartup] = useState(macro.startup || false); const [macroCommands, setMacroCommands] = useState( - JSON.parse(JSON.stringify(macro.macro)) || [] + (JSON.parse(JSON.stringify(macro.macro)) as MacroItem[]) || [] ); const handleAddCommand = async (): Promise => { @@ -237,8 +237,9 @@ export default function ({ id="checkbox" checked={startup} onChange={({ target }): void => { - //@ts-expect-error - setStartup(target?.checked ?? false); + setStartup( + (target as HTMLInputElement | null)?.checked ?? false + ); }} /> diff --git a/src/ui/components/MacroBuilderModal.ts b/src/ui/components/MacroBuilderModal.ts index e1ed92b..4cf561c 100644 --- a/src/ui/components/MacroBuilderModal.ts +++ b/src/ui/components/MacroBuilderModal.ts @@ -27,7 +27,7 @@ export default class MacroBuilderModal extends Modal { plugin: this.plugin, macro: this.macro, onSave: this.onSave, - onCancel: this.close.bind(this), + onCancel: (): void => this.close(), }), this.contentEl ); diff --git a/src/ui/components/commandComponent.tsx b/src/ui/components/commandComponent.tsx index 3b2c1f0..0837b8c 100644 --- a/src/ui/components/commandComponent.tsx +++ b/src/ui/components/commandComponent.tsx @@ -100,8 +100,8 @@ export default function CommandComponent({ const owningPlugin = plugin.app.plugins.manifests[owningPluginID!]; const isInternal = !owningPlugin; const isChecked = - Object.prototype.hasOwnProperty.call(cmd, "checkCallback") || - Object.prototype.hasOwnProperty.call(cmd, "editorCheckCallback"); + (Object.prototype.hasOwnProperty.call(cmd, "checkCallback") as boolean) || + (Object.prototype.hasOwnProperty.call(cmd, "editorCheckCallback") as boolean); const modeIcon = getModeIcon(pair.mode); const modeName = pair.mode.match(/desktop|mobile|any/) @@ -124,8 +124,9 @@ export default function CommandComponent({ { - /* @ts-ignore */ - handleRename(target?.value); + handleRename( + (target as HTMLInputElement | null)?.value ?? "" + ); }} value={pair.name} /> diff --git a/src/ui/components/settingTabComponent.tsx b/src/ui/components/settingTabComponent.tsx index 07f76b3..3c22fd1 100644 --- a/src/ui/components/settingTabComponent.tsx +++ b/src/ui/components/settingTabComponent.tsx @@ -176,14 +176,14 @@ export default function settingTabComponent({ } ); - plugin.app.setting.activeTab.containerEl - .querySelectorAll( - ".setting-item-heading" - )[1] - // @ts-ignore - .nextSibling?.nextSibling?.nextSibling?.addClass?.( - "cmdr-cta" - ); + ( + plugin.app.setting.activeTab.containerEl + .querySelectorAll( + ".setting-item-heading" + )[1] + .nextSibling?.nextSibling + ?.nextSibling as HTMLElement | null + )?.addClass?.("cmdr-cta"); }, 50); }} className="mod-cta" diff --git a/src/ui/confirmDeleteModal.ts b/src/ui/confirmDeleteModal.ts index 3981e8b..1cf9e16 100644 --- a/src/ui/confirmDeleteModal.ts +++ b/src/ui/confirmDeleteModal.ts @@ -1,11 +1,10 @@ import { Modal } from "obsidian"; -import { h, render, VNode } from "preact"; +import { h, render } from "preact"; import t from "src/l10n"; import CommanderPlugin from "src/main"; import { confirmDeleteComponent } from "./components/confirmDeleteComponent"; export default class ConfirmDeleteModal extends Modal { - private reactComponent: VNode; public remove: boolean; public constructor(public plugin: CommanderPlugin) { @@ -15,8 +14,7 @@ export default class ConfirmDeleteModal extends Modal { public async onOpen(): Promise { this.titleEl.innerText = t("Remove Command"); this.containerEl.style.zIndex = "99"; - this.reactComponent = h(confirmDeleteComponent, { modal: this }); - render(this.reactComponent, this.contentEl); + render(h(confirmDeleteComponent, { modal: this }), this.contentEl); } public async didChooseRemove(): Promise { diff --git a/src/ui/mobileModifyModal.ts b/src/ui/mobileModifyModal.ts index a8c4fd4..911261e 100644 --- a/src/ui/mobileModifyModal.ts +++ b/src/ui/mobileModifyModal.ts @@ -1,11 +1,10 @@ import { CommandIconPair } from "src/types"; import { Modal } from "obsidian"; -import { h, render, VNode } from "preact"; +import { h, render } from "preact"; import MobileModifyComponent from "./components/mobileModifyComponent"; import CommanderPlugin from "src/main"; export default class MobileModifyModal extends Modal { - private reactComponent: VNode; public remove: boolean; public constructor( @@ -21,11 +20,13 @@ export default class MobileModifyModal extends Modal { public async onOpen(): Promise { this.titleEl.innerText = this.pair.name; - this.reactComponent = h(MobileModifyComponent, { - plugin: this.plugin, - modal: this, - }); - render(this.reactComponent, this.contentEl); + render( + h(MobileModifyComponent, { + plugin: this.plugin, + modal: this, + }), + this.contentEl + ); } public onClose(): void { diff --git a/src/util.tsx b/src/util.tsx index 3b410e7..44f3e7b 100644 --- a/src/util.tsx +++ b/src/util.tsx @@ -24,7 +24,7 @@ export async function chooseNewCommand( const command = await new AddCommandModal(plugin).awaitSelection(); let icon; - if (!Object.prototype.hasOwnProperty.call(command, "icon")) { + if (!(Object.prototype.hasOwnProperty.call(command, "icon") as boolean)) { icon = await new ChooseIconModal(plugin).awaitSelection(); } @@ -141,7 +141,6 @@ export function updateMacroCommands(plugin: CommanderPlugin): void { p.startsWith("cmdr:macro-") ); for (const command of oldCommands) { - //@ts-ignore plugin.app.commands.removeCommand(command); }