eslint-plugin-runtime-cleanup 1.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (217) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE +21 -0
  3. package/README.md +117 -0
  4. package/dist/_internal/ast-node.d.ts +19 -0
  5. package/dist/_internal/ast-node.d.ts.map +1 -0
  6. package/dist/_internal/ast-node.js +42 -0
  7. package/dist/_internal/ast-node.js.map +1 -0
  8. package/dist/_internal/bounded-cache.d.ts +37 -0
  9. package/dist/_internal/bounded-cache.d.ts.map +1 -0
  10. package/dist/_internal/bounded-cache.js +63 -0
  11. package/dist/_internal/bounded-cache.js.map +1 -0
  12. package/dist/_internal/cycle-safe-linked-search.d.ts +48 -0
  13. package/dist/_internal/cycle-safe-linked-search.d.ts.map +1 -0
  14. package/dist/_internal/cycle-safe-linked-search.js +70 -0
  15. package/dist/_internal/cycle-safe-linked-search.js.map +1 -0
  16. package/dist/_internal/expression-boolean-memoizer.d.ts +17 -0
  17. package/dist/_internal/expression-boolean-memoizer.d.ts.map +1 -0
  18. package/dist/_internal/expression-boolean-memoizer.js +22 -0
  19. package/dist/_internal/expression-boolean-memoizer.js.map +1 -0
  20. package/dist/_internal/filter-callback.d.ts +52 -0
  21. package/dist/_internal/filter-callback.d.ts.map +1 -0
  22. package/dist/_internal/filter-callback.js +108 -0
  23. package/dist/_internal/filter-callback.js.map +1 -0
  24. package/dist/_internal/floating-resource.d.ts +29 -0
  25. package/dist/_internal/floating-resource.d.ts.map +1 -0
  26. package/dist/_internal/floating-resource.js +114 -0
  27. package/dist/_internal/floating-resource.js.map +1 -0
  28. package/dist/_internal/member-call.d.ts +53 -0
  29. package/dist/_internal/member-call.d.ts.map +1 -0
  30. package/dist/_internal/member-call.js +61 -0
  31. package/dist/_internal/member-call.js.map +1 -0
  32. package/dist/_internal/normalize-expression-text.d.ts +21 -0
  33. package/dist/_internal/normalize-expression-text.d.ts.map +1 -0
  34. package/dist/_internal/normalize-expression-text.js +186 -0
  35. package/dist/_internal/normalize-expression-text.js.map +1 -0
  36. package/dist/_internal/nullish-comparison.d.ts +44 -0
  37. package/dist/_internal/nullish-comparison.d.ts.map +1 -0
  38. package/dist/_internal/nullish-comparison.js +162 -0
  39. package/dist/_internal/nullish-comparison.js.map +1 -0
  40. package/dist/_internal/plugin-settings.d.ts +30 -0
  41. package/dist/_internal/plugin-settings.d.ts.map +1 -0
  42. package/dist/_internal/plugin-settings.js +90 -0
  43. package/dist/_internal/plugin-settings.js.map +1 -0
  44. package/dist/_internal/report-adapter.d.ts +24 -0
  45. package/dist/_internal/report-adapter.d.ts.map +1 -0
  46. package/dist/_internal/report-adapter.js +35 -0
  47. package/dist/_internal/report-adapter.js.map +1 -0
  48. package/dist/_internal/rule-catalog.d.ts +47 -0
  49. package/dist/_internal/rule-catalog.d.ts.map +1 -0
  50. package/dist/_internal/rule-catalog.js +97 -0
  51. package/dist/_internal/rule-catalog.js.map +1 -0
  52. package/dist/_internal/rule-docs-metadata.d.ts +35 -0
  53. package/dist/_internal/rule-docs-metadata.d.ts.map +1 -0
  54. package/dist/_internal/rule-docs-metadata.js +172 -0
  55. package/dist/_internal/rule-docs-metadata.js.map +1 -0
  56. package/dist/_internal/rule-docs-url.d.ts +15 -0
  57. package/dist/_internal/rule-docs-url.d.ts.map +1 -0
  58. package/dist/_internal/rule-docs-url.js +15 -0
  59. package/dist/_internal/rule-docs-url.js.map +1 -0
  60. package/dist/_internal/rules-registry.d.ts +11 -0
  61. package/dist/_internal/rules-registry.d.ts.map +1 -0
  62. package/dist/_internal/rules-registry.js +53 -0
  63. package/dist/_internal/rules-registry.js.map +1 -0
  64. package/dist/_internal/runtime-cleanup-config-references.d.ts +38 -0
  65. package/dist/_internal/runtime-cleanup-config-references.d.ts.map +1 -0
  66. package/dist/_internal/runtime-cleanup-config-references.js +78 -0
  67. package/dist/_internal/runtime-cleanup-config-references.js.map +1 -0
  68. package/dist/_internal/safe-type-operation.d.ts +89 -0
  69. package/dist/_internal/safe-type-operation.d.ts.map +1 -0
  70. package/dist/_internal/safe-type-operation.js +147 -0
  71. package/dist/_internal/safe-type-operation.js.map +1 -0
  72. package/dist/_internal/scope-variable.d.ts +17 -0
  73. package/dist/_internal/scope-variable.d.ts.map +1 -0
  74. package/dist/_internal/scope-variable.js +30 -0
  75. package/dist/_internal/scope-variable.js.map +1 -0
  76. package/dist/_internal/type-checker.d.ts +11 -0
  77. package/dist/_internal/type-checker.d.ts.map +1 -0
  78. package/dist/_internal/type-checker.js +25 -0
  79. package/dist/_internal/type-checker.js.map +1 -0
  80. package/dist/_internal/type-predicate-autofix-safety.d.ts +16 -0
  81. package/dist/_internal/type-predicate-autofix-safety.d.ts.map +1 -0
  82. package/dist/_internal/type-predicate-autofix-safety.js +54 -0
  83. package/dist/_internal/type-predicate-autofix-safety.js.map +1 -0
  84. package/dist/_internal/type-reference-node.d.ts +23 -0
  85. package/dist/_internal/type-reference-node.d.ts.map +1 -0
  86. package/dist/_internal/type-reference-node.js +41 -0
  87. package/dist/_internal/type-reference-node.js.map +1 -0
  88. package/dist/_internal/typed-rule.d.ts +91 -0
  89. package/dist/_internal/typed-rule.d.ts.map +1 -0
  90. package/dist/_internal/typed-rule.js +121 -0
  91. package/dist/_internal/typed-rule.js.map +1 -0
  92. package/dist/_internal/value-rewrite-autofix-safety.d.ts +29 -0
  93. package/dist/_internal/value-rewrite-autofix-safety.d.ts.map +1 -0
  94. package/dist/_internal/value-rewrite-autofix-safety.js +108 -0
  95. package/dist/_internal/value-rewrite-autofix-safety.js.map +1 -0
  96. package/dist/plugin.cjs +3693 -0
  97. package/dist/plugin.cjs.map +7 -0
  98. package/dist/plugin.d.cts +75 -0
  99. package/dist/plugin.d.ts +75 -0
  100. package/dist/plugin.d.ts.map +1 -0
  101. package/dist/plugin.js +223 -0
  102. package/dist/plugin.js.map +1 -0
  103. package/dist/rules/no-floating-abort-controllers.d.ts +9 -0
  104. package/dist/rules/no-floating-abort-controllers.d.ts.map +1 -0
  105. package/dist/rules/no-floating-abort-controllers.js +144 -0
  106. package/dist/rules/no-floating-abort-controllers.js.map +1 -0
  107. package/dist/rules/no-floating-audio-contexts.d.ts +9 -0
  108. package/dist/rules/no-floating-audio-contexts.d.ts.map +1 -0
  109. package/dist/rules/no-floating-audio-contexts.js +95 -0
  110. package/dist/rules/no-floating-audio-contexts.js.map +1 -0
  111. package/dist/rules/no-floating-broadcast-channels.d.ts +9 -0
  112. package/dist/rules/no-floating-broadcast-channels.d.ts.map +1 -0
  113. package/dist/rules/no-floating-broadcast-channels.js +151 -0
  114. package/dist/rules/no-floating-broadcast-channels.js.map +1 -0
  115. package/dist/rules/no-floating-child-processes.d.ts +9 -0
  116. package/dist/rules/no-floating-child-processes.d.ts.map +1 -0
  117. package/dist/rules/no-floating-child-processes.js +259 -0
  118. package/dist/rules/no-floating-child-processes.js.map +1 -0
  119. package/dist/rules/no-floating-disposable-stacks.d.ts +9 -0
  120. package/dist/rules/no-floating-disposable-stacks.d.ts.map +1 -0
  121. package/dist/rules/no-floating-disposable-stacks.js +177 -0
  122. package/dist/rules/no-floating-disposable-stacks.js.map +1 -0
  123. package/dist/rules/no-floating-file-watchers.d.ts +9 -0
  124. package/dist/rules/no-floating-file-watchers.d.ts.map +1 -0
  125. package/dist/rules/no-floating-file-watchers.js +241 -0
  126. package/dist/rules/no-floating-file-watchers.js.map +1 -0
  127. package/dist/rules/no-floating-geolocation-watches.d.ts +9 -0
  128. package/dist/rules/no-floating-geolocation-watches.d.ts.map +1 -0
  129. package/dist/rules/no-floating-geolocation-watches.js +156 -0
  130. package/dist/rules/no-floating-geolocation-watches.js.map +1 -0
  131. package/dist/rules/no-floating-infinite-animations.d.ts +9 -0
  132. package/dist/rules/no-floating-infinite-animations.d.ts.map +1 -0
  133. package/dist/rules/no-floating-infinite-animations.js +131 -0
  134. package/dist/rules/no-floating-infinite-animations.js.map +1 -0
  135. package/dist/rules/no-floating-media-streams.d.ts +9 -0
  136. package/dist/rules/no-floating-media-streams.d.ts.map +1 -0
  137. package/dist/rules/no-floating-media-streams.js +175 -0
  138. package/dist/rules/no-floating-media-streams.js.map +1 -0
  139. package/dist/rules/no-floating-message-channels.d.ts +9 -0
  140. package/dist/rules/no-floating-message-channels.d.ts.map +1 -0
  141. package/dist/rules/no-floating-message-channels.js +150 -0
  142. package/dist/rules/no-floating-message-channels.js.map +1 -0
  143. package/dist/rules/no-floating-network-connections.d.ts +9 -0
  144. package/dist/rules/no-floating-network-connections.d.ts.map +1 -0
  145. package/dist/rules/no-floating-network-connections.js +170 -0
  146. package/dist/rules/no-floating-network-connections.js.map +1 -0
  147. package/dist/rules/no-floating-object-urls.d.ts +9 -0
  148. package/dist/rules/no-floating-object-urls.d.ts.map +1 -0
  149. package/dist/rules/no-floating-object-urls.js +83 -0
  150. package/dist/rules/no-floating-object-urls.js.map +1 -0
  151. package/dist/rules/no-floating-observers.d.ts +9 -0
  152. package/dist/rules/no-floating-observers.d.ts.map +1 -0
  153. package/dist/rules/no-floating-observers.js +160 -0
  154. package/dist/rules/no-floating-observers.js.map +1 -0
  155. package/dist/rules/no-floating-servers.d.ts +9 -0
  156. package/dist/rules/no-floating-servers.d.ts.map +1 -0
  157. package/dist/rules/no-floating-servers.js +282 -0
  158. package/dist/rules/no-floating-servers.js.map +1 -0
  159. package/dist/rules/no-floating-streams.d.ts +9 -0
  160. package/dist/rules/no-floating-streams.d.ts.map +1 -0
  161. package/dist/rules/no-floating-streams.js +222 -0
  162. package/dist/rules/no-floating-streams.js.map +1 -0
  163. package/dist/rules/no-floating-timers.d.ts +9 -0
  164. package/dist/rules/no-floating-timers.d.ts.map +1 -0
  165. package/dist/rules/no-floating-timers.js +145 -0
  166. package/dist/rules/no-floating-timers.js.map +1 -0
  167. package/dist/rules/no-floating-wake-locks.d.ts +9 -0
  168. package/dist/rules/no-floating-wake-locks.d.ts.map +1 -0
  169. package/dist/rules/no-floating-wake-locks.js +159 -0
  170. package/dist/rules/no-floating-wake-locks.js.map +1 -0
  171. package/dist/rules/no-floating-web-stream-locks.d.ts +9 -0
  172. package/dist/rules/no-floating-web-stream-locks.d.ts.map +1 -0
  173. package/dist/rules/no-floating-web-stream-locks.js +87 -0
  174. package/dist/rules/no-floating-web-stream-locks.js.map +1 -0
  175. package/dist/rules/no-floating-workers.d.ts +9 -0
  176. package/dist/rules/no-floating-workers.d.ts.map +1 -0
  177. package/dist/rules/no-floating-workers.js +185 -0
  178. package/dist/rules/no-floating-workers.js.map +1 -0
  179. package/dist/rules/no-unmanaged-event-listeners.d.ts +9 -0
  180. package/dist/rules/no-unmanaged-event-listeners.d.ts.map +1 -0
  181. package/dist/rules/no-unmanaged-event-listeners.js +210 -0
  182. package/dist/rules/no-unmanaged-event-listeners.js.map +1 -0
  183. package/docs/rules/getting-started.md +29 -0
  184. package/docs/rules/guides/adoption-checklist.md +31 -0
  185. package/docs/rules/guides/preset-selection-strategy.md +24 -0
  186. package/docs/rules/guides/rollout-and-fix-safety.md +42 -0
  187. package/docs/rules/guides/snapshot-testing.md +20 -0
  188. package/docs/rules/guides/type-aware-linting-readiness.md +20 -0
  189. package/docs/rules/no-floating-abort-controllers.md +126 -0
  190. package/docs/rules/no-floating-audio-contexts.md +104 -0
  191. package/docs/rules/no-floating-broadcast-channels.md +105 -0
  192. package/docs/rules/no-floating-child-processes.md +123 -0
  193. package/docs/rules/no-floating-disposable-stacks.md +118 -0
  194. package/docs/rules/no-floating-file-watchers.md +111 -0
  195. package/docs/rules/no-floating-geolocation-watches.md +95 -0
  196. package/docs/rules/no-floating-infinite-animations.md +110 -0
  197. package/docs/rules/no-floating-media-streams.md +113 -0
  198. package/docs/rules/no-floating-message-channels.md +114 -0
  199. package/docs/rules/no-floating-network-connections.md +116 -0
  200. package/docs/rules/no-floating-object-urls.md +102 -0
  201. package/docs/rules/no-floating-observers.md +108 -0
  202. package/docs/rules/no-floating-servers.md +127 -0
  203. package/docs/rules/no-floating-streams.md +120 -0
  204. package/docs/rules/no-floating-timers.md +120 -0
  205. package/docs/rules/no-floating-wake-locks.md +109 -0
  206. package/docs/rules/no-floating-web-stream-locks.md +105 -0
  207. package/docs/rules/no-floating-workers.md +123 -0
  208. package/docs/rules/no-unmanaged-event-listeners.md +143 -0
  209. package/docs/rules/overview.md +44 -0
  210. package/docs/rules/presets/all.md +35 -0
  211. package/docs/rules/presets/experimental.md +44 -0
  212. package/docs/rules/presets/index.md +54 -0
  213. package/docs/rules/presets/minimal.md +17 -0
  214. package/docs/rules/presets/recommended-type-checked.md +43 -0
  215. package/docs/rules/presets/recommended.md +34 -0
  216. package/docs/rules/presets/strict.md +36 -0
  217. package/package.json +323 -0
