eslint-plugin-no-mistakes 0.95.0 → 0.96.0

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 (61) hide show
  1. package/README.md +5 -0
  2. package/package.json +1 -1
  3. package/src/index.js +3 -0
  4. package/src/rules/argv-main-fragments.js +25 -0
  5. package/src/rules/argv-main-values.js +65 -7
  6. package/src/rules/async-call-disposition.js +39 -154
  7. package/src/rules/async-disposition-bindings.js +131 -0
  8. package/src/rules/async-disposition-expressions.js +166 -0
  9. package/src/rules/async-disposition-flow.js +151 -0
  10. package/src/rules/async-disposition-mock-bindings.js +39 -0
  11. package/src/rules/async-disposition-mocks.js +142 -0
  12. package/src/rules/configured-runtime-import.js +28 -0
  13. package/src/rules/gh-api-argument-safety.js +6 -30
  14. package/src/rules/gh-api-wrappers.js +52 -0
  15. package/src/rules/logical-call.js +75 -0
  16. package/src/rules/module-mock-helpers.js +1 -62
  17. package/src/rules/no-banned-import-outside-allowed-paths-calls.js +39 -0
  18. package/src/rules/no-banned-import-outside-allowed-paths.js +3 -29
  19. package/src/rules/no-consecutive-unbatched-reads.js +136 -0
  20. package/src/rules/no-object-mutation-ast.js +13 -0
  21. package/src/rules/no-object-mutation-bind.js +10 -3
  22. package/src/rules/no-object-mutation-call.js +22 -10
  23. package/src/rules/no-object-mutation-expr.js +9 -2
  24. package/src/rules/no-object-mutation-fn.js +8 -0
  25. package/src/rules/no-object-mutation-forward.js +2 -11
  26. package/src/rules/no-object-mutation-invoke.js +40 -1
  27. package/src/rules/no-object-mutation-join.js +5 -0
  28. package/src/rules/no-object-mutation-method.js +65 -0
  29. package/src/rules/no-object-mutation-read.js +17 -1
  30. package/src/rules/no-object-mutation-scan.js +8 -1
  31. package/src/rules/no-object-mutation-values.js +4 -0
  32. package/src/rules/no-object-mutation-write.js +1 -0
  33. package/src/rules/playwright-assertion-timeout-cap.js +13 -18
  34. package/src/rules/playwright-scroll-call.js +8 -6
  35. package/src/rules/playwright-static-call-alias.js +65 -0
  36. package/src/rules/playwright-static-call.js +20 -74
  37. package/src/rules/postgres-executor-calls.js +157 -0
  38. package/src/rules/postgres-executor.js +19 -3
  39. package/src/rules/postgres-fanout-calls.js +72 -0
  40. package/src/rules/postgres-no-manual-transaction.js +5 -2
  41. package/src/rules/postgres-no-nonliteral-sql-registry-key.js +2 -2
  42. package/src/rules/postgres-no-unbounded-query-fanout.js +9 -3
  43. package/src/rules/postgres-runtime-helpers.js +9 -5
  44. package/src/rules/require-top-level-registration.js +64 -0
  45. package/src/rules/required-call-selector-aliases.js +45 -0
  46. package/src/rules/required-call-selector-compile.js +9 -3
  47. package/src/rules/required-call-selectors.js +24 -5
  48. package/src/rules/restricted-api-usage.js +195 -0
  49. package/src/rules/test-message-expect.js +10 -7
  50. package/src/rules/test-no-delayed-rejects-expect.js +59 -0
  51. package/src/rules/test-no-delayed-rejects.js +2 -26
  52. package/src/rules/test-no-skips-bindings.js +14 -4
  53. package/src/rules/test-no-skips.js +9 -5
  54. package/src/rules/ts-no-function-aliases-ast.js +65 -0
  55. package/src/rules/ts-no-function-aliases-contract.js +92 -0
  56. package/src/rules/ts-no-function-aliases-forwarding.js +46 -0
  57. package/src/rules/ts-no-function-aliases-legacy.js +79 -0
  58. package/src/rules/ts-no-function-aliases-reachable.js +76 -0
  59. package/src/rules/ts-no-function-aliases-self.js +132 -0
  60. package/src/rules/ts-no-function-aliases-signature.js +65 -0
  61. package/src/rules/ts-no-function-aliases.js +66 -95
