no-floating-message-channels
Require MessageChannel ports to be retained so they can be closed.
Rule catalog ID: R012
Targeted pattern scopeโ
This rule targets browser MessageChannel constructors:
MessageChannelwindow.MessageChannelself.MessageChannelglobalThis.MessageChannel
The rule reports channel instances that are immediately discarded or whose
port1/port2 properties are accessed directly from the temporary channel
object. Keeping only one inline port loses the peer port and makes full
teardown ambiguous.
What this rule reportsโ
The rule reports:
- standalone channel construction such as
new MessageChannel(); - voided channel construction such as
void new MessageChannel(); - immediate port sends such as
new MessageChannel().port1.postMessage(message); - retaining a single inline port such as
const port = new MessageChannel().port1;
It intentionally does not require same-function close() calls. Ownership can
be transferred to a component instance, channel manager, returned value, or both
destructured MessagePort handles.
Why this rule existsโ
MessageChannel creates two linked MessagePort handles. MessagePort.close()
disconnects a port so it is no longer active. If the channel object is discarded
or only one port is retained from an inline expression, code cannot reliably
close both sides of the channel during cleanup.
Incorrectโ
new MessageChannel();
void new MessageChannel();
new MessageChannel().port1.postMessage(message);
const port = new MessageChannel().port2;
Correctโ
const channel = new MessageChannel();
channel.port1.addEventListener("message", onMessage);
channel.port1.close();
channel.port2.close();
const { port1, port2 } = new MessageChannel();
port1.postMessage(message);
port1.close();
port2.close();
return new MessageChannel();
registerChannel(new MessageChannel());
Behavior and migration notesโ
Store either the MessageChannel object or both MessagePort handles in the
owner that will close them. For UI code, that owner is usually a component,
worker bridge, route lifecycle, or application-level channel manager.
This rule does not autofix. Introducing a variable without selecting the owner and teardown point would hide the lifecycle problem instead of solving it.
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 runtime-lifetime message ports and does not need explicit teardown. Prefer a narrow disable comment with a reason when a channel is meant to live for the whole runtime.