@@ -0,0 +1,42 @@
1
+ ---
2
+ title: Rollout and fix safety
3
+ description: Guidance for phased rollout, fix safety, and manual verification.
4
+ ---
5
+
6
+ # Rollout and fix safety
7
+
8
+ This page centralizes rollout guidance used across rule migrations.
9
+
10
+ ## Phased rollout model
11
+
12
+ 1. Start with `warn` severity to measure blast radius.
13
+ 2. Run `--fix` only on a scoped folder first.
14
+ 3. Review runtime-sensitive call sites manually.
15
+ 4. Promote to `error` after baseline cleanup.
16
+
17
+ ## Fix safety expectations
18
+
19
+ - **Autofix-safe patterns:** simple API shape substitutions where runtime behavior is equivalent.
20
+ - **Suggestion-only patterns:** potentially behavior-sensitive changes requiring explicit reviewer choice.
21
+ - **Manual migrations:** replacements that depend on local typing, nullability, or control flow assumptions.
22
+
23
+ ## Manual verification checklist
24
+
25
+ 1. Verify import changes are deduplicated and stable.
26
+ 2. Confirm narrowed types still match downstream usage.
27
+ 3. Validate guard/predicate behavior with tests.
28
+ 4. Confirm no accidental semantic changes in edge cases.
29
+
30
+ ## FAQ
31
+
32
+ ### Why not migrate everything in one pass?
33
+
34
+ Large one-shot migrations make regressions harder to detect and review. Smaller batches isolate risk.
35
+
36
+ ### Should we always use `--fix` in CI?
37
+
38
+ No. Prefer running `--fix` locally and committing explicit changes. CI should validate, not rewrite, code.
39
+
40
+ ### How do we handle wrapper utilities?
41
+
42
+ Either align wrappers to canonical helpers/types used by this plugin or deprecate wrappers that duplicate behavior.
@@ -0,0 +1,20 @@
1
+ ---
2
+ description: How to use Vitest snapshots safely and effectively in eslint-plugin-runtime-cleanup.
3
+ ---
4
+
5
+ # Snapshot testing
6
+
7
+ Prefer explicit assertions for rule behavior. Snapshots are useful only for
8
+ stable generated surfaces such as preset matrices or public contract summaries.
9
+
10
+ ## Good snapshot targets
11
+
12
+ - plugin preset contract summaries
13
+ - generated README rule tables
14
+ - generated docs navigation fragments
15
+
16
+ ## Avoid snapshots for rule fixes
17
+
18
+ Autofix output should be written as explicit strings in RuleTester cases. That
19
+ keeps cleanup transformations reviewable and prevents accidental acceptance of
20
+ unsafe fixer behavior.
@@ -0,0 +1,20 @@
1
+ ---
2
+ description: Checklist and rollout playbook for enabling type-aware eslint-plugin-runtime-cleanup rules safely.
3
+ ---
4
+
5
+ # Type-aware linting readiness
6
+
7
+ Some future cleanup rules may need TypeScript parser services to distinguish
8
+ resource-like values from unrelated identifiers. Use type-aware rules only when
9
+ the project can provide reliable type information.
10
+
11
+ ## Requirements
12
+
13
+ - `@typescript-eslint/parser` configured for the files being linted.
14
+ - `parserOptions.projectService` or an equivalent typed parser setup.
15
+ - CI memory and runtime budgets sized for type-aware linting.
16
+
17
+ ## Rollout
18
+
19
+ Start with a small package or workspace. Compare runtime and findings against
20
+ the non-type-aware preset before enabling typed rules broadly.
@@ -0,0 +1,126 @@
1
+ # no-floating-abort-controllers
2
+
3
+ Require `AbortController` handles to be retained so work can be aborted during
4
+ cleanup.
5
+
6
+ > **Rule catalog ID:** R006
7
+
8
+ ## Targeted pattern scope
9
+
10
+ This rule targets `AbortController` construction where the controller handle is
11
+ lost before the owning lifecycle can call `abort()`:
12
+
13
+ - `new AbortController()`
14
+ - `new window.AbortController()`
15
+ - `new self.AbortController()`
16
+ - `new globalThis.AbortController()`
17
+ - `new AbortController().signal`
18
+
19
+ The rule reports controllers that are immediately discarded and inline
20
+ `.signal` handoffs that keep only the signal. A signal can observe cancellation,
21
+ but it cannot initiate cancellation; the controller is the cleanup handle.
22
+
23
+ ## What this rule reports
24
+
25
+ The rule reports:
26
+
27
+ - standalone controller construction such as `new AbortController();`
28
+ - voided controller construction such as `void new AbortController();`
29
+ - inline signal-only ownership such as
30
+ `fetch(url, { signal: new AbortController().signal })`
31
+ - assignments that keep only `new AbortController().signal`
32
+
33
+ It intentionally does not require a same-function `abort()` call. Ownership can
34
+ be transferred to a component instance, request manager, disposable collection,
35
+ returned object, or another lifecycle owner.
36
+
37
+ ## Why this rule exists
38
+
39
+ `AbortController` is the cancellation handle for an `AbortSignal`. APIs such as
40
+ `fetch()` and `addEventListener()` accept a signal, and calling
41
+ `AbortController.abort()` later cancels or removes the associated work. If code
42
+ passes only `new AbortController().signal`, no reachable owner remains that can
43
+ abort the work during cleanup.
44
+
45
+ ## Incorrect
46
+
47
+ ```ts
48
+ new AbortController();
49
+ ```
50
+
51
+ ```ts
52
+ void new AbortController();
53
+ ```
54
+
55
+ ```ts
56
+ fetch("/api", {
57
+ signal: new AbortController().signal,
58
+ });
59
+ ```
60
+
61
+ ```ts
62
+ const signal = new AbortController().signal;
63
+ ```
64
+
65
+ ## Correct
66
+
67
+ ```ts
68
+ const controller = new AbortController();
69
+
70
+ fetch("/api", {
71
+ signal: controller.signal,
72
+ });
73
+
74
+ controller.abort();
75
+ ```
76
+
77
+ ```ts
78
+ return new AbortController();
79
+ ```
80
+
81
+ ```ts
82
+ registerAbortController(new AbortController());
83
+ ```
84
+
85
+ ```ts
86
+ using controller = createAbortControllerDisposable();
87
+ ```
88
+
89
+ ## Behavior and migration notes
90
+
91
+ Store the controller in the same owner that is responsible for cancelling the
92
+ work. In UI code, that is usually the component setup scope or a cleanup
93
+ callback. In service code, it may be a request context, operation manager, or
94
+ explicit disposable.
95
+
96
+ When a controller is intentionally owned elsewhere, pass the controller itself
97
+ to that owner instead of passing only the signal. A signal-only API boundary is
98
+ appropriate for consumers that should observe cancellation but should not be
99
+ able to cancel the operation.
100
+
101
+ This rule does not autofix. Introducing a new controller variable without
102
+ placing the matching `abort()` call in the correct lifecycle path would make the
103
+ code look managed without actually solving cleanup.
104
+
105
+ ## ESLint flat config example
106
+
107
+ ```js
108
+ import runtimeCleanup from "eslint-plugin-runtime-cleanup";
109
+
110
+ export default [
111
+ runtimeCleanup.configs.recommended,
112
+ ];
113
+ ```
114
+
115
+ ## When not to use it
116
+
117
+ Do not enable this rule for code that intentionally creates page-lifetime or
118
+ process-lifetime cancellation signals and never expects to abort them. Prefer a
119
+ narrow disable comment with a reason when the lifecycle is intentionally owned
120
+ by the whole runtime.
121
+
122
+ ## Further reading
123
+
124
+ - [MDN: `AbortController`](https://developer.mozilla.org/docs/Web/API/AbortController)
125
+ - [MDN: `AbortSignal`](https://developer.mozilla.org/docs/Web/API/AbortSignal)
126
+ - [MDN: `addEventListener()` signal option](https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener)
@@ -0,0 +1,104 @@
1
+ # no-floating-audio-contexts
2
+
3
+ Require `AudioContext` instances to be retained so they can be closed.
4
+
5
+ > **Rule catalog ID:** R018
6
+
7
+ ## Targeted pattern scope
8
+
9
+ This rule targets browser audio context construction:
10
+
11
+ - `new AudioContext(...)`
12
+ - `new webkitAudioContext(...)`
13
+ - `new window.AudioContext(...)`
14
+ - `new globalThis.AudioContext(...)`
15
+
16
+ The rule reports contexts that are immediately discarded, explicitly voided, or
17
+ used through an immediate non-cleanup method call such as
18
+ `new AudioContext().resume()`. It ignores locally shadowed direct constructor
19
+ bindings so project-local classes with the same name are not treated as browser
20
+ audio contexts.
21
+
22
+ ## What this rule reports
23
+
24
+ The rule reports:
25
+
26
+ - standalone `new AudioContext()` expressions
27
+ - `void new AudioContext()`
28
+ - immediate non-cleanup use of an unowned context
29
+
30
+ Immediate `close()` calls are allowed because they do not leave an owned context
31
+ behind.
32
+
33
+ ## Why this rule exists
34
+
35
+ `AudioContext` can hold audio hardware, decoding, graph, and scheduling
36
+ resources. `AudioContext.close()` releases system audio resources used by the
37
+ context. If the context is discarded, cleanup code cannot close it.
38
+
39
+ ## Incorrect
40
+
41
+ ```ts
42
+ new AudioContext();
43
+ ```
44
+
45
+ ```ts
46
+ void new window.AudioContext();
47
+ ```
48
+
49
+ ```ts
50
+ new AudioContext().resume();
51
+ ```
52
+
53
+ ## Correct
54
+
55
+ ```ts
56
+ const context = new AudioContext();
57
+
58
+ try {
59
+ await context.resume();
60
+ } finally {
61
+ await context.close();
62
+ }
63
+ ```
64
+
65
+ ```ts
66
+ function createAudioContext() {
67
+ return new AudioContext();
68
+ }
69
+ ```
70
+
71
+ ```ts
72
+ audioContextRegistry.add(new AudioContext());
73
+ ```
74
+
75
+ ## Behavior and migration notes
76
+
77
+ Store the context in the audio session, component, game engine, visualizer, or
78
+ test fixture that owns the audio graph. That owner should call `close()` during
79
+ cleanup.
80
+
81
+ This rule does not autofix because adding a variable without a matching
82
+ `close()` path would not fix the resource lifecycle.
83
+
84
+ ## ESLint flat config example
85
+
86
+ ```js
87
+ import runtimeCleanup from "eslint-plugin-runtime-cleanup";
88
+
89
+ export default [
90
+ runtimeCleanup.configs.recommended,
91
+ ];
92
+ ```
93
+
94
+ ## When not to use it
95
+
96
+ Do not enable this rule for tiny one-page demos where the browser page lifetime
97
+ is intentionally the cleanup boundary. Use narrow inline disables for those
98
+ examples.
99
+
100
+ ## Further reading
101
+
102
+ - [MDN: `AudioContext`](https://developer.mozilla.org/docs/Web/API/AudioContext)
103
+ - [MDN: `AudioContext.close()`](https://developer.mozilla.org/docs/Web/API/AudioContext/close)
104
+
@@ -0,0 +1,105 @@
1
+ # no-floating-broadcast-channels
2
+
3
+ Require `BroadcastChannel` handles to be retained so they can be closed.
4
+
5
+ > **Rule catalog ID:** R011
6
+
7
+ ## Targeted pattern scope
8
+
9
+ This rule targets browser `BroadcastChannel` constructors:
10
+
11
+ - `BroadcastChannel`
12
+ - `window.BroadcastChannel`
13
+ - `self.BroadcastChannel`
14
+ - `globalThis.BroadcastChannel`
15
+
16
+ The rule reports channel instances that are immediately discarded or chained
17
+ directly into a method call other than `.close()`. In both cases there is no
18
+ remaining channel handle available for teardown.
19
+
20
+ ## What this rule reports
21
+
22
+ The rule reports:
23
+
24
+ - standalone channel construction such as `new BroadcastChannel("updates");`
25
+ - voided channel construction such as `void new BroadcastChannel("updates");`
26
+ - immediate message sends such as
27
+ `new BroadcastChannel("updates").postMessage(message);`
28
+ - immediate listener registration on a discarded channel
29
+
30
+ It intentionally does not require same-function `close()` calls. Ownership can
31
+ be transferred to a component instance, channel manager, returned value, or
32
+ longer-lived runtime owner.
33
+
34
+ ## Why this rule exists
35
+
36
+ `BroadcastChannel.close()` terminates the connection to the underlying channel
37
+ and lets the browser know the channel is no longer needed. If the channel handle
38
+ is discarded, code cannot reliably call `.close()` or remove listeners during
39
+ cleanup.
40
+
41
+ ## Incorrect
42
+
43
+ ```ts
44
+ new BroadcastChannel("updates");
45
+ ```
46
+
47
+ ```ts
48
+ void new BroadcastChannel("updates");
49
+ ```
50
+
51
+ ```ts
52
+ new BroadcastChannel("updates").postMessage(message);
53
+ ```
54
+
55
+ ```ts
56
+ new BroadcastChannel("updates").addEventListener("message", onMessage);
57
+ ```
58
+
59
+ ## Correct
60
+
61
+ ```ts
62
+ const channel = new BroadcastChannel("updates");
63
+
64
+ channel.addEventListener("message", onMessage);
65
+ channel.close();
66
+ ```
67
+
68
+ ```ts
69
+ return new BroadcastChannel(name);
70
+ ```
71
+
72
+ ```ts
73
+ registerChannel(new BroadcastChannel(name));
74
+ ```
75
+
76
+ ## Behavior and migration notes
77
+
78
+ Store the channel in the owner that will close it. For UI code, that is
79
+ usually the component, route, or application shell lifecycle. For shared
80
+ libraries, returning the channel or passing it to a channel manager keeps
81
+ ownership explicit.
82
+
83
+ This rule does not autofix. Choosing the owner and close point is a semantic
84
+ decision.
85
+
86
+ ## ESLint flat config example
87
+
88
+ ```js
89
+ import runtimeCleanup from "eslint-plugin-runtime-cleanup";
90
+
91
+ export default [
92
+ runtimeCleanup.configs.recommended,
93
+ ];
94
+ ```
95
+
96
+ ## When not to use it
97
+
98
+ Do not enable this rule for code that intentionally creates application-lifetime
99
+ channels and does not need explicit teardown. Prefer a narrow disable comment
100
+ with a reason when a channel is meant to live for the whole runtime.
101
+
102
+ ## Further reading
103
+
104
+ - [MDN: `BroadcastChannel`](https://developer.mozilla.org/docs/Web/API/BroadcastChannel)
105
+ - [MDN: `BroadcastChannel.close()`](https://developer.mozilla.org/docs/Web/API/BroadcastChannel/close)
@@ -0,0 +1,123 @@
1
+ # no-floating-child-processes
2
+
3
+ Require child process handles to be retained so they can be killed during
4
+ cleanup.
5
+
6
+ > **Rule catalog ID:** R005
7
+
8
+ ## Targeted pattern scope
9
+
10
+ This rule targets asynchronous Node.js child process factories whose returned
11
+ handle is needed for cleanup:
12
+
13
+ - `spawn`
14
+ - `exec`
15
+ - `execFile`
16
+ - `fork`
17
+
18
+ The rule recognizes those factories when they come from `node:child_process` or
19
+ `child_process` through named imports, default or namespace imports, CommonJS
20
+ `require(...)` module bindings, destructured `require(...)`, or direct
21
+ `require("node:child_process").spawn(...)` calls.
22
+
23
+ The rule reports child process handles that are immediately discarded or chained
24
+ directly into a method call other than `.kill()` or `.disconnect()`.
25
+
26
+ ## What this rule reports
27
+
28
+ The rule reports:
29
+
30
+ - standalone child process calls such as `spawn("node", ["worker.js"]);`
31
+ - voided child process calls such as `void childProcess.exec("node worker.js");`
32
+ - immediate method chains such as
33
+ `childProcess.fork("./worker.js").on("exit", handleExit);`
34
+ - inline require calls such as
35
+ `require("node:child_process").spawn("node", ["worker.js"]);`
36
+
37
+ It intentionally does not require same-function `kill()` calls. Ownership can be
38
+ transferred to a supervisor, returned handle, disposable collection, or
39
+ process-lifetime owner.
40
+
41
+ ## Why this rule exists
42
+
43
+ Asynchronous child process factories return a `ChildProcess` handle. That handle
44
+ is the API surface for terminating the process, disconnecting IPC, inspecting
45
+ exit state, and wiring teardown. Discarding it leaves no explicit owner that can
46
+ clean up the subprocess when the surrounding runtime scope ends.
47
+
48
+ ## Incorrect
49
+
50
+ ```ts
51
+ import { spawn } from "node:child_process";
52
+
53
+ spawn("node", ["worker.js"]);
54
+ ```
55
+
56
+ ```ts
57
+ import childProcess from "node:child_process";
58
+
59
+ void childProcess.exec("node worker.js");
60
+ ```
61
+
62
+ ```ts
63
+ import * as childProcess from "child_process";
64
+
65
+ childProcess.fork("./worker.js").on("exit", handleExit);
66
+ ```
67
+
68
+ ```ts
69
+ require("node:child_process").spawn("node", ["worker.js"]);
70
+ ```
71
+
72
+ ## Correct
73
+
74
+ ```ts
75
+ import { spawn } from "node:child_process";
76
+
77
+ const child = spawn("node", ["worker.js"]);
78
+
79
+ child.kill();
80
+ ```
81
+
82
+ ```ts
83
+ import { fork } from "node:child_process";
84
+
85
+ return fork("./worker.js");
86
+ ```
87
+
88
+ ```ts
89
+ import childProcess from "node:child_process";
90
+
91
+ registerChildProcess(childProcess.spawn("node", ["worker.js"]));
92
+ ```
93
+
94
+ ## Behavior and migration notes
95
+
96
+ Store the `ChildProcess` handle in the owner that will terminate or supervise
97
+ the subprocess. For test helpers and task runners, that is usually a local
98
+ cleanup scope. For long-running services, it can be a process supervisor or
99
+ resource registry.
100
+
101
+ This rule does not autofix. Inserting a variable without a real shutdown path
102
+ would only make ownership look explicit while preserving the leak.
103
+
104
+ ## ESLint flat config example
105
+
106
+ ```js
107
+ import runtimeCleanup from "eslint-plugin-runtime-cleanup";
108
+
109
+ export default [
110
+ runtimeCleanup.configs.recommended,
111
+ ];
112
+ ```
113
+
114
+ ## When not to use it
115
+
116
+ Do not enable this rule for scripts that intentionally start detached,
117
+ process-lifetime children and do not own their cleanup. Prefer a narrow disable
118
+ comment with a reason for those launch points.
119
+
120
+ ## Further reading
121
+
122
+ - [Node.js: asynchronous process creation](https://nodejs.org/api/child_process.html#asynchronous-process-creation)
123
+ - [Node.js: `subprocess.kill()`](https://nodejs.org/api/child_process.html#subprocesskillsignal)
@@ -0,0 +1,118 @@
1
+ # no-floating-disposable-stacks
2
+
3
+ Require `DisposableStack` handles to be retained so registered disposers run.
4
+
5
+ > **Rule catalog ID:** R008
6
+
7
+ ## Targeted pattern scope
8
+
9
+ This rule targets the standard explicit resource-management stack
10
+ constructors:
11
+
12
+ - `DisposableStack`
13
+ - `AsyncDisposableStack`
14
+ - `globalThis.DisposableStack`, `window.DisposableStack`, and
15
+ `self.DisposableStack`
16
+ - `globalThis.AsyncDisposableStack`, `window.AsyncDisposableStack`, and
17
+ `self.AsyncDisposableStack`
18
+
19
+ The rule reports disposable stack instances that are immediately discarded or
20
+ chained directly into a method call other than `.dispose()` or
21
+ `.disposeAsync()`. In both cases there is no remaining stack handle available to
22
+ dispose registered resources.
23
+
24
+ ## What this rule reports
25
+
26
+ The rule reports:
27
+
28
+ - standalone stack construction such as `new DisposableStack();`
29
+ - voided stack construction such as `void new AsyncDisposableStack();`
30
+ - immediate registration calls such as `new DisposableStack().defer(cleanup);`
31
+ - immediate async registration calls such as
32
+ `new AsyncDisposableStack().use(resource);`
33
+
34
+ It intentionally does not require same-function disposal for retained stacks.
35
+ Ownership can be handled by a `using` or `await using` declaration, a returned
36
+ stack, or a dedicated lifecycle manager.
37
+
38
+ ## Why this rule exists
39
+
40
+ `DisposableStack` and `AsyncDisposableStack` are ownership containers. They only
41
+ provide cleanup if the stack itself is retained and later disposed. Discarding
42
+ the stack after registering work silently drops the only object that can run the
43
+ registered disposers.
44
+
45
+ ## Incorrect
46
+
47
+ ```ts
48
+ new DisposableStack();
49
+ ```
50
+
51
+ ```ts
52
+ void new AsyncDisposableStack();
53
+ ```
54
+
55
+ ```ts
56
+ new DisposableStack().defer(cleanup);
57
+ ```
58
+
59
+ ```ts
60
+ new AsyncDisposableStack().use(resource);
61
+ ```
62
+
63
+ ## Correct
64
+
65
+ ```ts
66
+ using stack = new DisposableStack();
67
+
68
+ stack.defer(cleanup);
69
+ ```
70
+
71
+ ```ts
72
+ await using stack = new AsyncDisposableStack();
73
+
74
+ stack.use(resource);
75
+ ```
76
+
77
+ ```ts
78
+ return new DisposableStack();
79
+ ```
80
+
81
+ ```ts
82
+ registerDisposableStack(new AsyncDisposableStack());
83
+ ```
84
+
85
+ ## Behavior and migration notes
86
+
87
+ Keep the stack in the same lifecycle owner that will dispose it. Prefer
88
+ `using` for synchronous stacks and `await using` for asynchronous stacks when
89
+ the current runtime and compiler settings support explicit resource
90
+ management. If a framework or library owns disposal, pass the stack directly to
91
+ that owner.
92
+
93
+ This rule does not autofix. Choosing between `using`, `await using`, returning
94
+ the stack, or wiring a lifecycle manager changes ownership semantics and must
95
+ be decided by the developer.
96
+
97
+ ## ESLint flat config example
98
+
99
+ ```js
100
+ import runtimeCleanup from "eslint-plugin-runtime-cleanup";
101
+
102
+ export default [
103
+ runtimeCleanup.configs.recommended,
104
+ ];
105
+ ```
106
+
107
+ ## When not to use it
108
+
109
+ Do not enable this rule for generated code or compatibility layers that
110
+ intentionally construct disposable stack shims for feature detection. Prefer a
111
+ narrow disable comment with a reason for those cases.
112
+
113
+ ## Further reading
114
+
115
+ - [MDN: JavaScript resource management](https://developer.mozilla.org/docs/Web/JavaScript/Guide/Resource_management)
116
+ - [MDN: `DisposableStack`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/DisposableStack)
117
+ - [MDN: `AsyncDisposableStack`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/AsyncDisposableStack)
118
+ - [TypeScript 5.2: Explicit Resource Management](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html)