package/README.md CHANGED
@@ -57,3 +57,8 @@ See [options and examples](../../docs/eslint-rules/postgres-no-async-sql-builder
57
57
  `require-options-on-imported-call` requires a static options object on configured
58
58
  imports, globals with no local binding, and terminal methods with a matching receiver shape.
59
59
  See [options and examples](../../docs/eslint-rules/require-options-on-imported-call.md).
60
+
61
+ `restricted-api-usage` is an opt-in import/export-name and call-name policy.
62
+ See [options and examples](../../docs/eslint-rules/restricted-api-usage.md).
63
+
64
+ Opt-in client contracts: [`no-consecutive-unbatched-reads`](../../docs/eslint-rules/no-consecutive-unbatched-reads.md) configures adjacent awaited read batching; [`require-top-level-registration`](../../docs/eslint-rules/require-top-level-registration.md) configures owner-module lifecycle registration. Both support ESLint/Oxlint and have no product defaults.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-no-mistakes",
3
- "version": "0.95.0",
3
+ "version": "0.96.0",
4
4
  "description": "ESLint and Oxlint rules that keep code static enough for no-mistakes analyzers and coding agents",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/index.js CHANGED
@@ -1,6 +1,9 @@
1
1
  "use strict";
2
2
 
3
3
  const rules = {
4
+ "restricted-api-usage": require("./rules/restricted-api-usage"),
5
+ "no-consecutive-unbatched-reads": require("./rules/no-consecutive-unbatched-reads"),
6
+ "require-top-level-registration": require("./rules/require-top-level-registration"),
4
7
  "forbidden-member-chains": require("./rules/forbidden-member-chains"),
5
8
  "gh-api-argument-safety": require("./rules/gh-api-argument-safety"),
6
9
  "no-argv-main-guard": require("./rules/no-argv-main-guard"),
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ const ARGV = 1;
3
+ // Strings and [prefix, suffix] fragments stay separate from path identities.
4
+ // Keeping the fragment until the comparison lets nested concatenations fold once.
5
+ function identity(value) {
6
+ if (Array.isArray(value)) return /^file:\/\/\/?$/i.test(value[0]) && value[1] === "" ? ARGV : 0;
7
+ return typeof value === "number" ? value : 0;
8
+ }
9
+ function concatenate(left, right) {
10
+ if (left === ARGV) left = ["", ""];
11
+ if (right === ARGV) right = ["", ""];
12
+ // Accepted prefixes have at most eight characters; reject before allocating.
13
+ if (typeof left === "string" && typeof right === "string")
14
+ return left.length + right.length <= 8 ? left + right : 0;
15
+ if (typeof left === "string" && Array.isArray(right))
16
+ return left.length + right[0].length <= 8 ? [left + right[0], right[1]] : 0;
17
+ if (Array.isArray(left) && typeof right === "string") return right === "" ? left : 0;
18
+ return 0;
19
+ }
20
+ function fileUrlArgument(target, read) {
21
+ if (target?.type !== "NewExpression" || target.arguments.length !== 1) return 0;
22
+ const argument = read(target.arguments[0]);
23
+ return Array.isArray(argument) && identity(argument) === ARGV ? argument : 0;
24
+ }
25
+ module.exports = { ARGV, identity, concatenate, fileUrlArgument };
@@ -2,6 +2,7 @@
2
2
  const { unwrapExpression } = require("./async-ast");
3
3
  const { resolveVariable, memberPropertyName, isLocalRequire } = require("./async-target-bindings");
4
4
  const { literalString, logicalCall } = require("./module-mock-helpers");
5
+ const { ARGV, identity, concatenate, fileUrlArgument } = require("./argv-main-fragments");
5
6
 
6
7
  function globalName(node, name, context) {
7
8
  if (node?.type !== "Identifier" || node.name !== name) return false;
@@ -29,16 +30,18 @@ function nodeProcess(node, context) {
29
30
  ["process", "node:process"].includes(literalString(init.arguments[0]))
30
31
  );
31
32
  }
32
- const ARGV = 1;
33
33
  const MAIN = 2;
34
34
  const PROCESS = 3;
35
35
  const ARGUMENTS = 4;
36
36
  function createClassifier(context, wrappers) {
37
37
  const stable = new WeakMap();
38
38
  const aliases = new WeakMap();
39
+ const value = (node, depth) => identity(classify(node, depth));
39
40
  function classify(input, depth = 0) {
40
41
  const node = unwrapExpression(input);
41
42
  if (!node || depth > 32) return 0;
43
+ const text = literalString(node);
44
+ if (text !== null) return text;
42
45
  if (node.type === "Identifier") {
43
46
  const variable = resolveVariable(node, context);
44
47
  if (!variable) {
@@ -67,7 +70,7 @@ function createClassifier(context, wrappers) {
67
70
  def.node?.id?.type === "ObjectPattern"
68
71
  ) {
69
72
  const key = objectPatternKey(def.node.id, node.name);
70
- if (key === "argv" && classify(def.node.init, depth + 1) === PROCESS) return ARGUMENTS;
73
+ if (key === "argv" && value(def.node.init, depth + 1) === PROCESS) return ARGUMENTS;
71
74
  return 0;
72
75
  }
73
76
  if (
@@ -83,6 +86,29 @@ function createClassifier(context, wrappers) {
83
86
  }
84
87
  return aliases.get(variable);
85
88
  }
89
+ if (node.type === "BinaryExpression" && node.operator === "+") {
90
+ return concatenate(classify(node.left, depth + 1), classify(node.right, depth + 1));
91
+ }
92
+ if (
93
+ node.type === "LogicalExpression" &&
94
+ node.operator === "??" &&
95
+ classify(node.right, depth + 1) === ""
96
+ ) {
97
+ return classify(node.left, depth + 1);
98
+ }
99
+ if (node.type === "TemplateLiteral") {
100
+ // Only prefix substitutions may be constant; a suffix substitution is not an argv guard.
101
+ const last = node.expressions.length - 1;
102
+ const argument = classify(node.expressions[last], depth + 1);
103
+ if (identity(argument) !== ARGV || node.quasis[last + 1].value.cooked !== "") return 0;
104
+ let prefix = node.quasis[0].value.cooked;
105
+ for (let i = 0; i < last; i++) {
106
+ const part = classify(node.expressions[i], depth + 1);
107
+ if (typeof part !== "string") return 0;
108
+ prefix = concatenate(prefix, concatenate(part, node.quasis[i + 1].value.cooked));
109
+ }
110
+ return concatenate(prefix, argument);
111
+ }
86
112
  if (node.type === "MemberExpression") {
87
113
  const name = memberPropertyName(node);
88
114
  if (
@@ -92,7 +118,12 @@ function createClassifier(context, wrappers) {
92
118
  ["url", "filename"].includes(name)
93
119
  )
94
120
  return MAIN;
95
- const object = classify(node.object, depth + 1);
121
+ const target = unwrapExpression(node.object);
122
+ if (name === "href" && globalName(target?.callee, "URL", context)) {
123
+ const constructed = fileUrlArgument(target, (argument) => classify(argument, depth + 1));
124
+ if (constructed) return constructed;
125
+ }
126
+ const object = value(node.object, depth + 1);
96
127
  if (object === PROCESS && name === "argv") return ARGUMENTS;
97
128
  if (object === ARGUMENTS && node.computed && staticIndexOne(node.property)) return ARGV;
98
129
  // A URL conversion's href preserves the same module/argv identity.
@@ -110,20 +141,47 @@ function createClassifier(context, wrappers) {
110
141
  if (
111
142
  callee?.type === "MemberExpression" &&
112
143
  memberPropertyName(callee) === "at" &&
113
- classify(callee.object, depth + 1) === ARGUMENTS &&
144
+ value(callee.object, depth + 1) === ARGUMENTS &&
114
145
  invoked.arguments.length === 1 &&
115
146
  staticIndexOne(invoked.arguments[0])
116
147
  )
117
148
  return ARGV;
149
+ if (
150
+ !invoked.forwarded &&
151
+ callee?.type === "MemberExpression" &&
152
+ memberPropertyName(callee) === "toString" &&
153
+ invoked.arguments.length === 0
154
+ ) {
155
+ const url = unwrapExpression(callee.object);
156
+ if (globalName(url?.callee, "URL", context)) {
157
+ const constructed = fileUrlArgument(url, (argument) => classify(argument, depth + 1));
158
+ if (constructed) return constructed;
159
+ }
160
+ const classified = classify(callee.object, depth + 1);
161
+ return typeof classified === "string" || [ARGV, MAIN].includes(identity(classified))
162
+ ? classified
163
+ : 0;
164
+ }
165
+ if (
166
+ !invoked.forwarded &&
167
+ callee?.type === "MemberExpression" &&
168
+ memberPropertyName(callee) === "concat" &&
169
+ invoked.arguments.every((argument) => argument.type !== "SpreadElement")
170
+ )
171
+ return invoked.arguments.reduce(
172
+ (folded, argument) => concatenate(folded, classify(argument, depth + 1)),
173
+ classify(callee.object, depth + 1),
174
+ );
118
175
  if (invoked.arguments.length !== 1 || invoked.arguments[0].type === "SpreadElement") return 0;
176
+ if (globalName(callee, "String", context)) return classify(invoked.arguments[0], depth + 1);
119
177
  if (globalName(callee, "Boolean", context) || wrappers.resolveCallTarget(node)) {
120
- const value = classify(invoked.arguments[0], depth + 1);
121
- return value === ARGV || value === MAIN ? value : 0;
178
+ const argument = value(invoked.arguments[0], depth + 1);
179
+ return argument === ARGV || argument === MAIN ? argument : 0;
122
180
  }
123
181
  }
124
182
  return 0;
125
183
  }
126
- return (node) => classify(node);
184
+ return (node) => value(node, 0);
127
185
  }
128
186
  function staticIndexOne(node) {
129
187
  if (!node) return false;
@@ -1,166 +1,50 @@
1
1
  "use strict";
2
2
 
3
3
  const { rule } = require("../helpers");
4
+ const { ruleSuppression } = require("../rule-suppression");
4
5
  const {
5
- findContainingFunction,
6
- isFunction,
7
- isTransparentExpression,
8
- unwrapExpression,
9
- unwrapTransparentParent,
10
- } = require("./async-ast");
6
+ collectedPromise,
7
+ isPromiseAllCall,
8
+ retainedCollection,
9
+ retainedPromise,
10
+ } = require("./async-disposition-bindings");
11
+ const {
12
+ directlyDisposed,
13
+ observedExpression,
14
+ promiseAllDisposed,
15
+ mappedPromise,
16
+ } = require("./async-disposition-expressions");
11
17
  const { targetOptionsSchema } = require("./async-schema");
12
- const { createTargetMatcher, memberPropertyName } = require("./async-targets");
18
+ const { createTargetMatcher } = require("./async-targets");
13
19
 
14
- function isPromiseAllCall(node) {
15
- const callee = unwrapExpression(node?.callee);
16
- const object = unwrapExpression(callee?.object);
20
+ function isDisposed(node, context, seen = new Set()) {
21
+ if (seen.has(node)) return false;
22
+ seen.add(node);
23
+ const observed = observedExpression(node, context);
24
+ const disposed = (value) => isDisposed(value, context, seen);
17
25
  return (
18
- node?.type === "CallExpression" &&
19
- callee?.type === "MemberExpression" &&
20
- object?.type === "Identifier" &&
21
- object.name === "Promise" &&
22
- memberPropertyName(callee) === "all"
26
+ directlyDisposed(node, context) ||
27
+ directlyDisposed(observed, context) ||
28
+ promiseAllDisposed(observed, context) ||
29
+ retainedPromise(observed, context, disposed) ||
30
+ mappedPromise(observed, (value) =>
31
+ retainedCollection(
32
+ value,
33
+ context,
34
+ (aggregate) => isPromiseAllCall(aggregate, context),
35
+ disposed,
36
+ ),
37
+ ) ||
38
+ collectedPromise(
39
+ observed,
40
+ context,
41
+ (value) => isPromiseAllCall(value, context),
42
+ disposed,
43
+ node.type === "CallExpression",
44
+ )
23
45
  );
24
46
  }
25
47
 
26
- function directlyDisposed(node) {
27
- const expression = unwrapTransparentParent(node);
28
- const parent = expression.parent;
29
- if (parent?.type === "ReturnStatement") {
30
- const fn = findContainingFunction(parent);
31
- return !isCallArgument(fn);
32
- }
33
- if (parent?.type === "ArrowFunctionExpression" && parent.body === expression) {
34
- if (isCallCallee(parent)) return directlyDisposed(parent.parent);
35
- return !isCallArgument(parent);
36
- }
37
- return (
38
- parent?.type === "AwaitExpression" ||
39
- (parent?.type === "UnaryExpression" && parent.operator === "void")
40
- );
41
- }
42
-
43
- function isCallArgument(node) {
44
- return Boolean(node?.parent?.type === "CallExpression" && node.parent.arguments.includes(node));
45
- }
46
-
47
- function isCallCallee(node) {
48
- return node?.parent?.type === "CallExpression" && node.parent.callee === node;
49
- }
50
-
51
- function isPromiseAllIterable(node, promiseAll) {
52
- return promiseAll.arguments[0] === node && (node.type === "ArrayExpression" || isMapCall(node));
53
- }
54
-
55
- function isMapCall(node) {
56
- return (
57
- node?.type === "CallExpression" &&
58
- node.callee.type === "MemberExpression" &&
59
- memberPropertyName(node.callee) === "map"
60
- );
61
- }
62
-
63
- function isMapCallback(fn) {
64
- const call = fn.parent;
65
- return isMapCall(call) && call.arguments.includes(fn);
66
- }
67
-
68
- function expressionReturnedFromCallback(node, fn) {
69
- let current = node;
70
- let returnStatement = null;
71
- while (current && current !== fn) {
72
- if (current.parent === fn && fn.body === current && fn.body.type !== "BlockStatement") {
73
- return true;
74
- }
75
- if (current.parent?.type === "ReturnStatement") {
76
- returnStatement = current.parent;
77
- }
78
- current = current.parent;
79
- }
80
- return Boolean(returnStatement);
81
- }
82
-
83
- function canReachPromiseAll(node, promiseAll) {
84
- let current = node;
85
- while (current && current !== promiseAll) {
86
- const parent = current.parent;
87
- if (isTransparentExpression(parent)) {
88
- current = parent;
89
- continue;
90
- }
91
- if (parent?.type === "MemberExpression") return false;
92
- if (parent === promiseAll) return isPromiseAllIterable(current, promiseAll);
93
- if (isFunction(parent)) {
94
- if (!expressionReturnedFromCallback(node, parent) || !isMapCallback(parent)) {
95
- return false;
96
- }
97
- current = parent.parent;
98
- continue;
99
- }
100
- if (parent?.type === "ArrayExpression") {
101
- if (parent.parent !== promiseAll || promiseAll.arguments[0] !== parent) return false;
102
- current = parent;
103
- continue;
104
- }
105
- if (parent?.type === "ObjectExpression") return false;
106
- if (
107
- parent?.type === "CallExpression" &&
108
- parent !== promiseAll &&
109
- parent.arguments.includes(current)
110
- ) {
111
- return false;
112
- }
113
- current = parent;
114
- }
115
- return current === promiseAll;
116
- }
117
-
118
- function observedExpression(node) {
119
- let current = node;
120
- while (current.parent) {
121
- const parent = current.parent;
122
- if (isTransparentExpression(parent)) {
123
- current = parent;
124
- continue;
125
- }
126
- if (
127
- parent.type === "MemberExpression" &&
128
- parent.object === current &&
129
- parent.parent?.type === "CallExpression" &&
130
- parent.parent.callee === parent
131
- ) {
132
- current = parent;
133
- continue;
134
- }
135
- if (parent.type === "CallExpression" && parent.callee === current) {
136
- current = parent;
137
- continue;
138
- }
139
- break;
140
- }
141
- return current;
142
- }
143
-
144
- function promiseAllDisposed(node) {
145
- let current = node.parent;
146
- while (current) {
147
- if (isPromiseAllCall(current) && canReachPromiseAll(node, current)) {
148
- return (
149
- directlyDisposed(current) ||
150
- directlyDisposed(observedExpression(current)) ||
151
- promiseAllDisposed(current)
152
- );
153
- }
154
- current = current.parent;
155
- }
156
- return false;
157
- }
158
-
159
- function isDisposed(node) {
160
- const observed = observedExpression(node);
161
- return directlyDisposed(node) || directlyDisposed(observed) || promiseAllDisposed(node);
162
- }
163
-
164
48
  module.exports = rule(
165
49
  {
166
50
  type: "problem",
@@ -178,12 +62,13 @@ module.exports = rule(
178
62
  },
179
63
  (context) => {
180
64
  const matcher = createTargetMatcher(context);
65
+ const suppressed = ruleSuppression(context, "async-call-disposition");
181
66
  if (!matcher.hasTargets) return {};
182
67
 
183
68
  return {
184
69
  ...matcher.visitors,
185
70
  CallExpression(node) {
186
- if (!matcher.isTargetCall(node) || isDisposed(node)) return;
71
+ if (!matcher.isTargetCall(node) || suppressed(node) || isDisposed(node, context)) return;
187
72
  context.report({
188
73
  node,
189
74
  messageId: "disposition",
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+
3
+ const { unwrapExpression, unwrapTransparentParent } = require("./async-ast");
4
+ const { resolveVariable, stableConstInitializer } = require("./async-target-bindings");
5
+ const { laterDrain, laterInBlock } = require("./async-disposition-flow");
6
+ const { propertiesWritten } = require("./async-disposition-mocks");
7
+ const { memberPropertyName } = require("./async-targets");
8
+
9
+ function globalPromise(node, context) {
10
+ const variable = resolveVariable(node, context);
11
+ return !variable?.defs.length && !propertiesWritten(node, context);
12
+ }
13
+
14
+ function isPromiseMethodCall(node, context, methods) {
15
+ const callee = unwrapExpression(node?.callee);
16
+ const object = unwrapExpression(callee?.object);
17
+ return (
18
+ node?.type === "CallExpression" &&
19
+ callee?.type === "MemberExpression" &&
20
+ object?.type === "Identifier" &&
21
+ object.name === "Promise" &&
22
+ globalPromise(object, context) &&
23
+ methods.includes(memberPropertyName(callee))
24
+ );
25
+ }
26
+
27
+ function isPromiseAllCall(node, context) {
28
+ return isPromiseMethodCall(node, context, ["all", "allSettled", "race", "any"]);
29
+ }
30
+
31
+ function standalonePush(call) {
32
+ return (
33
+ call.parent.type === "ExpressionStatement" ||
34
+ (call.parent.type === "ArrowFunctionExpression" && call.parent.body === call)
35
+ );
36
+ }
37
+
38
+ function pushedArray(node) {
39
+ const value = unwrapTransparentParent(node);
40
+ const push = value.parent;
41
+ const callee = unwrapExpression(push?.callee);
42
+ if (
43
+ push?.type !== "CallExpression" ||
44
+ !push.arguments.includes(value) ||
45
+ callee?.type !== "MemberExpression" ||
46
+ memberPropertyName(callee) !== "push" ||
47
+ callee.object.type !== "Identifier" ||
48
+ !standalonePush(push)
49
+ )
50
+ return null;
51
+ return { id: callee.object, push };
52
+ }
53
+
54
+ function collectedPromise(node, context, isAggregate, disposed, createdHere) {
55
+ const found = pushedArray(node);
56
+ if (!found) return false;
57
+ const init = unwrapExpression(stableConstInitializer(found.id, context, new Set()));
58
+ if (init?.type !== "ArrayExpression" || init.elements.length !== 0) return false;
59
+ const variable = resolveVariable(found.id, context);
60
+ let drained = false;
61
+ for (const reference of variable.references) {
62
+ if (reference.init) continue;
63
+ const id = reference.identifier;
64
+ const parent = unwrapTransparentParent(id).parent;
65
+ if (isAggregate(parent) && parent.arguments[0] === id) {
66
+ if (laterDrain(found.push, parent, createdHere) && disposed(parent)) drained = true;
67
+ continue;
68
+ }
69
+ const member = parent;
70
+ const call = member?.parent;
71
+ if (
72
+ member?.type !== "MemberExpression" ||
73
+ member.object !== id ||
74
+ memberPropertyName(member) !== "push" ||
75
+ call?.type !== "CallExpression" ||
76
+ call.callee !== member ||
77
+ !standalonePush(call)
78
+ )
79
+ return false;
80
+ // A sibling push cannot remove values already collected. Its producer
81
+ // independently proves whether it reaches a handled drain.
82
+ }
83
+ return drained;
84
+ }
85
+
86
+ function retainedCollection(node, context, isAggregate, disposed) {
87
+ const expression = unwrapTransparentParent(node);
88
+ const declaration = expression.parent;
89
+ if (
90
+ declaration?.type !== "VariableDeclarator" ||
91
+ declaration.init !== expression ||
92
+ declaration.id.type !== "Identifier" ||
93
+ !stableConstInitializer(declaration.id, context, new Set())
94
+ )
95
+ return false;
96
+ const variable = resolveVariable(declaration.id, context);
97
+ let drained = false;
98
+ for (const reference of variable.references) {
99
+ if (reference.init) continue;
100
+ const id = unwrapTransparentParent(reference.identifier);
101
+ const parent = id.parent;
102
+ if (!isAggregate(parent) || parent.arguments[0] !== id) return false;
103
+ if (laterDrain(node, parent) && disposed(parent)) drained = true;
104
+ }
105
+ return drained;
106
+ }
107
+
108
+ function retainedPromise(node, context, disposed) {
109
+ const expression = unwrapTransparentParent(node);
110
+ const declaration = expression.parent;
111
+ if (
112
+ declaration?.type !== "VariableDeclarator" ||
113
+ declaration.init !== expression ||
114
+ declaration.id.type !== "Identifier"
115
+ )
116
+ return false;
117
+ if (!stableConstInitializer(declaration.id, context, new Set())) return false;
118
+ const variable = resolveVariable(declaration.id, context);
119
+ return variable.references.some(
120
+ (reference) =>
121
+ !reference.init && laterInBlock(node, reference.identifier) && disposed(reference.identifier),
122
+ );
123
+ }
124
+
125
+ module.exports = {
126
+ collectedPromise,
127
+ isPromiseAllCall,
128
+ isPromiseMethodCall,
129
+ retainedCollection,
130
+ retainedPromise,
131
+ };