diff --git a/eslint-rules/prefer-active-doc-fixed.mjs b/eslint-rules/prefer-active-doc-fixed.mjs index 891589d..b637d3e 100644 --- a/eslint-rules/prefer-active-doc-fixed.mjs +++ b/eslint-rules/prefer-active-doc-fixed.mjs @@ -9,6 +9,29 @@ const REPLACEMENTS = Object.freeze({ }); const BANNED_GLOBALS = new Set(["global", "globalThis"]); +// Timer functions are the one case where `window` is correct, not `activeWindow`: +// obsidianmd's `prefer-window-timers` rule requires `window.setTimeout()` etc. +// Skip `window.` so this rule does not contradict that one. Mirrors +// the same carve-out in upstream eslint-plugin-obsidianmd's prefer-active-doc. +const WINDOW_TIMER_METHODS = new Set([ + "clearInterval", + "clearTimeout", + "requestAnimationFrame", + "setInterval", + "setTimeout", +]); + +function isWindowTimerCall(node) { + const p = node.parent; + return ( + node.name === "window" && + p?.type === "MemberExpression" && + p.object === node && + p.property.type === "Identifier" && + WINDOW_TIMER_METHODS.has(p.property.name) + ); +} + function findVariable(scope, name) { let current = scope; while (current) { @@ -62,6 +85,7 @@ export default { if (!Object.hasOwn(REPLACEMENTS, node.name)) return; if (isSkippableParent(node)) return; + if (isWindowTimerCall(node)) return; const scope = context.sourceCode.getScope(node); if (findVariable(scope, node.name)?.defs.length) return; diff --git a/eslint.config.mjs b/eslint.config.mjs index 0a6bf03..a58f190 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -4,10 +4,11 @@ import globals from "globals"; import json from "@eslint/json"; import preferActiveDocFixed from "./eslint-rules/prefer-active-doc-fixed.mjs"; -// Upstream eslint-plugin-obsidianmd v0.2.3 `prefer-active-doc` has a prototype- -// lookup bug (REPLACEMENTS[node.name] walks the prototype chain, so every class -// `constructor` is falsely flagged). Swap the rule implementation in-place on -// the plugin object before it gets registered. +// Swap obsidianmd's `prefer-active-doc` for a local implementation. Upstream's +// rule (as of v0.3.0) only flags `document`; this project also wants +// `window` -> `activeWindow` for popout compatibility. The local rule carves out +// `window.` calls so it does not contradict `prefer-window-timers`. +// Patched in-place on the plugin object before it gets registered. if (obsidianmd.rules) { obsidianmd.rules["prefer-active-doc"] = preferActiveDocFixed; }