fix(lint): carve out window timer calls in prefer-active-doc rule

The local prefer-active-doc rule flagged every `window` identifier as
preferring `activeWindow`. obsidianmd 0.3.0's new `prefer-window-timers`
rule requires the opposite for timer calls (`window.setTimeout` etc.),
so the two rules contradicted, leaving 11 unfixable warnings.

Add a `window.<timerMethod>` carve-out to the local rule, mirroring the
same exception in upstream eslint-plugin-obsidianmd's prefer-active-doc.
Lint now reports 0 warnings (4 errors remain: intentional brand-name
casing left per project decision).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Richard McCorkle 2026-05-17 07:48:09 +02:00
parent 7458806dde
commit 8800b98df4
2 changed files with 29 additions and 4 deletions

View file

@ -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.<timerMethod>` 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;

View file

@ -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.<timer>` 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;
}