no-floating-disposable-stacks
Require DisposableStack handles to be retained so registered disposers run.
Rule catalog ID: R008
Targeted pattern scopeโ
This rule targets the standard explicit resource-management stack constructors:
DisposableStackAsyncDisposableStackglobalThis.DisposableStack,window.DisposableStack, andself.DisposableStackglobalThis.AsyncDisposableStack,window.AsyncDisposableStack, andself.AsyncDisposableStack
The rule reports disposable stack instances that are immediately discarded or
chained directly into a method call other than .dispose() or
.disposeAsync(). In both cases there is no remaining stack handle available to
dispose registered resources.
What this rule reportsโ
The rule reports:
- standalone stack construction such as
new DisposableStack(); - voided stack construction such as
void new AsyncDisposableStack(); - immediate registration calls such as
new DisposableStack().defer(cleanup); - immediate async registration calls such as
new AsyncDisposableStack().use(resource);
It intentionally does not require same-function disposal for retained stacks.
Ownership can be handled by a using or await using declaration, a returned
stack, or a dedicated lifecycle manager.
Why this rule existsโ
DisposableStack and AsyncDisposableStack are ownership containers. They only
provide cleanup if the stack itself is retained and later disposed. Discarding
the stack after registering work silently drops the only object that can run the
registered disposers.
Incorrectโ
new DisposableStack();
void new AsyncDisposableStack();
new DisposableStack().defer(cleanup);
new AsyncDisposableStack().use(resource);
Correctโ
using stack = new DisposableStack();
stack.defer(cleanup);
await using stack = new AsyncDisposableStack();
stack.use(resource);
return new DisposableStack();
registerDisposableStack(new AsyncDisposableStack());
Behavior and migration notesโ
Keep the stack in the same lifecycle owner that will dispose it. Prefer
using for synchronous stacks and await using for asynchronous stacks when
the current runtime and compiler settings support explicit resource
management. If a framework or library owns disposal, pass the stack directly to
that owner.
This rule does not autofix. Choosing between using, await using, returning
the stack, or wiring a lifecycle manager changes ownership semantics and must
be decided by the developer.
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 generated code or compatibility layers that intentionally construct disposable stack shims for feature detection. Prefer a narrow disable comment with a reason for those cases.