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.
This commit is contained in:
johnny1093 2026-07-11 09:43:37 -04:00
parent fd04bcad28
commit c476604d09
14 changed files with 54 additions and 45 deletions

View file

@ -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'],
},
},

View file

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

View file

@ -8,7 +8,9 @@ const __dirname = path.dirname(__filename);
const LOCALE_DIR = path.resolve(__dirname, "../../locale");
function loadJson(file: string): Record<string, string> {
return JSON.parse(readFileSync(path.join(LOCALE_DIR, file), "utf-8"));
return JSON.parse(
readFileSync(path.join(LOCALE_DIR, file), "utf-8")
) as Record<string, string>;
}
// Extract all {{placeholder}} tokens from a string

View file

@ -142,7 +142,11 @@ export default class CommanderPlugin extends Plugin {
}
private async loadSettings(): Promise<void> {
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[] {

View file

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

View file

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

View file

@ -45,8 +45,7 @@ export default function ChangeableText({
) : (
<span
onDblClick={({ target }): void => {
/* @ts-ignore */
setWidth(target?.offsetWidth);
setWidth((target as HTMLElement | null)?.offsetWidth ?? 0);
setShowInput(true);
}}
aria-label={ariaLabel}

View file

@ -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<MacroItem[]>(
JSON.parse(JSON.stringify(macro.macro)) || []
(JSON.parse(JSON.stringify(macro.macro)) as MacroItem[]) || []
);
const handleAddCommand = async (): Promise<void> => {
@ -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
);
}}
/>
<label htmlFor="checkbox">Auto-Run on Startup</label>

View file

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

View file

@ -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({
<ChangeableText
ariaLabel={t("Double click to rename")}
handleChange={({ target }): void => {
/* @ts-ignore */
handleRename(target?.value);
handleRename(
(target as HTMLInputElement | null)?.value ?? ""
);
}}
value={pair.name}
/>

View file

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

View file

@ -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<void> {
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<boolean> {

View file

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

View file

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