no-floating-observers
Require observer instances to be retained so they can be disconnected during cleanup.
Rule catalog ID: R003
Targeted pattern scopeโ
This rule targets native observer constructors whose active observation must be
stopped with disconnect():
IntersectionObserverMutationObserverPerformanceObserverReportingObserverResizeObserver
The rule reports observer instances that are immediately discarded or chained
directly into .observe(...). In both cases there is no remaining observer
handle available for teardown.
What this rule reportsโ
The rule reports:
- standalone observer construction expressions such as
new ResizeObserver(callback); - voided observer construction such as
void new MutationObserver(callback); - immediate observation chains such as
new IntersectionObserver(callback).observe(element);
It intentionally does not require same-function disconnect() calls. That is a
separate ownership question, and valid designs transfer observer ownership
to a manager, component instance, returned disposable, or framework lifecycle.
Why this rule existsโ
Observer APIs hold callbacks and continue delivering records until disconnected.
If the observer instance is not retained, later cleanup cannot call
disconnect(), and the code has no explicit ownership point for the active
runtime resource.
Incorrectโ
new ResizeObserver(handleResize);
void new MutationObserver(handleMutations);
new IntersectionObserver(handleIntersections).observe(element);
Correctโ
const observer = new ResizeObserver(handleResize);
observer.observe(element);
observer.disconnect();
return new MutationObserver(handleMutations);
registerObserver(new IntersectionObserver(handleIntersections));
Behavior and migration notesโ
Store observer instances in the same lifecycle owner that will disconnect them. For components, that is usually the component setup scope or an owned disposable collection. For shared utilities, returning the observer or passing it to a dedicated resource manager is acceptable because ownership is explicit.
This rule does not autofix. Choosing the right owner and cleanup point is a
semantic decision, and inserting a variable without a matching disconnect()
would only make the leak less obvious.
ESLint flat config exampleโ
import runtimeCleanup from "eslint-plugin-runtime-cleanup";
export default [
runtimeCleanup.configs.recommended,
];
When not to use itโ
Do not enable this rule for code that intentionally creates page-lifetime or process-lifetime observers without later cleanup. Prefer a narrow disable comment with a reason when an observer is meant to live for the whole runtime.