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,111 @@
1
+ # no-floating-file-watchers
2
+
3
+ Require Node.js file watcher handles to be retained so they can be closed.
4
+
5
+ > **Rule catalog ID:** R010
6
+
7
+ ## Targeted pattern scope
8
+
9
+ This rule targets Node.js `fs.watch()` calls from `fs` and `node:fs`:
10
+
11
+ - `watch`
12
+ - `fs.watch`
13
+ - `require("fs").watch`
14
+ - `require("node:fs").watch`
15
+
16
+ The rule reports watcher handles that are immediately discarded or chained
17
+ directly into a method call other than `.close()`. It does not target
18
+ `node:fs/promises` async iterator watchers because those use different
19
+ ownership and cancellation patterns.
20
+
21
+ ## What this rule reports
22
+
23
+ The rule reports:
24
+
25
+ - standalone watcher creation such as `watch("src", onChange);`
26
+ - voided watcher creation such as `void fs.watch("src", onChange);`
27
+ - discarded CommonJS namespace or destructured `watch` calls
28
+ - immediate event registration on an unowned watcher such as
29
+ `fs.watch("src").on("error", onError);`
30
+
31
+ It intentionally does not require same-function `close()` calls. Watcher
32
+ ownership can be transferred by returning the watcher or passing it to a
33
+ dedicated lifecycle manager.
34
+
35
+ ## Why this rule exists
36
+
37
+ `fs.watch()` returns an `FSWatcher`. Active watchers can keep the Node.js event
38
+ loop alive and continue receiving file-system events until they are closed. If
39
+ the watcher handle is discarded, later cleanup cannot reliably call `.close()`
40
+ or coordinate shutdown behavior.
41
+
42
+ ## Incorrect
43
+
44
+ ```ts
45
+ import { watch } from "node:fs";
46
+
47
+ watch("src", onChange);
48
+ ```
49
+
50
+ ```ts
51
+ import * as fs from "node:fs";
52
+
53
+ void fs.watch("src", onChange);
54
+ ```
55
+
56
+ ```ts
57
+ import * as fs from "node:fs";
58
+
59
+ fs.watch("src", onChange).on("error", onError);
60
+ ```
61
+
62
+ ## Correct
63
+
64
+ ```ts
65
+ import { watch } from "node:fs";
66
+
67
+ const watcher = watch("src", onChange);
68
+
69
+ watcher.close();
70
+ ```
71
+
72
+ ```ts
73
+ import * as fs from "node:fs";
74
+
75
+ return fs.watch("src", onChange);
76
+ ```
77
+
78
+ ```ts
79
+ registerWatcher(fs.watch("src", onChange));
80
+ ```
81
+
82
+ ## Behavior and migration notes
83
+
84
+ Keep the watcher handle in the owner that will close it during teardown. For
85
+ development tools, that is usually the process-level watcher manager. For
86
+ library code, returning the watcher or passing it to a lifecycle manager makes
87
+ the ownership contract explicit.
88
+
89
+ This rule does not autofix. Introducing a variable without a matching close
90
+ path would hide the lifecycle problem instead of fixing it.
91
+
92
+ ## ESLint flat config example
93
+
94
+ ```js
95
+ import runtimeCleanup from "eslint-plugin-runtime-cleanup";
96
+
97
+ export default [
98
+ runtimeCleanup.configs.recommended,
99
+ ];
100
+ ```
101
+
102
+ ## When not to use it
103
+
104
+ Do not enable this rule for scripts where a watcher intentionally lives until
105
+ process exit and no explicit shutdown path is needed. Prefer a narrow disable
106
+ comment with a reason for those cases.
107
+
108
+ ## Further reading
109
+
110
+ - [Node.js: `fs.watch()`](https://nodejs.org/api/fs.html#fswatchfilename-options-listener)
111
+ - [Node.js: `FSWatcher.close()`](https://nodejs.org/api/fs.html#watcherclose)
@@ -0,0 +1,95 @@
1
+ # no-floating-geolocation-watches
2
+
3
+ Require geolocation watch IDs to be retained so they can be cleared.
4
+
5
+ > **Rule catalog ID:** R014
6
+
7
+ ## Targeted pattern scope
8
+
9
+ This rule targets browser geolocation watcher registration:
10
+
11
+ - `navigator.geolocation.watchPosition(...)`
12
+ - `window.navigator.geolocation.watchPosition(...)`
13
+ - `globalThis.navigator.geolocation.watchPosition(...)`
14
+
15
+ The rule reports calls whose returned watch ID is immediately discarded. The
16
+ watch ID is the handle needed to unregister the watcher with
17
+ `navigator.geolocation.clearWatch(...)`.
18
+
19
+ ## What this rule reports
20
+
21
+ The rule reports:
22
+
23
+ - standalone watcher registration such as
24
+ `navigator.geolocation.watchPosition(onPosition);`
25
+ - voided watcher registration such as
26
+ `void navigator.geolocation.watchPosition(onPosition);`
27
+ - TypeScript-wrapped expressions that still discard the returned watch ID
28
+
29
+ It intentionally does not require same-function `clearWatch()` calls. Ownership
30
+ can be transferred to a component instance, route lifecycle, returned value, or
31
+ watch manager.
32
+
33
+ ## Why this rule exists
34
+
35
+ `watchPosition()` installs repeated location monitoring callbacks. The browser
36
+ returns a numeric ID for that watch, and `clearWatch(id)` uses that ID to remove
37
+ the registered monitoring handlers. If the ID is discarded, the code that owns
38
+ the lifecycle cannot reliably unregister the watcher.
39
+
40
+ ## Incorrect
41
+
42
+ ```ts
43
+ navigator.geolocation.watchPosition(onPosition);
44
+ ```
45
+
46
+ ```ts
47
+ void navigator.geolocation.watchPosition(onPosition, onError);
48
+ ```
49
+
50
+ ## Correct
51
+
52
+ ```ts
53
+ const watchId = navigator.geolocation.watchPosition(onPosition);
54
+
55
+ navigator.geolocation.clearWatch(watchId);
56
+ ```
57
+
58
+ ```ts
59
+ return navigator.geolocation.watchPosition(onPosition);
60
+ ```
61
+
62
+ ```ts
63
+ registerWatch(navigator.geolocation.watchPosition(onPosition));
64
+ ```
65
+
66
+ ## Behavior and migration notes
67
+
68
+ Store the returned watch ID in the owner that will call `clearWatch()`. In UI
69
+ code, that is usually the component, route, or view lifecycle. In shared
70
+ libraries, returning the ID or passing it to a watcher manager keeps ownership
71
+ explicit.
72
+
73
+ This rule does not autofix. Choosing the owner and cleanup point is a semantic
74
+ decision.
75
+
76
+ ## ESLint flat config example
77
+
78
+ ```js
79
+ import runtimeCleanup from "eslint-plugin-runtime-cleanup";
80
+
81
+ export default [
82
+ runtimeCleanup.configs.recommended,
83
+ ];
84
+ ```
85
+
86
+ ## When not to use it
87
+
88
+ Do not enable this rule for code that intentionally creates page-lifetime
89
+ geolocation watches and does not need explicit teardown. Prefer a narrow disable
90
+ comment with a reason in those files.
91
+
92
+ ## Further reading
93
+
94
+ - [MDN: `Geolocation.watchPosition()`](https://developer.mozilla.org/docs/Web/API/Geolocation/watchPosition)
95
+ - [MDN: `Geolocation.clearWatch()`](https://developer.mozilla.org/docs/Web/API/Geolocation/clearWatch)
@@ -0,0 +1,110 @@
1
+ # no-floating-infinite-animations
2
+
3
+ Require infinite Web Animations to be retained so they can be canceled.
4
+
5
+ > **Rule catalog ID:** R020
6
+
7
+ ## Targeted pattern scope
8
+
9
+ This type-aware rule targets `Element#animate(...)` calls whose timing options
10
+ explicitly use infinite iterations:
11
+
12
+ - `{ iterations: Infinity }`
13
+ - `{ iterations: +Infinity }`
14
+ - `{ iterations: Number.POSITIVE_INFINITY }`
15
+ - `{ iterations: globalThis.Number.POSITIVE_INFINITY }`
16
+
17
+ The rule uses TypeScript parser services to confirm that the receiver is a DOM
18
+ `Element`. That keeps project-local `animate(...)` methods out of scope.
19
+
20
+ ## What this rule reports
21
+
22
+ The rule reports infinite animations when the returned `Animation` is discarded
23
+ or immediately used through a non-cleanup member:
24
+
25
+ - standalone infinite `element.animate(...)` calls
26
+ - immediate chains such as `element.animate(...).play()`
27
+
28
+ Finite one-shot animations are intentionally allowed. Fire-and-forget finite
29
+ animations are common and do not have the same long-running cleanup risk.
30
+ Immediate `cancel()` and `finish()` calls are allowed.
31
+
32
+ ## Why this rule exists
33
+
34
+ `Element#animate()` returns an `Animation` object. Infinite animations continue
35
+ until canceled, finished, the effect is removed, or the document lifecycle ends.
36
+ If the returned `Animation` is discarded, component or route cleanup code cannot
37
+ reliably stop it.
38
+
39
+ ## Incorrect
40
+
41
+ ```ts
42
+ element.animate(keyframes, {
43
+ duration: 1000,
44
+ iterations: Infinity,
45
+ });
46
+ ```
47
+
48
+ ```ts
49
+ element.animate(keyframes, {
50
+ iterations: Number.POSITIVE_INFINITY,
51
+ }).play();
52
+ ```
53
+
54
+ ## Correct
55
+
56
+ ```ts
57
+ const animation = element.animate(keyframes, {
58
+ duration: 1000,
59
+ iterations: Infinity,
60
+ });
61
+
62
+ cleanupCallbacks.add(() => animation.cancel());
63
+ ```
64
+
65
+ ```ts
66
+ function startPulse(element: Element) {
67
+ return element.animate(keyframes, {
68
+ duration: 1000,
69
+ iterations: Infinity,
70
+ });
71
+ }
72
+ ```
73
+
74
+ ```ts
75
+ element.animate(keyframes, {
76
+ iterations: 1,
77
+ });
78
+ ```
79
+
80
+ ## Behavior and migration notes
81
+
82
+ This rule is deliberately narrower than a general `Element#animate()` rule. It
83
+ only targets explicitly infinite animations because one-shot animations are often
84
+ safe to let complete naturally.
85
+
86
+ This rule does not autofix. A safe fix needs to choose the correct lifecycle
87
+ owner and cancellation point.
88
+
89
+ ## ESLint flat config example
90
+
91
+ ```js
92
+ import runtimeCleanup from "eslint-plugin-runtime-cleanup";
93
+
94
+ export default [
95
+ runtimeCleanup.configs["recommended-type-checked"],
96
+ ];
97
+ ```
98
+
99
+ ## When not to use it
100
+
101
+ Do not enable this rule without TypeScript parser services. For intentionally
102
+ page-lifetime infinite decorative animations, use a narrow disable comment near
103
+ the animation start.
104
+
105
+ ## Further reading
106
+
107
+ - [MDN: `Element.animate()`](https://developer.mozilla.org/docs/Web/API/Element/animate)
108
+ - [MDN: `Animation.cancel()`](https://developer.mozilla.org/docs/Web/API/Animation/cancel)
109
+ - [MDN: `Animation.finish()`](https://developer.mozilla.org/docs/Web/API/Animation/finish)
110
+
@@ -0,0 +1,113 @@
1
+ # no-floating-media-streams
2
+
3
+ Require captured `MediaStream` handles to be retained so their tracks can be
4
+ stopped.
5
+
6
+ > **Rule catalog ID:** R015
7
+
8
+ ## Targeted pattern scope
9
+
10
+ This rule targets browser media capture APIs:
11
+
12
+ - `navigator.mediaDevices.getUserMedia(...)`
13
+ - `navigator.mediaDevices.getDisplayMedia(...)`
14
+ - `window.navigator.mediaDevices.getUserMedia(...)`
15
+ - `globalThis.navigator.mediaDevices.getDisplayMedia(...)`
16
+
17
+ The rule reports capture requests whose resulting `MediaStream` is immediately
18
+ discarded, including `await` expressions that do not store, return, or pass the
19
+ stream to another owner.
20
+
21
+ ## What this rule reports
22
+
23
+ The rule reports:
24
+
25
+ - standalone media capture requests
26
+ - voided media capture requests
27
+ - `await navigator.mediaDevices.getUserMedia(...)` used as a standalone
28
+ expression
29
+ - `await navigator.mediaDevices.getDisplayMedia(...)` used as a standalone
30
+ expression
31
+
32
+ It intentionally allows promise chains and lifecycle-manager calls that receive
33
+ the stream. The rule focuses on obviously unowned stream handles, not on proving
34
+ that every possible owner eventually stops every track.
35
+
36
+ ## Why this rule exists
37
+
38
+ `getUserMedia()` and `getDisplayMedia()` return `MediaStream` objects backed by
39
+ media tracks. Tracks can keep cameras, microphones, screen capture, indicators,
40
+ or permission-sensitive resources active. `MediaStreamTrack.stop()` tells the
41
+ browser that a track's source is no longer needed. If the stream handle is
42
+ discarded, cleanup code cannot reliably stop its tracks.
43
+
44
+ ## Incorrect
45
+
46
+ ```ts
47
+ navigator.mediaDevices.getUserMedia({ video: true });
48
+ ```
49
+
50
+ ```ts
51
+ void navigator.mediaDevices.getDisplayMedia({ video: true });
52
+ ```
53
+
54
+ ```ts
55
+ async function openCamera() {
56
+ await navigator.mediaDevices.getUserMedia({ video: true });
57
+ }
58
+ ```
59
+
60
+ ## Correct
61
+
62
+ ```ts
63
+ async function openCamera() {
64
+ const stream = await navigator.mediaDevices.getUserMedia({ video: true });
65
+
66
+ stream.getTracks().forEach((track) => track.stop());
67
+ }
68
+ ```
69
+
70
+ ```ts
71
+ async function openScreen() {
72
+ return navigator.mediaDevices.getDisplayMedia({ video: true });
73
+ }
74
+ ```
75
+
76
+ ```ts
77
+ async function openCamera() {
78
+ registerStream(
79
+ await navigator.mediaDevices.getUserMedia({ audio: true }),
80
+ );
81
+ }
82
+ ```
83
+
84
+ ## Behavior and migration notes
85
+
86
+ Store the stream in the lifecycle owner that will stop its tracks. For UI code,
87
+ that is usually a component cleanup hook, route cleanup hook, recording
88
+ controller, or media session manager.
89
+
90
+ This rule does not autofix. Introducing a local variable without a matching
91
+ track-stop lifecycle would hide the cleanup bug instead of solving it.
92
+
93
+ ## ESLint flat config example
94
+
95
+ ```js
96
+ import runtimeCleanup from "eslint-plugin-runtime-cleanup";
97
+
98
+ export default [
99
+ runtimeCleanup.configs.recommended,
100
+ ];
101
+ ```
102
+
103
+ ## When not to use it
104
+
105
+ Do not enable this rule for short demo snippets where the browser page lifetime
106
+ is the intended cleanup boundary. Prefer narrow disable comments for those
107
+ snippets rather than weakening the rule globally.
108
+
109
+ ## Further reading
110
+
111
+ - [MDN: `MediaDevices.getUserMedia()`](https://developer.mozilla.org/docs/Web/API/MediaDevices/getUserMedia)
112
+ - [MDN: `MediaDevices.getDisplayMedia()`](https://developer.mozilla.org/docs/Web/API/MediaDevices/getDisplayMedia)
113
+ - [MDN: `MediaStreamTrack.stop()`](https://developer.mozilla.org/docs/Web/API/MediaStreamTrack/stop)
@@ -0,0 +1,114 @@
1
+ # no-floating-message-channels
2
+
3
+ Require `MessageChannel` ports to be retained so they can be closed.
4
+
5
+ > **Rule catalog ID:** R012
6
+
7
+ ## Targeted pattern scope
8
+
9
+ This rule targets browser `MessageChannel` constructors:
10
+
11
+ - `MessageChannel`
12
+ - `window.MessageChannel`
13
+ - `self.MessageChannel`
14
+ - `globalThis.MessageChannel`
15
+
16
+ The rule reports channel instances that are immediately discarded or whose
17
+ `port1`/`port2` properties are accessed directly from the temporary channel
18
+ object. Keeping only one inline port loses the peer port and makes full
19
+ teardown ambiguous.
20
+
21
+ ## What this rule reports
22
+
23
+ The rule reports:
24
+
25
+ - standalone channel construction such as `new MessageChannel();`
26
+ - voided channel construction such as `void new MessageChannel();`
27
+ - immediate port sends such as `new MessageChannel().port1.postMessage(message);`
28
+ - retaining a single inline port such as `const port = new MessageChannel().port1;`
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 both
32
+ destructured `MessagePort` handles.
33
+
34
+ ## Why this rule exists
35
+
36
+ `MessageChannel` creates two linked `MessagePort` handles. `MessagePort.close()`
37
+ disconnects a port so it is no longer active. If the channel object is discarded
38
+ or only one port is retained from an inline expression, code cannot reliably
39
+ close both sides of the channel during cleanup.
40
+
41
+ ## Incorrect
42
+
43
+ ```ts
44
+ new MessageChannel();
45
+ ```
46
+
47
+ ```ts
48
+ void new MessageChannel();
49
+ ```
50
+
51
+ ```ts
52
+ new MessageChannel().port1.postMessage(message);
53
+ ```
54
+
55
+ ```ts
56
+ const port = new MessageChannel().port2;
57
+ ```
58
+
59
+ ## Correct
60
+
61
+ ```ts
62
+ const channel = new MessageChannel();
63
+
64
+ channel.port1.addEventListener("message", onMessage);
65
+ channel.port1.close();
66
+ channel.port2.close();
67
+ ```
68
+
69
+ ```ts
70
+ const { port1, port2 } = new MessageChannel();
71
+
72
+ port1.postMessage(message);
73
+ port1.close();
74
+ port2.close();
75
+ ```
76
+
77
+ ```ts
78
+ return new MessageChannel();
79
+ ```
80
+
81
+ ```ts
82
+ registerChannel(new MessageChannel());
83
+ ```
84
+
85
+ ## Behavior and migration notes
86
+
87
+ Store either the `MessageChannel` object or both `MessagePort` handles in the
88
+ owner that will close them. For UI code, that owner is usually a component,
89
+ worker bridge, route lifecycle, or application-level channel manager.
90
+
91
+ This rule does not autofix. Introducing a variable without selecting the owner
92
+ and teardown point would hide the lifecycle problem instead of solving it.
93
+
94
+ ## ESLint flat config example
95
+
96
+ ```js
97
+ import runtimeCleanup from "eslint-plugin-runtime-cleanup";
98
+
99
+ export default [
100
+ runtimeCleanup.configs.recommended,
101
+ ];
102
+ ```
103
+
104
+ ## When not to use it
105
+
106
+ Do not enable this rule for code that intentionally creates runtime-lifetime
107
+ message ports and does not need explicit teardown. Prefer a narrow disable
108
+ comment with a reason when a channel is meant to live for the whole runtime.
109
+
110
+ ## Further reading
111
+
112
+ - [MDN: `MessageChannel`](https://developer.mozilla.org/docs/Web/API/MessageChannel)
113
+ - [MDN: `MessagePort`](https://developer.mozilla.org/docs/Web/API/MessagePort)
114
+ - [MDN: `MessagePort.close()`](https://developer.mozilla.org/docs/Web/API/MessagePort/close)
@@ -0,0 +1,116 @@
1
+ # no-floating-network-connections
2
+
3
+ Require browser network connection handles to be retained so they can be
4
+ closed.
5
+
6
+ > **Rule catalog ID:** R009
7
+
8
+ ## Targeted pattern scope
9
+
10
+ This rule targets browser connection constructors with explicit `.close()`
11
+ lifecycle APIs:
12
+
13
+ - `WebSocket`
14
+ - `EventSource`
15
+ - `window.WebSocket`, `self.WebSocket`, and `globalThis.WebSocket`
16
+ - `window.EventSource`, `self.EventSource`, and `globalThis.EventSource`
17
+
18
+ The rule reports connection instances that are immediately discarded or chained
19
+ directly into a method call other than `.close()`. In both cases there is no
20
+ remaining connection handle available for teardown.
21
+
22
+ ## What this rule reports
23
+
24
+ The rule reports:
25
+
26
+ - standalone connection construction such as `new WebSocket(url);`
27
+ - voided connection construction such as `void new EventSource(url);`
28
+ - immediate send or listener chains such as `new WebSocket(url).send(message);`
29
+ - immediate `EventSource` listener registration on a discarded instance
30
+
31
+ It intentionally does not require same-function `close()` calls. Ownership can
32
+ be transferred to a component instance, connection manager, returned value, or
33
+ longer-lived runtime owner.
34
+
35
+ ## Why this rule exists
36
+
37
+ `WebSocket` and `EventSource` create long-lived network connections. If the
38
+ handle is not retained, code cannot reliably call `.close()`, remove listeners,
39
+ or coordinate reconnect and shutdown behavior. Discarding the handle makes the
40
+ connection lifecycle implicit and usually leaks work until the page or runtime
41
+ exits.
42
+
43
+ ## Incorrect
44
+
45
+ ```ts
46
+ new WebSocket("wss://example.com/socket");
47
+ ```
48
+
49
+ ```ts
50
+ void new EventSource("/events");
51
+ ```
52
+
53
+ ```ts
54
+ new WebSocket(url).send("hello");
55
+ ```
56
+
57
+ ```ts
58
+ new EventSource("/events").addEventListener("message", onMessage);
59
+ ```
60
+
61
+ ## Correct
62
+
63
+ ```ts
64
+ const socket = new WebSocket("wss://example.com/socket");
65
+
66
+ socket.addEventListener("message", onMessage);
67
+ socket.close();
68
+ ```
69
+
70
+ ```ts
71
+ const source = new EventSource("/events");
72
+
73
+ source.addEventListener("message", onMessage);
74
+ source.close();
75
+ ```
76
+
77
+ ```ts
78
+ return new WebSocket(url);
79
+ ```
80
+
81
+ ```ts
82
+ registerConnection(new EventSource(url));
83
+ ```
84
+
85
+ ## Behavior and migration notes
86
+
87
+ Store the connection handle in the owner that will close it. For UI code, that
88
+ is usually the component or route lifecycle. For shared libraries, returning
89
+ the connection or passing it to a connection manager makes the ownership
90
+ contract explicit.
91
+
92
+ This rule does not autofix. Introducing a variable without a matching close
93
+ path would hide the lifecycle problem instead of solving it.
94
+
95
+ ## ESLint flat config example
96
+
97
+ ```js
98
+ import runtimeCleanup from "eslint-plugin-runtime-cleanup";
99
+
100
+ export default [
101
+ runtimeCleanup.configs.recommended,
102
+ ];
103
+ ```
104
+
105
+ ## When not to use it
106
+
107
+ Do not enable this rule for code that intentionally creates page-lifetime
108
+ connections and does not need explicit teardown. Prefer a narrow disable
109
+ comment with a reason when a connection is meant to live for the whole runtime.
110
+
111
+ ## Further reading
112
+
113
+ - [MDN: `WebSocket`](https://developer.mozilla.org/docs/Web/API/WebSocket)
114
+ - [MDN: `WebSocket.close()`](https://developer.mozilla.org/docs/Web/API/WebSocket/close)
115
+ - [MDN: `EventSource`](https://developer.mozilla.org/docs/Web/API/EventSource)
116
+ - [MDN: `EventSource.close()`](https://developer.mozilla.org/docs/Web/API/EventSource/close)