monkeypatchlist 0.0.0-stage → 0.1.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 (221) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +61 -0
  3. package/README.md +183 -2
  4. package/catalog/catalog.json +552211 -0
  5. package/catalog/catalog.schema.json +787 -0
  6. package/catalog/changelog.json +3 -0
  7. package/dist/bin/monkeypatchlist.d.ts +2 -0
  8. package/dist/bin/monkeypatchlist.js +3 -0
  9. package/dist/catalog.d.ts +561 -0
  10. package/dist/catalog.js +58 -0
  11. package/dist/cli.d.ts +84 -0
  12. package/dist/cli.js +470 -0
  13. package/dist/index.d.ts +2 -0
  14. package/dist/index.js +1 -0
  15. package/dist/lockfile-input.d.ts +9 -0
  16. package/dist/lockfile-input.js +91 -0
  17. package/dist/packages/analysis/binding.d.ts +2 -0
  18. package/dist/packages/analysis/binding.js +5 -0
  19. package/dist/packages/analysis/index.d.ts +10 -0
  20. package/dist/packages/analysis/index.js +13 -0
  21. package/dist/packages/analysis/job.d.ts +33 -0
  22. package/dist/packages/analysis/job.js +85 -0
  23. package/dist/packages/analysis/lib/absence-tags.d.ts +34 -0
  24. package/dist/packages/analysis/lib/absence-tags.js +340 -0
  25. package/dist/packages/analysis/lib/ast.d.ts +28 -0
  26. package/dist/packages/analysis/lib/ast.js +64 -0
  27. package/dist/packages/analysis/lib/binding.d.ts +36 -0
  28. package/dist/packages/analysis/lib/binding.js +66 -0
  29. package/dist/packages/analysis/lib/budget.d.ts +80 -0
  30. package/dist/packages/analysis/lib/budget.js +31 -0
  31. package/dist/packages/analysis/lib/catch-arms.d.ts +19 -0
  32. package/dist/packages/analysis/lib/catch-arms.js +351 -0
  33. package/dist/packages/analysis/lib/chunk-loads.d.ts +160 -0
  34. package/dist/packages/analysis/lib/chunk-loads.js +882 -0
  35. package/dist/packages/analysis/lib/chunk-registry.d.ts +45 -0
  36. package/dist/packages/analysis/lib/chunk-registry.js +334 -0
  37. package/dist/packages/analysis/lib/code-routes.d.ts +52 -0
  38. package/dist/packages/analysis/lib/code-routes.js +921 -0
  39. package/dist/packages/analysis/lib/collisions.d.ts +22 -0
  40. package/dist/packages/analysis/lib/collisions.js +103 -0
  41. package/dist/packages/analysis/lib/definitions.d.ts +76 -0
  42. package/dist/packages/analysis/lib/definitions.js +782 -0
  43. package/dist/packages/analysis/lib/early-exits.d.ts +39 -0
  44. package/dist/packages/analysis/lib/early-exits.js +161 -0
  45. package/dist/packages/analysis/lib/effect.d.ts +138 -0
  46. package/dist/packages/analysis/lib/effect.js +800 -0
  47. package/dist/packages/analysis/lib/global-object.d.ts +581 -0
  48. package/dist/packages/analysis/lib/global-object.js +8240 -0
  49. package/dist/packages/analysis/lib/keys.d.ts +33 -0
  50. package/dist/packages/analysis/lib/keys.js +82 -0
  51. package/dist/packages/analysis/lib/line-starts.d.ts +41 -0
  52. package/dist/packages/analysis/lib/line-starts.js +79 -0
  53. package/dist/packages/analysis/lib/link-group.d.ts +51 -0
  54. package/dist/packages/analysis/lib/link-group.js +487 -0
  55. package/dist/packages/analysis/lib/module-links.d.ts +117 -0
  56. package/dist/packages/analysis/lib/module-links.js +1060 -0
  57. package/dist/packages/analysis/lib/nested-code.d.ts +38 -0
  58. package/dist/packages/analysis/lib/nested-code.js +200 -0
  59. package/dist/packages/analysis/lib/numeric.d.ts +16 -0
  60. package/dist/packages/analysis/lib/numeric.js +216 -0
  61. package/dist/packages/analysis/lib/once-flags.d.ts +12 -0
  62. package/dist/packages/analysis/lib/once-flags.js +190 -0
  63. package/dist/packages/analysis/lib/parameter-arguments.d.ts +12 -0
  64. package/dist/packages/analysis/lib/parameter-arguments.js +34 -0
  65. package/dist/packages/analysis/lib/parse.d.ts +10 -0
  66. package/dist/packages/analysis/lib/parse.js +39 -0
  67. package/dist/packages/analysis/lib/partial.d.ts +60 -0
  68. package/dist/packages/analysis/lib/partial.js +518 -0
  69. package/dist/packages/analysis/lib/print.d.ts +3 -0
  70. package/dist/packages/analysis/lib/print.js +150 -0
  71. package/dist/packages/analysis/lib/reachability.d.ts +137 -0
  72. package/dist/packages/analysis/lib/reachability.js +1295 -0
  73. package/dist/packages/analysis/lib/resolver.d.ts +13 -0
  74. package/dist/packages/analysis/lib/resolver.js +2886 -0
  75. package/dist/packages/analysis/lib/restores.d.ts +42 -0
  76. package/dist/packages/analysis/lib/restores.js +482 -0
  77. package/dist/packages/analysis/lib/rewritten-deletes.d.ts +6 -0
  78. package/dist/packages/analysis/lib/rewritten-deletes.js +142 -0
  79. package/dist/packages/analysis/lib/scope.d.ts +110 -0
  80. package/dist/packages/analysis/lib/scope.js +443 -0
  81. package/dist/packages/analysis/lib/script-loads.d.ts +56 -0
  82. package/dist/packages/analysis/lib/script-loads.js +359 -0
  83. package/dist/packages/analysis/lib/summaries.d.ts +177 -0
  84. package/dist/packages/analysis/lib/summaries.js +498 -0
  85. package/dist/packages/analysis/lib/types.d.ts +632 -0
  86. package/dist/packages/analysis/lib/types.js +128 -0
  87. package/dist/packages/analysis/lib/wrap.d.ts +34 -0
  88. package/dist/packages/analysis/lib/wrap.js +407 -0
  89. package/dist/packages/analysis/lib/writes.d.ts +358 -0
  90. package/dist/packages/analysis/lib/writes.js +1612 -0
  91. package/dist/packages/analysis/visit-order.d.ts +3 -0
  92. package/dist/packages/analysis/visit-order.js +23 -0
  93. package/dist/packages/attribution/index.d.ts +233 -0
  94. package/dist/packages/attribution/index.js +444 -0
  95. package/dist/packages/attribution/lib/containing-package.d.ts +76 -0
  96. package/dist/packages/attribution/lib/containing-package.js +304 -0
  97. package/dist/packages/attribution/lib/first-party.d.ts +37 -0
  98. package/dist/packages/attribution/lib/first-party.js +153 -0
  99. package/dist/packages/attribution/lib/lockfile-graph.d.ts +99 -0
  100. package/dist/packages/attribution/lib/lockfile-graph.js +158 -0
  101. package/dist/packages/attribution/lib/lockfile.d.ts +33 -0
  102. package/dist/packages/attribution/lib/lockfile.js +330 -0
  103. package/dist/packages/attribution/lib/path-rules.d.ts +29 -0
  104. package/dist/packages/attribution/lib/path-rules.js +84 -0
  105. package/dist/packages/attribution/lib/pnpm.d.ts +4 -0
  106. package/dist/packages/attribution/lib/pnpm.js +133 -0
  107. package/dist/packages/attribution/lib/remote-url.d.ts +7 -0
  108. package/dist/packages/attribution/lib/remote-url.js +24 -0
  109. package/dist/packages/attribution/lib/source-map.d.ts +51 -0
  110. package/dist/packages/attribution/lib/source-map.js +125 -0
  111. package/dist/packages/attribution/lib/yaml.d.ts +9 -0
  112. package/dist/packages/attribution/lib/yaml.js +277 -0
  113. package/dist/packages/attribution/lib/yarn.d.ts +7 -0
  114. package/dist/packages/attribution/lib/yarn.js +227 -0
  115. package/dist/packages/baseline/index.d.ts +45 -0
  116. package/dist/packages/baseline/index.js +41 -0
  117. package/dist/packages/baseline/lib/error.d.ts +15 -0
  118. package/dist/packages/baseline/lib/error.js +25 -0
  119. package/dist/packages/baseline/lib/format.d.ts +49 -0
  120. package/dist/packages/baseline/lib/format.js +22 -0
  121. package/dist/packages/baseline/lib/migrations.d.ts +19 -0
  122. package/dist/packages/baseline/lib/migrations.js +7 -0
  123. package/dist/packages/baseline/lib/previous.d.ts +23 -0
  124. package/dist/packages/baseline/lib/previous.js +64 -0
  125. package/dist/packages/baseline/lib/read.d.ts +39 -0
  126. package/dist/packages/baseline/lib/read.js +219 -0
  127. package/dist/packages/baseline/lib/report.d.ts +25 -0
  128. package/dist/packages/baseline/lib/report.js +104 -0
  129. package/dist/packages/baseline/lib/skew.d.ts +51 -0
  130. package/dist/packages/baseline/lib/skew.js +75 -0
  131. package/dist/packages/compare/index.d.ts +120 -0
  132. package/dist/packages/compare/index.js +274 -0
  133. package/dist/packages/compare/lib/catalog-notes.d.ts +73 -0
  134. package/dist/packages/compare/lib/catalog-notes.js +224 -0
  135. package/dist/packages/completeness/index.d.ts +246 -0
  136. package/dist/packages/completeness/index.js +249 -0
  137. package/dist/packages/confidence/index.d.ts +54 -0
  138. package/dist/packages/confidence/index.js +71 -0
  139. package/dist/packages/config/index.d.ts +107 -0
  140. package/dist/packages/config/index.js +95 -0
  141. package/dist/packages/config/lib/command-line.d.ts +23 -0
  142. package/dist/packages/config/lib/command-line.js +239 -0
  143. package/dist/packages/config/lib/config-file.d.ts +11 -0
  144. package/dist/packages/config/lib/config-file.js +119 -0
  145. package/dist/packages/config/lib/scan-config-id.d.ts +28 -0
  146. package/dist/packages/config/lib/scan-config-id.js +54 -0
  147. package/dist/packages/config/lib/schema.d.ts +2 -0
  148. package/dist/packages/config/lib/schema.js +31 -0
  149. package/dist/packages/config/lib/types.d.ts +64 -0
  150. package/dist/packages/config/lib/types.js +6 -0
  151. package/dist/packages/config/lib/urls.d.ts +6 -0
  152. package/dist/packages/config/lib/urls.js +9 -0
  153. package/dist/packages/discovery/index.d.ts +277 -0
  154. package/dist/packages/discovery/index.js +1950 -0
  155. package/dist/packages/discovery/lib/import-map.d.ts +68 -0
  156. package/dist/packages/discovery/lib/import-map.js +180 -0
  157. package/dist/packages/discovery/lib/requests.d.ts +58 -0
  158. package/dist/packages/discovery/lib/requests.js +591 -0
  159. package/dist/packages/discovery/lib/scopes.d.ts +6 -0
  160. package/dist/packages/discovery/lib/scopes.js +286 -0
  161. package/dist/packages/discovery/lib/source-maps.d.ts +56 -0
  162. package/dist/packages/discovery/lib/source-maps.js +209 -0
  163. package/dist/packages/discovery/lib/swept-code.d.ts +27 -0
  164. package/dist/packages/discovery/lib/swept-code.js +107 -0
  165. package/dist/packages/discovery/lib/syntax.d.ts +17 -0
  166. package/dist/packages/discovery/lib/syntax.js +39 -0
  167. package/dist/packages/findings/index.d.ts +106 -0
  168. package/dist/packages/findings/index.js +254 -0
  169. package/dist/packages/findings/lib/scope.d.ts +38 -0
  170. package/dist/packages/findings/lib/scope.js +110 -0
  171. package/dist/packages/parallel/index.d.ts +2 -0
  172. package/dist/packages/parallel/index.js +5 -0
  173. package/dist/packages/parallel/lib/protocol.d.ts +15 -0
  174. package/dist/packages/parallel/lib/protocol.js +2 -0
  175. package/dist/packages/parallel/lib/runner.d.ts +36 -0
  176. package/dist/packages/parallel/lib/runner.js +96 -0
  177. package/dist/packages/parallel/lib/schedule.d.ts +16 -0
  178. package/dist/packages/parallel/lib/schedule.js +52 -0
  179. package/dist/packages/parallel/lib/worker.d.ts +1 -0
  180. package/dist/packages/parallel/lib/worker.js +18 -0
  181. package/dist/packages/policy/index.d.ts +36 -0
  182. package/dist/packages/policy/index.js +22 -0
  183. package/dist/packages/report/explain.d.ts +26 -0
  184. package/dist/packages/report/explain.js +99 -0
  185. package/dist/packages/report/index.d.ts +210 -0
  186. package/dist/packages/report/index.js +388 -0
  187. package/dist/packages/report/terminal.d.ts +11 -0
  188. package/dist/packages/report/terminal.js +331 -0
  189. package/dist/packages/source-store/fixture.d.ts +22 -0
  190. package/dist/packages/source-store/fixture.js +58 -0
  191. package/dist/packages/source-store/index.d.ts +278 -0
  192. package/dist/packages/source-store/index.js +322 -0
  193. package/dist/packages/source-store/lib/addresses.d.ts +6 -0
  194. package/dist/packages/source-store/lib/addresses.js +91 -0
  195. package/dist/packages/source-store/lib/connection.d.ts +15 -0
  196. package/dist/packages/source-store/lib/connection.js +135 -0
  197. package/dist/packages/source-store/lib/content-types.d.ts +16 -0
  198. package/dist/packages/source-store/lib/content-types.js +75 -0
  199. package/dist/packages/source-store/lib/data-urls.d.ts +10 -0
  200. package/dist/packages/source-store/lib/data-urls.js +85 -0
  201. package/dist/packages/source-store/lib/deadline.d.ts +18 -0
  202. package/dist/packages/source-store/lib/deadline.js +44 -0
  203. package/dist/packages/source-store/lib/exchange.d.ts +29 -0
  204. package/dist/packages/source-store/lib/exchange.js +112 -0
  205. package/dist/packages/source-store/lib/integrity.d.ts +19 -0
  206. package/dist/packages/source-store/lib/integrity.js +86 -0
  207. package/dist/packages/source-store/lib/outcomes.d.ts +12 -0
  208. package/dist/packages/source-store/lib/outcomes.js +22 -0
  209. package/dist/packages/source-store/lib/proxy.d.ts +11 -0
  210. package/dist/packages/source-store/lib/proxy.js +53 -0
  211. package/dist/packages/source-store/lib/session.d.ts +12 -0
  212. package/dist/packages/source-store/lib/session.js +139 -0
  213. package/dist/packages/source-store/lib/urls.d.ts +6 -0
  214. package/dist/packages/source-store/lib/urls.js +15 -0
  215. package/dist/packages/source-store/resolve.d.ts +59 -0
  216. package/dist/packages/source-store/resolve.js +45 -0
  217. package/dist/scan.d.ts +65 -0
  218. package/dist/scan.js +592 -0
  219. package/package.json +95 -4
  220. package/schema/config.schema.json +133 -0
  221. package/schema/report.schema.json +1905 -0
@@ -0,0 +1,1612 @@
1
+ // Writes (§3 Mechanisms): the native members a node writes, by which mechanism, and what the syntax says about the new
2
+ // value. Whether a write is a platform mutation, and its effect, is for the catalog to decide (`effect.ts`).
3
+ //
4
+ // - `assign`: `h.m = v`, `h["m"] = v`, a bare global `m = v`, and `Reflect.set(h, "m", v)`, whose receiver, when it is
5
+ // given, is the object written: `Reflect.set(h, "m", v, r)` is a [[Set]] of r through h's chain
6
+ // - `define-property`, `reflect-define` and `define-properties`: `Object.defineProperty(h, "m", d)`,
7
+ // `Reflect.defineProperty(h, "m", d)` and `Object.defineProperties(h, { m: d, … })`. `h.__defineGetter__("m", g)` and
8
+ // `h.__defineSetter__("m", s)` are a `define-property` of an enumerable, configurable accessor. A descriptor keeps the
9
+ // parts the syntax doesn't show, for the outcome to weigh (§3 Undecidable outcomes).
10
+ // - `bulk-assign`: `Object.assign(h, { m: v, … }, …)`. A source's own `__proto__` key calls h's `__proto__` setter, as
11
+ // `h.__proto__ = p` does, so it is a `set-prototype`.
12
+ // - `delete`: `delete h.m`, `Reflect.deleteProperty(h, "m")`, and an unqualified `delete m` of a global (§5 post-pass 4)
13
+ // - `set-prototype`: `Object.setPrototypeOf(h, p)`, `Reflect.setPrototypeOf(h, p)`, and `h.__proto__ = p` (or
14
+ // `Reflect.set(h, "__proto__", p)`), which calls `Object.prototype.__proto__`'s setter
15
+ //
16
+ // `h` is an expression that denotes exactly one native holder (§5 Global resolution). A key is a name, or a key that
17
+ // folds to a constant (§5 Resolver capabilities 1): `h["add" + "EventListener"]`, `h[method]` with a `const method`,
18
+ // `h[0]`, `h[Symbol.iterator]`, converted as ToPropertyKey converts it (§4 Property keys). The native functions a call
19
+ // mechanism uses are recognized through the same global resolution: `window.Object.defineProperty(…)` is one too. Each
20
+ // write keeps the folds its holder, key and descriptor come from.
21
+ import { calleeExpression, setsLiteralPrototype, walkTree } from "./ast.js";
22
+ import { inDocumentOrder, instanceOf, mergedFolds, opaque, sameWindow, truthiness } from "./global-object.js";
23
+ import { literalKey, memberName, patternName, toPropertyKey } from "./keys.js";
24
+ import { boundNames } from "./scope.js";
25
+ /** The most candidates a site's candidate set gives findings for (§5 Candidate sets, ADR 0019). */
26
+ export const maxCandidates = 4;
27
+ /**
28
+ * The member of a wildcard write (§5 Partially resolved writes): a key the analyzer can't fold. It names no member, so
29
+ * each member the wildcard matches takes its place when the outcome is judged (`effect.ts`).
30
+ */
31
+ export const unknownMember = Symbol('unknown member');
32
+ /** The native functions that write a member, by catalog name, with their mechanism. */
33
+ const nativeWriters = {
34
+ 'Object.defineProperty': 'define-property',
35
+ 'Reflect.defineProperty': 'reflect-define',
36
+ 'Object.defineProperties': 'define-properties',
37
+ 'Object.assign': 'bulk-assign',
38
+ 'Object.setPrototypeOf': 'set-prototype',
39
+ 'Reflect.setPrototypeOf': 'set-prototype',
40
+ 'Reflect.set': 'assign',
41
+ 'Reflect.deleteProperty': 'delete',
42
+ };
43
+ /** The legacy methods that define an accessor on their `this` (§3), with the accessor field each gives. */
44
+ const accessorDefiners = { __defineGetter__: 'get', __defineSetter__: 'set' };
45
+ /** The member names of the native writer functions. */
46
+ const writerMembers = new Set(Object.keys(nativeWriters).map((name) => name.slice(name.indexOf('.') + 1)));
47
+ /**
48
+ * The native members `node` writes directly, in source order. When a fold in the key, holder or descriptor of the write
49
+ * passed the `fold-length` cap, and its holder can be the global object or a native holder, it goes to `budget`, whether
50
+ * or not the write resolves (Principle 5, §13): the value written, and a call's native function, are no part of it.
51
+ */
52
+ export function writesAt(node, at, context) {
53
+ return sitesAt(node, at, context).writes;
54
+ }
55
+ /**
56
+ * The native members `node` writes directly, as `writesAt` gives them, and the write sites in it whose member folds
57
+ * but whose holder may be an object the analyzer can't resolve (§5 Partially resolved writes).
58
+ */
59
+ export function sitesAt(node, where, context) {
60
+ // Whether a holder written can be a native holder: one alternative of it is enough (Principle 3).
61
+ let native = false;
62
+ const cuts = [];
63
+ const run = (choice, wildKeys) => {
64
+ const open = [];
65
+ const wild = [];
66
+ const splits = new Map();
67
+ const chooser = { choice, wildKeys, splits };
68
+ const writes = writesOf(node, where, context, (reached) => (native ||= reached), open, cuts, wild, chooser);
69
+ return { writes, open, wild, splits };
70
+ };
71
+ let cut = false;
72
+ const { value: sites, capped } = context.resolution.tracked(() => {
73
+ const first = run(new Map(), new Set());
74
+ if (first.splits.size === 0)
75
+ return { ...first, wide: [] };
76
+ return candidateSites(first, run, context.caps.candidateRuns, () => (cut = true));
77
+ });
78
+ if (cut)
79
+ context.candidatesCut(node, native);
80
+ if (native && capped.length > 0)
81
+ context.budget(capped);
82
+ if (cuts.length > 0)
83
+ context.descriptorsCut([...new Set(cuts)], native);
84
+ return sites;
85
+ }
86
+ /**
87
+ * Every run of `writesOf` that decides each branch it meets, one per combination of alternatives, in source order.
88
+ * Undefined past `maxRuns` runs (the `candidateRuns` cap).
89
+ */
90
+ function enumerated(first, run, wildKeys, maxRuns) {
91
+ const done = [];
92
+ const stack = [[new Map(), first]];
93
+ let runs = 0;
94
+ while (stack.length > 0) {
95
+ const [choice, given] = stack.pop();
96
+ if (++runs > maxRuns)
97
+ return undefined;
98
+ const result = given ?? run(choice, wildKeys);
99
+ const fresh = [...result.splits].find(([node]) => !choice.has(node));
100
+ if (fresh === undefined) {
101
+ done.push({ run: result, choice });
102
+ continue;
103
+ }
104
+ const [node, split] = fresh;
105
+ const alternatives = split.kind === 'holder'
106
+ ? split.holders.map((reached) => ({ kind: 'holder', reached }))
107
+ : [
108
+ ...split.keys.map((key) => ({ kind: 'key', key, unresolved: split.unresolved, tested: split.tested })),
109
+ ...split.prefixes.map((prefix) => ({ kind: 'prefix', prefix, unresolved: split.unresolved, tested: split.tested })),
110
+ ];
111
+ for (const chosen of alternatives.reverse())
112
+ stack.push([new Map([...choice, [node, chosen]]), undefined]);
113
+ }
114
+ return done;
115
+ }
116
+ /** The name of the target a write names, before the catalog decides its outcome. */
117
+ export function candidateName(write) {
118
+ const { change } = write;
119
+ if (change.kind === 'prototype')
120
+ return `${write.holder}.[[Prototype]]`;
121
+ const { pattern } = write;
122
+ if (pattern !== undefined)
123
+ return patternName(write.holder, pattern);
124
+ return change.member === unknownMember ? `${write.holder}.*` : memberName(write.holder, change.member);
125
+ }
126
+ /** The member a write names, as a candidate: a key, or the prototype. */
127
+ const candidateMember = (write) => write.change.kind === 'prototype' ? '[[Prototype]]' : write.change.member;
128
+ /**
129
+ * The write sites at a node whose holder or key has several candidates (§5 Candidate sets, ADR 0019): every
130
+ * combination of alternatives is a run of `writesOf`, and each site's candidate set is the targets those runs give it,
131
+ * the cross product of its holders and members, capped on distinct targets:
132
+ * - up to `maxCandidates` targets give one write each, which carries its candidate set
133
+ * - more, with one member that folds, are a `WideSite`, an `unresolved-holder` limitation at most (§5)
134
+ * - more, with several members and at most `maxCandidates` holders, give a wildcard per holder, its matched set
135
+ * narrowed to the members; with more holders, nothing
136
+ * - wildcard writes, whose key doesn't fold, on up to `maxCandidates` holders give one each, and on more a `WideSite`
137
+ * Past `maxRuns` runs (the `candidateRuns` cap), the sites the runs decide give nothing, and `cut` hears it.
138
+ */
139
+ function candidateSites(first, run, maxRuns, cut) {
140
+ const complete = enumerated(first, run, new Set(), maxRuns);
141
+ if (complete === undefined) {
142
+ cut();
143
+ return { writes: [], open: [], wild: [], wide: [] };
144
+ }
145
+ const sites = new Map();
146
+ const siteOf = (site) => {
147
+ let found = sites.get(site);
148
+ if (found === undefined)
149
+ sites.set(site, (found = { named: [], wild: [], open: [] }));
150
+ return found;
151
+ };
152
+ for (const { run: each, choice } of complete) {
153
+ for (const write of each.writes)
154
+ siteOf(write.site).named.push({ write, choice });
155
+ for (const write of each.wild)
156
+ siteOf(write.site).wild.push({ write, choice });
157
+ for (const write of each.open) {
158
+ const { open } = siteOf(write.site);
159
+ if (!open.some((other) => other.change.member === write.change.member))
160
+ open.push(write);
161
+ }
162
+ }
163
+ /**
164
+ * The branch a candidate's holder or member comes from: the first branch of its kind whose choice gives it. A pattern
165
+ * wildcard's member comes from the branch that gives its prefix.
166
+ */
167
+ const branchOf = (choice, holder, member, prefix) => {
168
+ for (const [node, chosen] of choice) {
169
+ if (holder !== undefined && chosen.kind === 'holder' && chosen.reached.holder === holder)
170
+ return node;
171
+ if (member !== undefined && chosen.kind === 'key' && chosen.key.member === member)
172
+ return node;
173
+ if (prefix !== undefined && chosen.kind === 'prefix' && chosen.prefix === prefix)
174
+ return node;
175
+ }
176
+ return undefined;
177
+ };
178
+ /**
179
+ * The candidates of a site, one per target, the first run's that gives it. The `<holder>.*` of several prefixes some
180
+ * catalog member starts with is one target, narrowed to the members that start with any of them (Principle 3).
181
+ */
182
+ const byTarget = (candidates) => {
183
+ const out = new Map();
184
+ for (const candidate of candidates) {
185
+ const name = candidateName(candidate.write);
186
+ const first = out.get(name);
187
+ if (first === undefined)
188
+ out.set(name, candidate);
189
+ else {
190
+ const prefixes = first.write.prefixes;
191
+ const more = candidate.write.prefixes;
192
+ if (prefixes === undefined || more === undefined)
193
+ continue;
194
+ const merged = [...new Set([...prefixes, ...more])];
195
+ if (merged.length > prefixes.length)
196
+ out.set(name, { ...first, write: { ...first.write, prefixes: merged } });
197
+ }
198
+ }
199
+ return out;
200
+ };
201
+ /** The prefix of a candidate that is a pattern wildcard (§5 Pattern wildcards). */
202
+ const patternOf = (write) => write.pattern;
203
+ /**
204
+ * The prefix a candidate's member comes from: a pattern wildcard's, or the first of a `<holder>.*` narrowed to the
205
+ * members that start with its prefixes (§5 Pattern wildcards).
206
+ */
207
+ const prefixOf = (write) => patternOf(write) ?? write.prefixes?.[0];
208
+ /** The key alternative a candidate's member comes from, if any. */
209
+ const keyChosen = (choice, write) => {
210
+ const member = candidateMember(write);
211
+ const prefix = prefixOf(write);
212
+ return [...choice.values()].find((chosen) => (chosen.kind === 'key' && prefix === undefined && chosen.key.member === member) ||
213
+ (chosen.kind === 'prefix' && chosen.prefix === prefix));
214
+ };
215
+ /** Whether a candidate's member comes from a key that also has alternatives the analyzer can't resolve. */
216
+ const unresolvedKey = (choice, write) => keyChosen(choice, write)?.unresolved === true;
217
+ /**
218
+ * Whether a candidate's member comes from a key that lost a heap-read or once-flag boolean, `null` or `undefined` an
219
+ * undecided test gave back.
220
+ */
221
+ const testedKey = (choice, write) => keyChosen(choice, write)?.tested === true;
222
+ const together = (...groups) => {
223
+ const candidates = groups.flatMap((group) => [...group.values()]);
224
+ const keys = new Set();
225
+ const prefixes = new Set();
226
+ for (const { write } of candidates) {
227
+ const pattern = patternOf(write);
228
+ const member = candidateMember(write);
229
+ if (pattern !== undefined)
230
+ prefixes.add(pattern);
231
+ else if (member !== unknownMember)
232
+ keys.add(member);
233
+ for (const prefix of write.prefixes ?? [])
234
+ prefixes.add(prefix);
235
+ }
236
+ return { targets: groups.flatMap((group) => [...group.keys()]), keys: [...keys], prefixes: [...prefixes] };
237
+ };
238
+ /**
239
+ * The candidates with their candidate set, when the site has more than one, or when a member comes from a key with
240
+ * alternatives the analyzer can't resolve, which keeps `member-alternatives` even alone, as a
241
+ * configurable prefix does.
242
+ */
243
+ const tagged = (targets, site, members) => {
244
+ const candidates = [...targets.values()];
245
+ const holders = [...new Set(candidates.map(({ write }) => write.holder))];
246
+ const keys = members ?? site.keys;
247
+ const prefixes = members === undefined ? site.prefixes : [];
248
+ const mixed = members === undefined && candidates.some(({ write, choice }) => unresolvedKey(choice, write) || testedKey(choice, write));
249
+ if (site.targets.length < 2 && !mixed)
250
+ return candidates.map(({ write }) => write);
251
+ return candidates.map(({ write, choice }) => {
252
+ const holderNode = holders.length > 1 ? branchOf(choice, write.holder) : undefined;
253
+ const member = candidateMember(write);
254
+ const prefix = prefixOf(write);
255
+ const unresolved = members === undefined && unresolvedKey(choice, write);
256
+ const tested = members === undefined && testedKey(choice, write);
257
+ const memberNode = members === undefined && (keys.length + prefixes.length > 1 || unresolved || tested)
258
+ ? prefix !== undefined
259
+ ? branchOf(choice, undefined, undefined, prefix)
260
+ : member !== unknownMember
261
+ ? branchOf(choice, undefined, member)
262
+ : undefined
263
+ : undefined;
264
+ const alternatives = {
265
+ ...(holderNode === undefined ? {} : { holders: { node: holderNode, names: holders } }),
266
+ ...(memberNode === undefined
267
+ ? {}
268
+ : {
269
+ members: {
270
+ node: memberNode,
271
+ keys: [...keys],
272
+ ...(prefixes.length === 0 ? {} : { prefixes }),
273
+ ...(unresolved ? { unresolved: true } : {}),
274
+ ...(tested ? { tested: true } : {}),
275
+ },
276
+ }),
277
+ targets: site.targets,
278
+ };
279
+ return { ...write, alternatives };
280
+ });
281
+ };
282
+ const writes = [];
283
+ const open = [];
284
+ const wild = [];
285
+ const wide = [];
286
+ // The sites whose candidates are taken as a wildcard per holder, with the members each is narrowed to, and the
287
+ // prefixes some catalog member starts with whose members it is narrowed to as well.
288
+ const narrowed = new Map();
289
+ for (const [site, { named, wild: wilds, open: opens }] of sites) {
290
+ let keepOpen = true;
291
+ const namedTargets = byTarget(named);
292
+ const wildTargets = byTarget(wilds);
293
+ if (wilds.some(({ write }) => write.pattern !== undefined || write.prefixes !== undefined) &&
294
+ namedTargets.size + [...wildTargets.values()].reduce((sum, { write }) => sum + (write.prefixes?.length ?? 1), 0) > maxCandidates) {
295
+ // More than 4 constants and prefixes follow the wildcard rule, narrowed to the candidates (§5 Candidate sets):
296
+ // to the constants and the members that start with a prefix, which a pattern's prefix narrows to none.
297
+ const holders = new Set([...named, ...wilds].map(({ write }) => write.holder));
298
+ const members = [...new Set(named.map(({ write }) => candidateMember(write)))];
299
+ const prefixes = [...new Set(wilds.flatMap(({ write }) => write.prefixes ?? []))];
300
+ if (members.length + prefixes.length > 0 && holders.size <= maxCandidates)
301
+ narrowed.set(site, { members, prefixes });
302
+ continue;
303
+ }
304
+ // A key whose alternatives are constants and prefixes gives both: one candidate set (§5 Pattern wildcards).
305
+ const candidateSet = together(...(namedTargets.size <= maxCandidates ? [namedTargets] : []), ...(wildTargets.size <= maxCandidates ? [wildTargets] : []));
306
+ if (named.length > 0) {
307
+ const targets = namedTargets;
308
+ const holders = new Set(named.map(({ write }) => write.holder));
309
+ const members = [...new Set(named.map(({ write }) => candidateMember(write)))];
310
+ if (targets.size <= maxCandidates)
311
+ writes.push(...tagged(targets, candidateSet));
312
+ else if (members.length === 1) {
313
+ // One member on more holders than the cap: an `unresolved-holder` limitation at most (§5).
314
+ const [first] = opens;
315
+ if (first !== undefined)
316
+ wide.push({ site, open: first, writes: [...targets.values()].map(({ write }) => write) });
317
+ keepOpen = false;
318
+ }
319
+ else {
320
+ if (holders.size <= maxCandidates)
321
+ narrowed.set(site, { members, prefixes: [] });
322
+ keepOpen = false;
323
+ }
324
+ }
325
+ if (wilds.length > 0) {
326
+ const targets = wildTargets;
327
+ if (targets.size <= maxCandidates)
328
+ wild.push(...tagged(targets, candidateSet));
329
+ else
330
+ wide.push({ site, wild: [...targets.values()].map(({ write }) => write) });
331
+ }
332
+ if (keepOpen)
333
+ open.push(...opens);
334
+ }
335
+ if (narrowed.size > 0) {
336
+ // The same runs, with each key that has several candidates taken as unknown, give the wildcards.
337
+ const keys = new Set(complete.flatMap(({ choice }) => [...choice].flatMap(([node, chosen]) => (chosen.kind === 'holder' ? [] : [node]))));
338
+ const wildRuns = enumerated(run(new Map(), keys), run, keys, maxRuns);
339
+ if (wildRuns === undefined)
340
+ cut();
341
+ const found = new Map();
342
+ for (const { run: each, choice } of wildRuns ?? []) {
343
+ for (const write of each.wild) {
344
+ if (!narrowed.has(write.site))
345
+ continue;
346
+ const list = found.get(write.site);
347
+ if (list === undefined)
348
+ found.set(write.site, [{ write, choice }]);
349
+ else
350
+ list.push({ write, choice });
351
+ }
352
+ }
353
+ for (const [site, candidates] of found) {
354
+ const { members, prefixes } = narrowed.get(site);
355
+ const narrowing = prefixes.length === 0 ? { members } : { members, prefixes };
356
+ const targets = byTarget(candidates.map(({ write, choice }) => ({ write: { ...write, ...narrowing }, choice })));
357
+ wild.push(...tagged(targets, together(targets), members));
358
+ }
359
+ }
360
+ return { writes, open, wild, wide };
361
+ }
362
+ /**
363
+ * The writes `node` makes. `reachesNative` hears whether each holder a write is made on (not a value, nor a call's
364
+ * native function) can be a native holder.
365
+ */
366
+ function writesOf(node, where, context, reachesNative, open, cuts, wild, chooser) {
367
+ const { scope, global } = context;
368
+ // Where the write's parts are evaluated: at the write, or where a heap field's value is (`evaluatedAt`).
369
+ let at = where;
370
+ const evaluatedAt = (position, run) => {
371
+ const saved = at;
372
+ at = position;
373
+ try {
374
+ return run();
375
+ }
376
+ finally {
377
+ at = saved;
378
+ }
379
+ };
380
+ /**
381
+ * An evaluation that is no part of the write's key, holder or descriptor: the value written, or the native function a
382
+ * call uses. The folds it finds too long are only counted.
383
+ */
384
+ let apartDepth = 0;
385
+ const apart = (run) => {
386
+ apartDepth++;
387
+ try {
388
+ return context.resolution.tracked(run, true).value;
389
+ }
390
+ finally {
391
+ apartDepth--;
392
+ }
393
+ };
394
+ /**
395
+ * The one native holder an expression denotes, with its leaves and folds. Several are a candidate set (§5): the
396
+ * alternative the run's choice takes, or else the first, the branch going to the chooser. A value or a call's native
397
+ * function (`apart`) has none. `single` takes several as none, as for the object a `Reflect.set` looks its member up
398
+ * on when it writes another.
399
+ */
400
+ const holderOf = (expression, unresolved, single = false) => {
401
+ const values = context.resolution.evaluate(expression, at);
402
+ const holders = values.filter((value) => value.kind === 'holder');
403
+ if (apartDepth === 0)
404
+ reachesNative(holders.length > 0);
405
+ // An alternative the analyzer can't resolve may be any object (§5 Partially resolved writes).
406
+ const unknown = values.some(opaque);
407
+ if (unresolved !== undefined && unknown)
408
+ unresolved();
409
+ const reached = ({ holder, leaves, folds = [] }) => ({ holder, leaves: inDocumentOrder(leaves), folds });
410
+ if (holders.length === 1)
411
+ return reached(holders[0]);
412
+ if (holders.length === 0 || apartDepth > 0 || single)
413
+ return undefined;
414
+ const chosen = chooser.choice.get(expression);
415
+ if (chosen?.kind === 'holder') {
416
+ // More holders than the cap with one member are an open write site too (§5 Candidate sets).
417
+ if (unresolved !== undefined && !unknown && holders.length > maxCandidates)
418
+ unresolved();
419
+ return chosen.reached;
420
+ }
421
+ if (!chooser.splits.has(expression))
422
+ chooser.splits.set(expression, { kind: 'holder', holders: holders.map(reached) });
423
+ return reached(holders[0]);
424
+ };
425
+ /**
426
+ * A key that folds to a constant: a literal, a template literal, a `+`, a `const`, converted as ECMA-262
427
+ * ToPropertyKey converts it (§4 Property keys): `0` and `"0"` are the same key. Several constants are a candidate
428
+ * set (§5), taken as `holderOf` takes several holders; a value or a call's native function (`apart`) takes only one.
429
+ * A key whose alternatives mix constants and values the analyzer can't resolve has its constants as candidates, even
430
+ * one alone, and the unknown part gives nothing.
431
+ */
432
+ const constantKey = (key) => {
433
+ const values = keyValues(key);
434
+ if (apartDepth > 0) {
435
+ const [value] = values;
436
+ if (values.length !== 1 || value?.kind !== 'constant')
437
+ return undefined;
438
+ return { member: toPropertyKey(value.value), folds: value.folds ?? [] };
439
+ }
440
+ if (chooser.wildKeys.has(key))
441
+ return undefined;
442
+ const chosen = keyChoice(key);
443
+ return chosen?.kind === 'key' ? chosen.key : undefined;
444
+ };
445
+ /**
446
+ * The alternative of a key a run takes: a constant (`constantKey`), or a constant prefix a string the analyzer can't
447
+ * fold whole starts with (§5 Pattern wildcards); undefined when the key has neither, or is no key at all (an object).
448
+ * Several are a candidate set, and so is one beside values the analyzer can't resolve, which give nothing.
449
+ */
450
+ const keyChoice = (key) => {
451
+ const values = keyValues(key);
452
+ const tested = testedKeys.has(key);
453
+ const keys = [];
454
+ const prefixes = [];
455
+ let unresolved = false;
456
+ for (const value of values) {
457
+ if (value.kind === 'unknown' && value.prefix !== undefined) {
458
+ if (!prefixes.includes(value.prefix))
459
+ prefixes.push(value.prefix);
460
+ continue;
461
+ }
462
+ if (opaque(value)) {
463
+ unresolved = true;
464
+ continue;
465
+ }
466
+ if (value.kind !== 'constant')
467
+ return undefined;
468
+ const member = toPropertyKey(value.value);
469
+ if (!keys.some((each) => each.member === member))
470
+ keys.push({ member, folds: value.folds ?? [] });
471
+ }
472
+ // With no constant, a key the analyzer can't resolve is a wildcard's (§5 Partially resolved writes).
473
+ if (keys.length + prefixes.length === 0)
474
+ return undefined;
475
+ const first = () => keys.length > 0
476
+ ? { kind: 'key', key: keys[0], unresolved, tested }
477
+ : { kind: 'prefix', prefix: prefixes[0], unresolved, tested };
478
+ if (keys.length + prefixes.length === 1 && !unresolved && !tested)
479
+ return first();
480
+ const chosen = chooser.choice.get(key);
481
+ if (chosen?.kind === 'key' || chosen?.kind === 'prefix')
482
+ return chosen;
483
+ if (!chooser.splits.has(key))
484
+ chooser.splits.set(key, { kind: 'key', keys, prefixes, unresolved, tested });
485
+ return first();
486
+ };
487
+ /**
488
+ * The values a key can have, evaluated once where the write is, so that a key read again to tell whether it is
489
+ * unknown counts no cap twice (§18).
490
+ */
491
+ let lastKey;
492
+ // The keys that lost such a value, whose other constants keep `member-alternatives`.
493
+ const testedKeys = new Set();
494
+ const keyValues = (key) => {
495
+ if (lastKey?.node === key && lastKey.at === at)
496
+ return lastKey.values;
497
+ // A heap-read or once-flag boolean, `null` or `undefined` an undecided test gives back is never a key (§5 Candidate
498
+ // sets).
499
+ const evaluated = context.resolution.evaluate(key, at);
500
+ const values = evaluated.filter((value) => value.kind !== 'constant' || value.tested !== true);
501
+ if (values.length < evaluated.length)
502
+ testedKeys.add(key);
503
+ lastKey = { node: key, at, values };
504
+ return values;
505
+ };
506
+ /** Whether a key folds to nothing the analyzer knows: every value it can have is unknown (§5 Partially resolved writes). */
507
+ const unknownKey = (key) => {
508
+ if (chooser.wildKeys.has(key))
509
+ return true;
510
+ const chosen = keyChoice(key);
511
+ if (chosen !== undefined)
512
+ return chosen.kind === 'prefix';
513
+ const values = keyValues(key);
514
+ return values.length > 0 && values.every(opaque);
515
+ };
516
+ /** A wildcard write of `holder`'s member `key`, as the write `site` makes it, with what the syntax shows of it. */
517
+ const wildWrite = (site, reached, object, key, mechanism, change, lookup, extra = {}) => {
518
+ const { leaves = reached.leaves, folds = [], ...rest } = extra;
519
+ // A key that folds to a constant prefix (§5 Pattern wildcards): a pattern wildcard when no catalog member of the
520
+ // holder or its chain starts with it, otherwise `<holder>.*` narrowed to the members that do.
521
+ const chosen = chooser.wildKeys.has(key) ? undefined : keyChoice(key);
522
+ const prefixed = chosen?.kind !== 'prefix'
523
+ ? {}
524
+ : {
525
+ ...(context.patternPrefix(reached.holder, chosen.prefix) ? { pattern: chosen.prefix } : { prefixes: [chosen.prefix] }),
526
+ ...(chosen.unresolved ? { configurable: true } : {}),
527
+ };
528
+ wild.push({
529
+ site,
530
+ holder: reached.holder,
531
+ leaves,
532
+ realmInstance: instanceOf(reached.leaves),
533
+ mechanism,
534
+ change,
535
+ folds: [...folds, ...reached.folds],
536
+ parts: { holder: reached.folds, member: [] },
537
+ lookup,
538
+ key,
539
+ object,
540
+ ...prefixed,
541
+ ...rest,
542
+ });
543
+ };
544
+ /** The member a member expression names: `h.m`, or `h[k]` with a key that folds to a constant. */
545
+ const memberKey = (member) => {
546
+ const property = member['property'];
547
+ if (member['computed'] === true)
548
+ return constantKey(property);
549
+ return property.type === 'Identifier' ? { member: property['name'], folds: [] } : undefined;
550
+ };
551
+ /** A property's key in an object literal: a name, a string or number literal, or a computed key that folds. */
552
+ const propertyKey = (property) => {
553
+ const key = property['key'];
554
+ if (property['computed'] === true)
555
+ return constantKey(key);
556
+ if (key.type === 'Identifier')
557
+ return { member: key['name'], folds: [] };
558
+ const member = literalKey(key);
559
+ return member === undefined ? undefined : { member, folds: [] };
560
+ };
561
+ /**
562
+ * The native member an assignment target or a `delete` operand denotes: a bare global, or a holder's member, with the
563
+ * leaves its holder was reached from.
564
+ */
565
+ const memberOf = (target, unresolved, wildcard) => {
566
+ if (target.type === 'Identifier') {
567
+ const leaf = bareGlobal(target, scope, context.instance);
568
+ if (leaf === undefined)
569
+ return undefined;
570
+ const lookup = { node: target, token: leaf.name };
571
+ const parts = { holder: [], member: [] };
572
+ return { holder: global, member: leaf.name, realmInstance: leaf.instance, leaves: [leaf], folds: [], parts, lookup };
573
+ }
574
+ if (target.type !== 'MemberExpression' || target['object'].type === 'Super')
575
+ return undefined;
576
+ const { value: key, capped } = context.resolution.tracked(() => memberKey(target));
577
+ const object = target['object'];
578
+ // A key the analyzer knows nothing of, on one native holder, may be a wildcard write (§5).
579
+ if (key === undefined && capped.length === 0 && wildcard !== undefined && target['computed'] === true) {
580
+ const property = target['property'];
581
+ if (!unknownKey(property))
582
+ return undefined;
583
+ const reached = holderOf(object);
584
+ if (reached !== undefined)
585
+ wildcard(object, property, reached);
586
+ return undefined;
587
+ }
588
+ // A key too long to fold still cuts the write when its holder is native (Principle 5).
589
+ const reached = key === undefined && capped.length === 0
590
+ ? undefined
591
+ : holderOf(object, key === undefined || unresolved === undefined ? undefined : () => unresolved(object, key));
592
+ if (reached === undefined || key === undefined)
593
+ return undefined;
594
+ const { holder, leaves } = reached;
595
+ const folds = [...reached.folds, ...key.folds];
596
+ // A write with no member, `h.__proto__ = p`, looks up its holder.
597
+ const lookup = key.member === '__proto__' ? holderLookup(object) : memberLookup(target, object);
598
+ const parts = { holder: reached.folds, member: key.folds };
599
+ return { holder, member: key.member, realmInstance: instanceOf(leaves), leaves, folds, parts, lookup };
600
+ };
601
+ /**
602
+ * The lookup of a member expression's write: its member token when the member is written at the site, as a name
603
+ * (`h.m`), a string (`h["m"]`) or a template with no substitutions. A computed member (`h[k]`) or a number
604
+ * (`h[0]`) has no token, and its holder is looked up.
605
+ */
606
+ const memberLookup = (member, object) => {
607
+ const property = member['property'];
608
+ if (member['computed'] !== true)
609
+ return { node: property, token: property['name'] };
610
+ const written = writtenString(property);
611
+ return written === undefined ? { node: object, token: null } : { node: property, token: written };
612
+ };
613
+ /**
614
+ * The lookup of a write at its holder argument (§10): its token is its name only when it is written as a free global
615
+ * (`window`) or a literal property name (`history` in `window.history`), never as a local binding.
616
+ */
617
+ const holderLookup = (holder) => {
618
+ const expression = holder.type === 'ChainExpression' ? holder['expression'] : holder;
619
+ if (expression.type === 'Identifier') {
620
+ return { node: holder, token: scope.resolve(expression) === 'local' ? null : expression['name'] };
621
+ }
622
+ if (expression.type !== 'MemberExpression')
623
+ return { node: holder, token: null };
624
+ const property = expression['property'];
625
+ const token = expression['computed'] === true ? writtenString(property) : property['name'];
626
+ return { node: holder, token: token ?? null };
627
+ };
628
+ /**
629
+ * The lookup of an object literal's property in a bulk mechanism: its key when written as a name or a string
630
+ * (`fetch: w`, `'fetch': w`, `['fetch']: w`, or a template with no substitutions, as `h['fetch']` is for a member).
631
+ * Any other key, a number included, has no token, and the holder argument is looked up.
632
+ */
633
+ const propertyLookup = (property, object) => {
634
+ const key = property['key'];
635
+ if (property['computed'] !== true && key.type === 'Identifier')
636
+ return { node: key, token: key['name'] };
637
+ const written = writtenString(key);
638
+ return written === undefined ? { node: object, token: null } : { node: key, token: written };
639
+ };
640
+ /**
641
+ * The lookup of a call's key argument (`defineProperty(h, 'fetch', d)`, `Reflect.set(h, 'fetch', v)`): the key when
642
+ * it is written at the site as a string or a template with no substitutions. Any other key has no token, and the
643
+ * holder argument `object` is looked up.
644
+ */
645
+ const keyLookup = (key, object) => {
646
+ const written = writtenString(key);
647
+ return written === undefined ? { node: object, token: null } : { node: key, token: written };
648
+ };
649
+ /**
650
+ * Whether `value` reads the slot being written: `h.m` of the same holder and member, or the bare global `m`. The same
651
+ * member of two windows is two slots, so the read must reach the member through the same window expression as the
652
+ * write (§3 Undecidable outcomes), with nothing in `evaluated`, the expressions the write evaluates, changing which
653
+ * window that is.
654
+ */
655
+ const readsSlot = (value, written, evaluated, depth = 0) => {
656
+ const read = value.type === 'ChainExpression' ? value['expression'] : value;
657
+ const slot = read.type === 'Identifier' || read.type === 'MemberExpression' ? apart(() => memberOf(read)) : undefined;
658
+ if (slot === undefined) {
659
+ // A field of objects the heap holds reads the slot when every value written to it does (§5 Resolver
660
+ // capabilities 5): `s.orig = window.fetch; window.fetch = s.orig` writes back.
661
+ if (read.type !== 'MemberExpression')
662
+ return false;
663
+ if (depth >= context.caps.heapDepth) {
664
+ context.capHit('heapDepth', read);
665
+ return false;
666
+ }
667
+ const sources = apart(() => context.resolution.fieldSources(read, at));
668
+ return (sources !== undefined &&
669
+ sources.every((source) => evaluatedAt(source.at, () => readsSlot(source.node, written, [...evaluated, source.node], depth + 1))));
670
+ }
671
+ return slot.holder === written.holder && slot.member === written.member && sameWindow(slot.leaves, written.leaves, evaluated);
672
+ };
673
+ /** Whether a value that reads the slot reads it through a heap field, not as `h.m` or the bare global (`readsSlot`). */
674
+ const throughHeap = (value) => {
675
+ const read = value.type === 'ChainExpression' ? value['expression'] : value;
676
+ return read.type === 'MemberExpression' && apart(() => memberOf(read)) === undefined;
677
+ };
678
+ /**
679
+ * What the syntax shows of the value a [[Set]] of `member` writes, for the one member whose outcome follows it
680
+ * (`opener`, §3): whether it is `null`. `value` is the expression written, `getter` when a getter gives the value, or
681
+ * `primitive` when the write computes a new primitive or writes a missing argument's `undefined`, neither ever
682
+ * `null`. A write-back of the slot may be `null` whatever the resolver finds, since the getter may return `null`.
683
+ */
684
+ const writtenValue = (member, value, site, same) => {
685
+ if (member !== 'opener')
686
+ return {};
687
+ if (value === 'primitive')
688
+ return { value: { isNull: false, site } };
689
+ if (value === 'getter' || same)
690
+ return { value: { isNull: 'unknown', site } };
691
+ const values = apart(() => context.resolution.evaluate(value, at));
692
+ const isNull = (each) => each.kind === 'constant' && each.value === null;
693
+ const known = values.length > 0 && values.every((each) => !opaque(each));
694
+ const decided = known && values.every(isNull) ? true : known && !values.some(isNull) ? false : 'unknown';
695
+ return { value: { isNull: decided, site } };
696
+ };
697
+ /** Whether `node` is plainly a primitive: every value it can have is a constant. */
698
+ const primitive = (node) => context.resolution.evaluate(node, at).every((value) => value.kind === 'constant');
699
+ /**
700
+ * The descriptors `node` gives, as far as the analyzer sees them (§3 Undecidable outcomes): for each object the heap
701
+ * holds that it can be (§5 Resolver capabilities 5), its parts, with every field that may be absent present and
702
+ * absent in turn, and the parts the analyzer can't see. Anything else is one descriptor with no field seen.
703
+ * Undefined when ECMA-262 ToPropertyDescriptor refuses every one with a TypeError, so nothing is written (Principle
704
+ * 1): a primitive, a literal with both accessor and data fields, or one whose `get` or `set` is a primitive other
705
+ * than `undefined`. `slot` is the slot defined, when its key folds. The folds the fields' keys and attributes come
706
+ * from go to `folds`.
707
+ */
708
+ const descriptorOf = (node, slot, evaluated, folds) => {
709
+ const { objects, primitive: mayBePrimitive, unseen } = context.resolution.descriptorValues(node, at);
710
+ if (objects.length === 0)
711
+ return unseen ? { descriptor: unseenDescriptor(node) } : undefined;
712
+ // Every value the descriptor can be is an alternative (Principle 3): each object the heap holds, a value the
713
+ // analyzer can't see, which is one unseen descriptor, and a primitive, which ToPropertyDescriptor refuses.
714
+ const several = objects.length > 1 || unseen || mayBePrimitive;
715
+ const alternatives = [];
716
+ const found = [];
717
+ for (const { node: object, parts } of objects) {
718
+ const shown = presentOrAbsent(parts, context.caps.descriptorAlternatives);
719
+ // Past the cap, the fields that may be absent are taken as parts the analyzer can't see (Principle 5).
720
+ if (shown === undefined && !cuts.includes(node))
721
+ cuts.push(node);
722
+ const each = shown ?? [parts.map((part) => (isMaybe(part) ? { kind: 'unseen', node: part.maybe } : part))];
723
+ const evidence = [
724
+ ...(several ? [object] : []),
725
+ ...(shown === undefined ? [] : parts.filter(isMaybe).map((part) => part.maybe)),
726
+ ];
727
+ for (const [alternative, split] of each.flatMap(byPrototype)) {
728
+ const descriptor = fromParts(alternative, slot, evaluated, found);
729
+ const all = split === undefined ? evidence : [...evidence, split];
730
+ alternatives.push(descriptor === undefined ? 'refused' : all.length === 0 ? descriptor : { ...descriptor, evidence: all });
731
+ }
732
+ }
733
+ if (unseen)
734
+ alternatives.push({ ...unseenDescriptor(node), evidence: [node] });
735
+ if (mayBePrimitive)
736
+ alternatives.push('refused');
737
+ const [descriptor] = alternatives.filter((each) => each !== 'refused');
738
+ if (descriptor === undefined)
739
+ return undefined;
740
+ folds.push(...mergedFolds([], found));
741
+ return alternatives.length === 1 ? { descriptor } : { descriptor, alternatives };
742
+ };
743
+ /**
744
+ * The parts of one descriptor per candidate of its `__proto__:` field, when the field has several candidates and every
745
+ * one is a catalog holder, each with the field as what makes it one (§3, §5 Candidate sets). Otherwise the parts
746
+ * as they are: a candidate set with any other value stays unseen.
747
+ */
748
+ const byPrototype = (parts) => {
749
+ const index = parts.findIndex((part) => part.kind === 'proto');
750
+ const proto = parts[index];
751
+ if (proto?.kind !== 'proto' || !proto.values.every((value) => value.kind === 'holder'))
752
+ return [[parts, undefined]];
753
+ const candidates = new Map();
754
+ for (const value of proto.values)
755
+ if (value.kind === 'holder' && !candidates.has(value.holder))
756
+ candidates.set(value.holder, value);
757
+ if (candidates.size < 2)
758
+ return [[parts.with(index, { ...proto, values: [...candidates.values()] }), undefined]];
759
+ return [...candidates.values()].map((value) => [parts.with(index, { ...proto, values: [value] }), proto.site]);
760
+ };
761
+ /** A descriptor as `parts` give it, or undefined when ToPropertyDescriptor refuses it (`descriptorOf`). */
762
+ const fromParts = (parts, slot, evaluated, folds) => {
763
+ let descriptor = unseenDescriptor();
764
+ // The fields whose attribute can't be decided, the folds of the fields read, and the accessor fields that are plainly
765
+ // not callable, since the last spread.
766
+ let undecided = new Map();
767
+ let found = [];
768
+ let uncallable = new Set();
769
+ // The fields the literal has. A later spread or key that doesn't fold may give a field another value, but never
770
+ // takes it away.
771
+ const has = new Set();
772
+ // The folds that show what a `__proto__` field's value is, which no later part undoes, and the fields that value
773
+ // supplies where the literal has none of its own.
774
+ const prototypeFolds = [];
775
+ let prototypeFields = { functions: new Set(), throwing: new Set() };
776
+ // The getter a field may have (ECMA-262 PropertyDefinitionEvaluation): the last `get` of it since its last value,
777
+ // or any `get` whose key doesn't fold. A setter after it joins it, and a setter alone leaves the field `undefined`.
778
+ const getters = new Map();
779
+ let anyGetter;
780
+ for (const part of parts) {
781
+ if (part.kind === 'unseen') {
782
+ // A spread, a key that doesn't fold, or an object's unseen base, may give any field, over the fields before it.
783
+ descriptor = unseenDescriptor(...descriptor.unseen, part.node);
784
+ undecided = new Map();
785
+ found = [];
786
+ uncallable = new Set();
787
+ if (part.node.type === 'Property' && part.node['kind'] === 'get')
788
+ anyGetter = part.node;
789
+ continue;
790
+ }
791
+ // `__proto__: p` gives the literal a prototype, whose fields ToPropertyDescriptor reads where the literal has
792
+ // none of its own. `null`, or a primitive, which sets no prototype, gives none.
793
+ // A catalog holder is read through, against the unmutated catalog.
794
+ if (part.kind === 'proto') {
795
+ const proto = part.values;
796
+ const [only] = proto;
797
+ const inherited = proto.length === 1 && only?.kind === 'holder' ? context.descriptorFields(only.holder) : undefined;
798
+ if (proto.every((value) => value.kind === 'constant')) {
799
+ prototypeFolds.push(...proto.flatMap((value) => (value.kind === 'constant' ? (value.folds ?? []) : [])));
800
+ }
801
+ else if (inherited !== undefined) {
802
+ prototypeFields = inherited;
803
+ prototypeFolds.push(...(only?.kind === 'holder' ? (only.folds ?? []) : []));
804
+ }
805
+ else
806
+ descriptor.unseen.push(part.site);
807
+ continue;
808
+ }
809
+ const member = part.key;
810
+ // A symbol-keyed property is no field.
811
+ if (typeof member === 'symbol' || !descriptorFields.has(member))
812
+ continue;
813
+ has.add(member);
814
+ found.push(...part.folds);
815
+ undecided.delete(member);
816
+ const kind = part.accessor ?? 'init';
817
+ if (kind === 'get')
818
+ getters.set(member, part.site);
819
+ else if (kind === 'init')
820
+ getters.delete(member);
821
+ switch (member) {
822
+ case 'value': {
823
+ // A getter gives the value whatever it returns, and a setter alone gives `undefined`: a new value either way.
824
+ // A fresh object or function differs from every value a member holds already (§3 No-op or ineffective): every
825
+ // value the field's writes can give is one.
826
+ const init = kind === 'init' && part.sources.length > 0;
827
+ const same = init &&
828
+ slot !== undefined &&
829
+ part.sources.every((source) => evaluatedAt(source.at, () => readsSlot(source.node, slot, evaluated)));
830
+ descriptor.value = {
831
+ same,
832
+ fresh: init && part.values.length > 0 && part.values.every((each) => each.kind === 'object'),
833
+ ...(init ? { sources: part.sources } : {}),
834
+ // Read through a heap field, the value may be the native saved before an install (§3 Restores).
835
+ ...(same && part.sources.some((source) => throughHeap(source.node)) ? { throughHeap: true } : {}),
836
+ };
837
+ break;
838
+ }
839
+ case 'get':
840
+ case 'set': {
841
+ // An accessor field must be callable or `undefined`.
842
+ const values = kind === 'init' ? part.values : [];
843
+ const refused = values.length > 0 && values.every((each) => each.kind === 'constant' && each.value !== undefined);
844
+ if (refused)
845
+ uncallable.add(member);
846
+ else
847
+ uncallable.delete(member);
848
+ break;
849
+ }
850
+ default: {
851
+ const field = member;
852
+ if (kind !== 'init') {
853
+ // A getter gives the attribute whatever it returns. A setter with no getter gives it `undefined`, which
854
+ // ToBoolean makes false.
855
+ const getter = getters.get(member) ?? anyGetter;
856
+ descriptor[field] = getter === undefined ? false : 'unknown';
857
+ if (getter !== undefined)
858
+ undecided.set(member, getter);
859
+ break;
860
+ }
861
+ const values = part.values;
862
+ const attribute = truthiness(values);
863
+ descriptor[field] = attribute ?? 'unknown';
864
+ if (attribute === undefined)
865
+ undecided.set(member, part.site);
866
+ else {
867
+ found.push(...values.flatMap((each) => (each.kind === 'constant' || each.kind === 'holder' ? (each.folds ?? []) : [])));
868
+ }
869
+ }
870
+ }
871
+ }
872
+ // A field the prototype supplies as a Web IDL attribute throws when read, unless the literal has its own (Principle
873
+ // 1). A spread or a key that doesn't fold may give it one, so the descriptor then stays unseen.
874
+ for (const field of prototypeFields.throwing) {
875
+ if (has.has(field))
876
+ continue;
877
+ if (descriptor.unseen.length === 0)
878
+ return undefined;
879
+ has.add(field);
880
+ }
881
+ // The fields the prototype supplies as a function, which is callable and truthy. A spread or a key that doesn't fold
882
+ // may give such a field another value, but the literal still has it.
883
+ for (const field of prototypeFields.functions) {
884
+ if (has.has(field))
885
+ continue;
886
+ has.add(field);
887
+ if (descriptor.unseen.length > 0)
888
+ continue;
889
+ if (field === 'value')
890
+ descriptor.value = { same: false, fresh: false };
891
+ else if (field === 'writable' || field === 'enumerable' || field === 'configurable')
892
+ descriptor[field] = true;
893
+ }
894
+ // A literal with an accessor field is an accessor descriptor, whatever a later part gives the field.
895
+ descriptor.get = has.has('get');
896
+ descriptor.set = has.has('set');
897
+ const data = has.has('value') || has.has('writable');
898
+ if (((descriptor.get || descriptor.set) && data) || uncallable.size > 0)
899
+ return undefined;
900
+ descriptor.undecided = [...undecided.values()].sort((a, b) => a.start - b.start);
901
+ folds.push(...prototypeFolds, ...found);
902
+ return descriptor;
903
+ };
904
+ switch (node.type) {
905
+ case 'AssignmentExpression': {
906
+ const left = node['left'];
907
+ const operator = node['operator'];
908
+ const target = memberOf(left, (object, { member }) => {
909
+ // A `__proto__` write has no member: on an unknown object it gives nothing (§5).
910
+ if (member === '__proto__')
911
+ return;
912
+ const value = node['right'];
913
+ const keepsPresent = operator === '||=' || operator === '??=';
914
+ const change = { kind: 'set', member, same: false };
915
+ open.push({ site: node, holder: object, mechanism: 'assign', change, value, target: left, keepsPresent });
916
+ }, (object, key, reached) => {
917
+ const value = node['right'];
918
+ const keepsPresent = operator === '||=' || operator === '??=';
919
+ const sources = writeBackOperators.has(operator) ? { sources: [{ node: value, at }] } : {};
920
+ const change = { kind: 'set', member: unknownMember, same: false, ...sources };
921
+ const lookup = memberLookup(left, object);
922
+ wildWrite(node, reached, object, key, 'assign', change, lookup, { target: left, value, keepsPresent });
923
+ });
924
+ if (target === undefined)
925
+ return [];
926
+ const { holder, member, leaves, realmInstance, folds, parts, lookup } = target;
927
+ const value = node['right'];
928
+ // `h.__proto__ = p` calls `Object.prototype.__proto__`'s setter, which sets h's prototype (§3).
929
+ if (member === '__proto__') {
930
+ const change = prototypeChange(value, true, leaves, [node['left'], value]);
931
+ return [{ site: node, holder, leaves, realmInstance, mechanism: 'set-prototype', change, folds, parts, lookup }];
932
+ }
933
+ // `h.m += h.m` writes a new value, so only `=` and the logical assignments can write back what is there (§3).
934
+ const writesBack = writeBackOperators.has(node['operator']);
935
+ const same = writesBack && readsSlot(value, target, [node['left'], value]);
936
+ // Any other operator writes a new primitive, which is never `null`.
937
+ const written = writtenValue(member, writesBack ? value : 'primitive', value, same);
938
+ const heap = same && throughHeap(value) ? { throughHeap: true } : {};
939
+ const change = { kind: 'set', member, same, ...heap, ...written, ...(writesBack ? { sources: [{ node: value, at }] } : {}) };
940
+ return [{ site: node, holder, leaves, realmInstance, mechanism: 'assign', change, folds, parts, lookup }];
941
+ }
942
+ case 'UnaryExpression': {
943
+ if (node['operator'] !== 'delete')
944
+ return [];
945
+ let operand = node['argument'];
946
+ if (operand.type === 'ChainExpression')
947
+ operand = operand['expression'];
948
+ // Post-pass 4 (§5): an unqualified delete of a name bound on the global object deletes the global object's
949
+ // property. In strict code it is a SyntaxError, so the script never runs.
950
+ if (operand.type === 'Identifier' && scope.isStrict(node.start))
951
+ return [];
952
+ const target = memberOf(operand, (object, { member }) => {
953
+ open.push({ site: node, holder: object, mechanism: 'delete', change: { kind: 'delete', member }, target: operand });
954
+ }, (object, key, reached) => {
955
+ const change = { kind: 'delete', member: unknownMember };
956
+ wildWrite(node, reached, object, key, 'delete', change, memberLookup(operand, object), { target: operand });
957
+ });
958
+ if (target === undefined)
959
+ return [];
960
+ const { holder, member, leaves, realmInstance, folds, parts, lookup } = target;
961
+ const change = { kind: 'delete', member };
962
+ return [{ site: node, holder, leaves, realmInstance, mechanism: 'delete', change, folds, parts, lookup }];
963
+ }
964
+ case 'CallExpression': {
965
+ // A method read from an object can write, `h.m(…)`, `(0, h.m)(…)`, and so can a name that aliases a writer
966
+ // (`Ae = Object.defineProperty; Ae(h, k, d)`, §5 Resolver capabilities 2).
967
+ const callee = calleeExpression(node['callee']);
968
+ if (callee.type === 'Identifier') {
969
+ return apart(() => aliasedWriters(callee)).flatMap((writer) => callWrites(node, writer));
970
+ }
971
+ if (callee.type !== 'MemberExpression' || callee['object'].type === 'Super')
972
+ return [];
973
+ const method = apart(() => memberKey(callee));
974
+ if (method === undefined || typeof method.member !== 'string')
975
+ return [];
976
+ const named = { expression: callee, member: method.member, folds: method.folds };
977
+ if (!Object.hasOwn(accessorDefiners, named.member))
978
+ return callWrites(node, named);
979
+ // `(0, h.__defineGetter__)(…)` calls it with no `this`, which ToObject refuses (Principle 1).
980
+ return callee === node['callee'] ? definerWrites(node, named) : [];
981
+ }
982
+ case 'VariableDeclaration':
983
+ case 'FunctionDeclaration':
984
+ case 'ClassDeclaration':
985
+ return declarationWrites(node);
986
+ default:
987
+ return [];
988
+ }
989
+ /**
990
+ * The global object's members a declaration writes (§3 Declarations in classic scripts), with mechanism `declare`:
991
+ * one per name it binds, as the change a [[Set]] of the name's value makes. Whether that is a mutation, and how, is
992
+ * for the declaration's form and the catalog to decide (`effect.ts`). A binding's value is its initializer, a
993
+ * function or class, `undefined` when it has none, or a value the analyzer doesn't take apart (a loop's, or a pattern
994
+ * element's), except that `{ m } = h` reads `h.m`.
995
+ */
996
+ function declarationWrites(declaration) {
997
+ const declared = scope.declared(declaration);
998
+ if (declared === undefined)
999
+ return [];
1000
+ const bound = [];
1001
+ if (declaration.type !== 'VariableDeclaration') {
1002
+ bound.push({ id: declaration['id'], value: 'function' });
1003
+ }
1004
+ else {
1005
+ for (const declarator of declaration['declarations']) {
1006
+ const id = declarator['id'];
1007
+ const init = declarator['init'];
1008
+ if (id.type === 'Identifier') {
1009
+ bound.push({ id, value: declared.loop === true ? 'unknown' : (init ?? 'undefined') });
1010
+ continue;
1011
+ }
1012
+ for (const name of patternIdentifiers(id)) {
1013
+ const key = init === null || declared.loop === true ? undefined : readKey(id, name);
1014
+ bound.push({ id: name, value: key === undefined ? 'unknown' : { holder: init, key } });
1015
+ }
1016
+ }
1017
+ }
1018
+ return bound.map(({ id, value }) => {
1019
+ const name = id['name'];
1020
+ const leaf = {
1021
+ form: 'global-property',
1022
+ node: id,
1023
+ name,
1024
+ binding: { withs: [], evals: [], declaration: { kind: declared.kind, node: declaration } },
1025
+ instance: context.instance,
1026
+ };
1027
+ const slot = { holder: global, member: name, realmInstance: context.instance, leaves: [leaf], folds: [] };
1028
+ let same = false;
1029
+ let written;
1030
+ let sources = [];
1031
+ let heap = {};
1032
+ if (typeof value === 'string') {
1033
+ // A function, a class or `undefined` is never `null`; a value not taken apart may be.
1034
+ const site = value === 'function' ? declaration : id;
1035
+ written = value === 'unknown' ? writtenValue(name, id, id, true) : writtenValue(name, 'primitive', site, false);
1036
+ if (value === 'function')
1037
+ sources = [{ node: declaration, at }];
1038
+ }
1039
+ else if ('type' in value) {
1040
+ same = readsSlot(value, slot, [value]);
1041
+ if (same && throughHeap(value))
1042
+ heap = { throughHeap: true };
1043
+ written = writtenValue(name, value, value, same);
1044
+ sources = [{ node: value, at }];
1045
+ }
1046
+ else {
1047
+ const reached = apart(() => holderOf(value.holder));
1048
+ same = reached !== undefined && reached.holder === global && value.key === name && sameWindow(reached.leaves, [leaf], [value.holder]);
1049
+ written = writtenValue(name, id, id, true);
1050
+ }
1051
+ const bare = declared.kind === 'var' && value === 'undefined';
1052
+ const change = { kind: 'set', member: name, same, ...heap, ...written, ...(sources.length > 0 ? { sources } : {}) };
1053
+ return {
1054
+ site: id,
1055
+ holder: global,
1056
+ leaves: [leaf],
1057
+ realmInstance: context.instance,
1058
+ mechanism: 'declare',
1059
+ change,
1060
+ folds: [],
1061
+ parts: { holder: [], member: [] },
1062
+ lookup: { node: id, token: name },
1063
+ declaration: bare ? { ...declared, bare } : declared,
1064
+ };
1065
+ });
1066
+ }
1067
+ /**
1068
+ * The key a name bound by a pattern reads from the value destructured, when the pattern is an object pattern whose
1069
+ * property binds the name directly, with no default: `m` in `{ m }` or `{ m: x }`, and `"m"` in `{ "m": x }` or
1070
+ * `{ ["m"]: x }`.
1071
+ */
1072
+ function readKey(pattern, id) {
1073
+ if (pattern.type !== 'ObjectPattern')
1074
+ return undefined;
1075
+ const property = pattern['properties'].find((each) => each['value'] === id);
1076
+ if (property === undefined)
1077
+ return undefined;
1078
+ const key = property['key'];
1079
+ if (key.type === 'Identifier' && property['computed'] !== true)
1080
+ return key['name'];
1081
+ const literal = literalKey(key);
1082
+ return typeof literal === 'string' ? literal : undefined;
1083
+ }
1084
+ /**
1085
+ * The write `h.__defineGetter__(k, g)` or `h.__defineSetter__(k, s)` makes (ECMA-262 B.2.2.2, B.2.2.3): a define of
1086
+ * an enumerable, configurable accessor on h, its `this` (§3), through the method h inherits from `Object.prototype`.
1087
+ * A bare call, or one through a comma sequence, has no `this`, so ToObject throws, and is never one.
1088
+ */
1089
+ function definerWrites(call, method) {
1090
+ const via = method.member;
1091
+ const object = method.expression['object'];
1092
+ let unresolved = false;
1093
+ const reached = holderOf(object, () => (unresolved = true));
1094
+ const [name, accessor] = call['arguments'];
1095
+ if (unresolved && name !== undefined && name.type !== 'SpreadElement' && accessor !== undefined) {
1096
+ const key = accessor.type === 'SpreadElement' || primitive(accessor) ? undefined : constantKey(name);
1097
+ if (key !== undefined && key.member !== '__proto__') {
1098
+ const field = accessorDefiners[via];
1099
+ const descriptor = { ...unseenDescriptor(), get: field === 'get', set: field === 'set', enumerable: true, configurable: true };
1100
+ open.push({ site: call, holder: object, mechanism: 'define-property', change: { kind: 'define', member: key.member, descriptor } });
1101
+ }
1102
+ }
1103
+ if (reached === undefined || name === undefined || accessor === undefined)
1104
+ return [];
1105
+ if (name.type === 'SpreadElement' || accessor.type === 'SpreadElement')
1106
+ return [];
1107
+ // The accessor must be callable: a primitive is a TypeError.
1108
+ if (primitive(accessor))
1109
+ return [];
1110
+ const key = constantKey(name);
1111
+ if (key === undefined) {
1112
+ // A key the analyzer knows nothing of may be a wildcard define (§5 Partially resolved writes).
1113
+ if (unknownKey(name)) {
1114
+ const field = accessorDefiners[via];
1115
+ const descriptor = { ...unseenDescriptor(), get: field === 'get', set: field === 'set', enumerable: true, configurable: true };
1116
+ const change = { kind: 'define', member: unknownMember, descriptor, via };
1117
+ wildWrite(call, reached, object, name, 'define-property', change, keyLookup(name, object), { writer: via, folds: method.folds });
1118
+ }
1119
+ return [];
1120
+ }
1121
+ const { holder, leaves } = reached;
1122
+ const field = accessorDefiners[via];
1123
+ const descriptor = {
1124
+ ...unseenDescriptor(),
1125
+ get: field === 'get',
1126
+ set: field === 'set',
1127
+ enumerable: true,
1128
+ configurable: true,
1129
+ };
1130
+ const change = { kind: 'define', member: key.member, descriptor, via };
1131
+ const folds = [...reached.folds, ...method.folds, ...key.folds];
1132
+ const lookup = keyLookup(name, object);
1133
+ const realmInstance = instanceOf(leaves);
1134
+ const parts = { holder: reached.folds, member: key.folds };
1135
+ return [{ site: call, holder, leaves, realmInstance, mechanism: 'define-property', writer: via, change, folds, parts, lookup }];
1136
+ }
1137
+ /**
1138
+ * A change to the prototype `value` gives of the holder reached from `leaves`: unknown when a getter gives it.
1139
+ * `evaluated` is what the write evaluates, which could change the window a window expression reaches.
1140
+ */
1141
+ function prototypeChange(value, setter, leaves, evaluated) {
1142
+ const proto = value === undefined ? [{ kind: 'unknown' }] : apart(() => context.resolution.evaluate(value, at));
1143
+ const [only] = proto;
1144
+ const same = proto.length === 1 && only?.kind === 'holder' && sameWindow(only.leaves, leaves, evaluated);
1145
+ return { kind: 'prototype', proto, setter, sameWindow: same };
1146
+ }
1147
+ /** The writes a call to one of the native writer functions makes. */
1148
+ function callWrites(call, method) {
1149
+ const callee = 'expression' in method ? apart(() => nativeFunction(method)) : method;
1150
+ if (callee === undefined || !Object.hasOwn(nativeWriters, callee.name))
1151
+ return [];
1152
+ const mechanism = nativeWriters[callee.name];
1153
+ const args = call['arguments'];
1154
+ const [object, second, third] = args;
1155
+ if (object === undefined || object.type === 'SpreadElement')
1156
+ return [];
1157
+ let unresolved = false;
1158
+ // `Reflect.set(t, k, v, r)` writes r: several holders t may be are no candidate set of what is written.
1159
+ const lookupOnly = callee.name === 'Reflect.set' && args.length > 3;
1160
+ const reached = holderOf(object, () => (unresolved = true), lookupOnly);
1161
+ if (callee.name === 'Reflect.set') {
1162
+ openReflectSet(call, mechanism, unresolved);
1163
+ return reflectSetWrites(call, callee, reached);
1164
+ }
1165
+ if (unresolved)
1166
+ openCallWrites(call, mechanism, callee.name, object);
1167
+ if (reached === undefined)
1168
+ return [];
1169
+ const { holder } = reached;
1170
+ const leaves = inDocumentOrder([...reached.leaves, ...callee.leaves]);
1171
+ // The native writer function may be another window's: the holder written decides the realm instance.
1172
+ const realmInstance = instanceOf(reached.leaves);
1173
+ const slot = ({ member, folds }) => ({
1174
+ holder,
1175
+ member,
1176
+ realmInstance,
1177
+ leaves: reached.leaves,
1178
+ folds: [...reached.folds, ...folds],
1179
+ });
1180
+ const write = (site, change, folds, lookup, member, as = mechanism) => ({
1181
+ site,
1182
+ holder,
1183
+ leaves,
1184
+ realmInstance,
1185
+ mechanism: as,
1186
+ writer: callee.name,
1187
+ change,
1188
+ folds: [...callee.folds, ...reached.folds, ...folds],
1189
+ parts: { holder: reached.folds, member },
1190
+ lookup,
1191
+ });
1192
+ switch (callee.name) {
1193
+ case 'Object.defineProperty':
1194
+ case 'Reflect.defineProperty': {
1195
+ if ([second, third].some((arg) => arg === undefined || arg.type === 'SpreadElement'))
1196
+ return [];
1197
+ const key = constantKey(second);
1198
+ if (key === undefined) {
1199
+ if (unknownKey(second)) {
1200
+ const described = descriptorOf(third, undefined, args, []);
1201
+ const change = described && { kind: 'define', member: unknownMember, ...described };
1202
+ if (change !== undefined) {
1203
+ const extra = { writer: callee.name, leaves, folds: callee.folds };
1204
+ wildWrite(call, reached, object, second, mechanism, change, keyLookup(second, object), extra);
1205
+ }
1206
+ }
1207
+ return [];
1208
+ }
1209
+ const folds = [...key.folds];
1210
+ const lookup = keyLookup(second, object);
1211
+ const described = descriptorOf(third, slot(key), args, folds);
1212
+ if (described === undefined)
1213
+ return [];
1214
+ const source = { node: third, at };
1215
+ return [write(call, { kind: 'define', member: key.member, ...described, source }, folds, lookup, key.folds)];
1216
+ }
1217
+ case 'Object.defineProperties': {
1218
+ if (second?.type !== 'ObjectExpression')
1219
+ return [];
1220
+ // ObjectDefineProperties converts every descriptor before it defines any, so one that ToPropertyDescriptor
1221
+ // refuses, whatever its key, throws before anything is written (Principle 1).
1222
+ const writes = [];
1223
+ const wildDefines = [];
1224
+ for (const property of second['properties']) {
1225
+ if (property.type !== 'Property')
1226
+ continue;
1227
+ const key = propertyKey(property);
1228
+ if (key !== undefined && typeof key.member === 'string' && setsLiteralPrototype(property, key.member))
1229
+ continue;
1230
+ const folds = [...(key?.folds ?? [])];
1231
+ // A getter in the literal gives a descriptor the syntax doesn't show.
1232
+ const described = property['kind'] === 'init'
1233
+ ? descriptorOf(property['value'], key && slot(key), args, folds)
1234
+ : { descriptor: unseenDescriptor(property) };
1235
+ if (described === undefined)
1236
+ return [];
1237
+ if (key === undefined) {
1238
+ // A computed key the analyzer knows nothing of may be a wildcard define (§5 Partially resolved writes).
1239
+ const computed = property['key'];
1240
+ if (property['computed'] === true && unknownKey(computed))
1241
+ wildDefines.push({ property, computed, described });
1242
+ continue;
1243
+ }
1244
+ const lookup = propertyLookup(property, object);
1245
+ const source = property['kind'] === 'init' ? { source: { node: property['value'], at } } : {};
1246
+ writes.push(write(property, { kind: 'define', member: key.member, ...described, ...source }, folds, lookup, key.folds));
1247
+ }
1248
+ for (const { property, computed, described } of wildDefines) {
1249
+ const change = { kind: 'define', member: unknownMember, ...described };
1250
+ const extra = { writer: callee.name, leaves, folds: callee.folds };
1251
+ wildWrite(property, reached, object, computed, mechanism, change, propertyLookup(property, object), extra);
1252
+ }
1253
+ return writes;
1254
+ }
1255
+ case 'Object.assign': {
1256
+ // Each source's own enumerable properties are [[Set]] on the target in turn. A source that isn't an object
1257
+ // literal has keys the syntax doesn't show.
1258
+ const literals = args.slice(1).filter((source) => source.type === 'ObjectExpression');
1259
+ // A computed key the analyzer knows nothing of may be a wildcard assign (§5 Partially resolved writes).
1260
+ for (const source of literals) {
1261
+ for (const property of source['properties']) {
1262
+ if (property.type !== 'Property' || property['computed'] !== true)
1263
+ continue;
1264
+ const computed = property['key'];
1265
+ if (constantKey(computed) !== undefined || !unknownKey(computed))
1266
+ continue;
1267
+ const value = property['kind'] === 'init' ? property['value'] : undefined;
1268
+ const written = value === undefined ? {} : { sources: [{ node: value, at }] };
1269
+ const change = { kind: 'set', member: unknownMember, same: false, ...written };
1270
+ const extra = { writer: callee.name, leaves, folds: callee.folds, ...(value === undefined ? {} : { value }) };
1271
+ wildWrite(property, reached, object, computed, mechanism, change, propertyLookup(property, object), extra);
1272
+ }
1273
+ }
1274
+ return literals
1275
+ .flatMap((source) => properties(source).map(({ property, key }) => {
1276
+ // A getter in the literal gives whatever it returns.
1277
+ const value = property['kind'] === 'init' ? property['value'] : undefined;
1278
+ // An own `__proto__` key (computed, shorthand or a method) is [[Set]] through the target's setter. Such a
1279
+ // write has no member, so it looks up its holder.
1280
+ if (key.member === '__proto__') {
1281
+ const change = prototypeChange(value, true, reached.leaves, args);
1282
+ return write(property, change, key.folds, holderLookup(object), [], 'set-prototype');
1283
+ }
1284
+ const lookup = propertyLookup(property, object);
1285
+ const same = value !== undefined && readsSlot(value, slot(key), args);
1286
+ const written = writtenValue(key.member, value ?? 'getter', value ?? property, same);
1287
+ const sources = value === undefined ? {} : { sources: [{ node: value, at }] };
1288
+ const heap = same && throughHeap(value) ? { throughHeap: true } : {};
1289
+ return write(property, { kind: 'set', member: key.member, same, ...heap, ...written, ...sources }, key.folds, lookup, key.folds);
1290
+ }));
1291
+ }
1292
+ case 'Reflect.deleteProperty': {
1293
+ // `delete h[k]` (§3): a missing key would be `"undefined"`, which no holder has as a configurable member.
1294
+ if (second === undefined || second.type === 'SpreadElement')
1295
+ return [];
1296
+ const key = constantKey(second);
1297
+ if (key === undefined) {
1298
+ if (unknownKey(second)) {
1299
+ const extra = { writer: callee.name, leaves, folds: callee.folds };
1300
+ const change = { kind: 'delete', member: unknownMember };
1301
+ wildWrite(call, reached, object, second, mechanism, change, keyLookup(second, object), extra);
1302
+ }
1303
+ return [];
1304
+ }
1305
+ return [write(call, { kind: 'delete', member: key.member }, key.folds, keyLookup(second, object), key.folds)];
1306
+ }
1307
+ default:
1308
+ // Object.setPrototypeOf and Reflect.setPrototypeOf.
1309
+ if (second === undefined || second.type === 'SpreadElement')
1310
+ return [];
1311
+ return [write(call, prototypeChange(second, false, reached.leaves, args), [], holderLookup(object), [])];
1312
+ }
1313
+ }
1314
+ /**
1315
+ * The write sites of a call to a native writer function whose holder argument `object` may be an object the analyzer
1316
+ * can't resolve (§5 Partially resolved writes): a define or delete of a key that folds, and each property of a bulk
1317
+ * mechanism's object literal whose key folds. A prototype change has no member, so it has none.
1318
+ */
1319
+ function openCallWrites(call, mechanism, name, object) {
1320
+ const args = call['arguments'];
1321
+ const [, second, third] = args;
1322
+ const push = (site, change, value) => {
1323
+ if (change.member === '__proto__')
1324
+ return;
1325
+ open.push({ site, holder: object, mechanism, change, ...(value === undefined ? {} : { value }) });
1326
+ };
1327
+ switch (name) {
1328
+ case 'Object.defineProperty':
1329
+ case 'Reflect.defineProperty': {
1330
+ if ([second, third].some((arg) => arg === undefined || arg.type === 'SpreadElement'))
1331
+ return;
1332
+ const key = constantKey(second);
1333
+ const described = key && descriptorOf(third, undefined, args, []);
1334
+ if (key !== undefined && described !== undefined)
1335
+ push(call, { kind: 'define', member: key.member, ...described });
1336
+ return;
1337
+ }
1338
+ case 'Object.defineProperties':
1339
+ if (second?.type !== 'ObjectExpression')
1340
+ return;
1341
+ for (const { property, key } of properties(second)) {
1342
+ const described = property['kind'] === 'init' ? descriptorOf(property['value'], undefined, args, []) : { descriptor: unseenDescriptor(property) };
1343
+ if (described !== undefined)
1344
+ push(property, { kind: 'define', member: key.member, ...described });
1345
+ }
1346
+ return;
1347
+ case 'Object.assign':
1348
+ for (const source of args.slice(1)) {
1349
+ if (source.type !== 'ObjectExpression')
1350
+ continue;
1351
+ for (const { property, key } of properties(source)) {
1352
+ const value = property['kind'] === 'init' ? property['value'] : undefined;
1353
+ push(property, { kind: 'set', member: key.member, same: false }, value);
1354
+ }
1355
+ }
1356
+ return;
1357
+ case 'Reflect.deleteProperty': {
1358
+ if (second === undefined || second.type === 'SpreadElement')
1359
+ return;
1360
+ const key = constantKey(second);
1361
+ if (key !== undefined)
1362
+ push(call, { kind: 'delete', member: key.member });
1363
+ return;
1364
+ }
1365
+ default:
1366
+ }
1367
+ }
1368
+ /**
1369
+ * The write site of `Reflect.set(t, k, v, r)`, an assign (§3), when its receiver (r, or t when not given) may be an
1370
+ * object the analyzer can't resolve and k folds (§5 Partially resolved writes). `tUnresolved` says t may be. A
1371
+ * `__proto__` key has no member.
1372
+ */
1373
+ function openReflectSet(call, mechanism, tUnresolved) {
1374
+ const args = call['arguments'];
1375
+ const [object, second, third, fourth] = args;
1376
+ if (object === undefined || second === undefined || args.slice(1).some((arg) => arg.type === 'SpreadElement'))
1377
+ return;
1378
+ const receiver = fourth ?? object;
1379
+ const unresolved = fourth === undefined ? tUnresolved : context.resolution.evaluate(fourth, at).some(opaque);
1380
+ if (!unresolved)
1381
+ return;
1382
+ const key = constantKey(second);
1383
+ if (key === undefined || key.member === '__proto__')
1384
+ return;
1385
+ const change = { kind: 'set', member: key.member, same: false };
1386
+ open.push({ site: call, holder: receiver, mechanism, change, ...(third === undefined ? {} : { value: third }) });
1387
+ }
1388
+ /**
1389
+ * The write `Reflect.set(t, k, v, r)` makes (§3): a [[Set]] that looks k up on t's chain and writes its receiver r, t
1390
+ * when not given (ECMA-262 OrdinarySet). A receiver that is t's holder is `r[k] = v`. Another native holder is written
1391
+ * as [[Set]] decides from what t's chain gives and r's own property, or, when t denotes no native holder, as if t's
1392
+ * chain lacked k. A receiver that doesn't resolve, as a spread may give, gives nothing.
1393
+ */
1394
+ function reflectSetWrites(call, callee, reached) {
1395
+ const args = call['arguments'];
1396
+ const [object, second, third, fourth] = args;
1397
+ if (second === undefined || args.slice(1).some((arg) => arg.type === 'SpreadElement'))
1398
+ return [];
1399
+ const key = constantKey(second);
1400
+ const receiver = fourth === undefined ? reached : holderOf(fourth);
1401
+ // `Reflect.set(h, k, v)` with a key the analyzer knows nothing of is `h[k] = v` (§3).
1402
+ if (key === undefined && fourth === undefined && reached !== undefined && unknownKey(second)) {
1403
+ const sources = third === undefined || third.type === 'SpreadElement' ? {} : { sources: [{ node: third, at }] };
1404
+ const change = { kind: 'set', member: unknownMember, same: false, ...sources };
1405
+ const extra = { writer: callee.name, leaves: inDocumentOrder([...reached.leaves, ...callee.leaves]), folds: callee.folds };
1406
+ wildWrite(call, reached, object, second, 'assign', change, keyLookup(second, object), {
1407
+ ...extra,
1408
+ ...(third === undefined ? {} : { value: third }),
1409
+ });
1410
+ }
1411
+ if (key === undefined || receiver === undefined)
1412
+ return [];
1413
+ // What k is looked up on, when it isn't the holder written. A t that denotes no native holder may be any object,
1414
+ // unless it is plainly no object, which throws before any write (Principle 1). One that may be several native
1415
+ // holders is a candidate set (§5). Neither of those is written.
1416
+ let through;
1417
+ // A t known to be an ordinary object whose chain reaches `Object.prototype` gives the `__proto__` setter there
1418
+ // (§3): its chain, from the prototype its literal has, decides the write.
1419
+ const ordinary = reached === undefined && key.member === '__proto__' ? ordinaryPrototype(object) : undefined;
1420
+ if (ordinary !== undefined)
1421
+ through = { holder: ordinary };
1422
+ else if (reached === undefined) {
1423
+ const values = context.resolution.evaluate(object, at);
1424
+ if (values.every((value) => value.kind === 'constant') || values.some((value) => value.kind === 'holder'))
1425
+ return [];
1426
+ through = { unresolved: object };
1427
+ }
1428
+ else if (reached.holder !== receiver.holder)
1429
+ through = { holder: reached.holder };
1430
+ const own = receiver === reached;
1431
+ const realmInstance = instanceOf(receiver.leaves);
1432
+ const written = { holder: receiver.holder, member: key.member, realmInstance, leaves: receiver.leaves, folds: [] };
1433
+ const write = (change, as, lookup) => ({
1434
+ site: call,
1435
+ holder: receiver.holder,
1436
+ leaves: inDocumentOrder([...(reached?.leaves ?? []), ...callee.leaves, ...(own ? [] : receiver.leaves)]),
1437
+ realmInstance,
1438
+ mechanism: as,
1439
+ writer: callee.name,
1440
+ change,
1441
+ folds: [...callee.folds, ...(reached?.folds ?? []), ...key.folds, ...(own ? [] : receiver.folds)],
1442
+ parts: { holder: receiver.folds, member: as === 'set-prototype' ? [] : key.folds },
1443
+ lookup,
1444
+ });
1445
+ // The object written: r, or t when not given.
1446
+ const target = fourth ?? object;
1447
+ // `r.__proto__ = p` calls the setter t's chain gives, `Object.prototype.__proto__`'s, which ignores a missing
1448
+ // (undefined) value. Such a write has no member, so it looks up the object written.
1449
+ if (key.member === '__proto__' && (through === undefined || 'holder' in through)) {
1450
+ if (third === undefined)
1451
+ return [];
1452
+ const change = { ...prototypeChange(third, true, receiver.leaves, args), through: through?.holder };
1453
+ return [write(change, 'set-prototype', holderLookup(target))];
1454
+ }
1455
+ // A missing value sets `undefined`, a new value.
1456
+ const same = third !== undefined && readsSlot(third, written, args);
1457
+ const value = writtenValue(key.member, third ?? 'primitive', third ?? call, same);
1458
+ const sources = third === undefined || third.type === 'SpreadElement' ? {} : { sources: [{ node: third, at }] };
1459
+ return [write({ kind: 'set', member: key.member, same, through, ...value, ...sources }, 'assign', keyLookup(second, target))];
1460
+ }
1461
+ /**
1462
+ * The prototype holder of an object that `node` is known to create with no own `__proto__`: an object literal with
1463
+ * no own `__proto__`, and no spread or key that doesn't fold, which could give it one (`Object.prototype`, or the
1464
+ * native holder its `__proto__:` field gives); an array literal (`Array.prototype`); or a function literal
1465
+ * (`Function.prototype`).
1466
+ */
1467
+ function ordinaryPrototype(node) {
1468
+ switch (node.type) {
1469
+ case 'ArrayExpression':
1470
+ return 'Array.prototype';
1471
+ case 'FunctionExpression':
1472
+ case 'ArrowFunctionExpression':
1473
+ return 'Function.prototype';
1474
+ case 'ObjectExpression': {
1475
+ let prototype = 'Object.prototype';
1476
+ for (const property of node['properties']) {
1477
+ if (property.type !== 'Property')
1478
+ return undefined;
1479
+ const key = propertyKey(property);
1480
+ if (key === undefined)
1481
+ return undefined;
1482
+ if (key.member !== '__proto__')
1483
+ continue;
1484
+ if (!setsLiteralPrototype(property, key.member))
1485
+ return undefined;
1486
+ // `__proto__: p` gives the literal p as its prototype: a native holder, or `null`, where the chain ends. Any
1487
+ // other primitive sets none.
1488
+ const values = context.resolution.evaluate(property['value'], at);
1489
+ const [only] = values;
1490
+ if (values.length !== 1 || only === undefined)
1491
+ return undefined;
1492
+ if (only.kind === 'holder')
1493
+ prototype = only.holder;
1494
+ else if (only.kind !== 'constant' || only.value === null)
1495
+ return undefined;
1496
+ }
1497
+ return prototype;
1498
+ }
1499
+ default:
1500
+ return undefined;
1501
+ }
1502
+ }
1503
+ /**
1504
+ * An object literal's own properties with keys that fold to constants, in source order. Spread properties are left
1505
+ * out, and so is `__proto__: p`, which gives the literal a prototype rather than an own property.
1506
+ */
1507
+ function properties(literal) {
1508
+ return literal['properties'].flatMap((property) => {
1509
+ if (property.type !== 'Property')
1510
+ return [];
1511
+ const key = propertyKey(property);
1512
+ if (key === undefined || (typeof key.member === 'string' && setsLiteralPrototype(property, key.member)))
1513
+ return [];
1514
+ return [{ property, key }];
1515
+ });
1516
+ }
1517
+ /** The native writer functions a name aliases: each alternative that is one counts (Principle 3). */
1518
+ function aliasedWriters(id) {
1519
+ return context.resolution.aliasCallee(id, at).flatMap((value) => value.kind === 'native' && Object.hasOwn(nativeWriters, value.name)
1520
+ ? [{ name: value.name, leaves: inDocumentOrder(value.leaves), folds: value.folds ?? [] }]
1521
+ : []);
1522
+ }
1523
+ /**
1524
+ * The native function a called method denotes, by catalog name (`Object.defineProperty`), with its holder's leaves,
1525
+ * and the folds its holder and key come from.
1526
+ */
1527
+ function nativeFunction({ expression, member, folds }) {
1528
+ // Most calls are to other functions: only a writer's name is worth resolving the holder for.
1529
+ if (!writerMembers.has(member))
1530
+ return undefined;
1531
+ const reached = holderOf(expression['object']);
1532
+ return reached && { name: `${reached.holder}.${member}`, leaves: reached.leaves, folds: [...reached.folds, ...folds] };
1533
+ }
1534
+ }
1535
+ /** The fields ECMA-262 ToPropertyDescriptor reads. */
1536
+ export const descriptorFields = new Set(['value', 'get', 'set', 'writable', 'enumerable', 'configurable']);
1537
+ /** A descriptor with no field the syntax shows, and `unseen` the parts that may give it any. */
1538
+ function unseenDescriptor(...unseen) {
1539
+ return { get: false, set: false, unseen, undecided: [] };
1540
+ }
1541
+ /**
1542
+ * The name a key is written as at the site when it is a string key: a string literal, or a template literal with no
1543
+ * substitutions, by its cooked value (§10 Token check). A number, a template with substitutions, or any other
1544
+ * expression is no token.
1545
+ */
1546
+ function writtenString(key) {
1547
+ if (key.type === 'Literal')
1548
+ return typeof key['value'] === 'string' ? key['value'] : undefined;
1549
+ if (key.type !== 'TemplateLiteral' || key['expressions'].length > 0)
1550
+ return undefined;
1551
+ const cooked = key['quasis'][0]?.['value']?.cooked;
1552
+ return typeof cooked === 'string' ? cooked : undefined;
1553
+ }
1554
+ /**
1555
+ * The assignment operators whose value is the right side as read: `=`, and the logical assignments, which write it only
1556
+ * when they write at all. The others (`+=`, `|=`, `**=`) write a new value computed from both sides.
1557
+ */
1558
+ const writeBackOperators = new Set(['=', '||=', '&&=', '??=']);
1559
+ /** The identifiers a binding pattern binds, as `boundNames` names them. */
1560
+ function patternIdentifiers(pattern) {
1561
+ const found = [];
1562
+ walkTree(pattern, {
1563
+ enter(node, parent) {
1564
+ // A default value, or a computed key, is an expression, whose identifiers are read, not bound.
1565
+ if (parent?.type === 'AssignmentPattern' && parent['right'] === node)
1566
+ return false;
1567
+ if (parent?.type === 'Property' && parent['key'] === node)
1568
+ return false;
1569
+ if (node.type === 'Identifier')
1570
+ found.push(node);
1571
+ return undefined;
1572
+ },
1573
+ });
1574
+ const names = new Set(boundNames(pattern));
1575
+ return found.filter((id) => names.has(id['name']));
1576
+ }
1577
+ /** A bare name bound on the global object: the global object's property of that name (§5), in the window `instance`. */
1578
+ function bareGlobal(id, scope, instance) {
1579
+ const binding = scope.resolve(id);
1580
+ if (binding === 'local')
1581
+ return undefined;
1582
+ return { form: 'global-property', node: id, name: id['name'], binding, instance };
1583
+ }
1584
+ const isMaybe = (part) => part.kind === 'field' && part.maybe !== undefined;
1585
+ /**
1586
+ * Every way an object's fields that may be absent can be present or absent, as the parts each gives, or undefined
1587
+ * past the descriptor-alternatives cap, `maxDescriptorAlternatives`. Only the last present write of a field decides it,
1588
+ * so each field is absent, or has exactly one of its maybe-absent writes: the product of one more than each field's
1589
+ * writes.
1590
+ */
1591
+ function presentOrAbsent(parts, maxDescriptorAlternatives) {
1592
+ const byKey = new Map();
1593
+ for (const part of parts) {
1594
+ if (!isMaybe(part))
1595
+ continue;
1596
+ const same = byKey.get(part.key);
1597
+ if (same === undefined)
1598
+ byKey.set(part.key, [part]);
1599
+ else
1600
+ same.push(part);
1601
+ }
1602
+ let count = 1;
1603
+ for (const writes of byKey.values())
1604
+ count *= writes.length + 1;
1605
+ if (count > maxDescriptorAlternatives)
1606
+ return undefined;
1607
+ let chosen = [new Set()];
1608
+ for (const writes of byKey.values()) {
1609
+ chosen = chosen.flatMap((set) => [set, ...writes.map((write) => new Set([...set, write]))]);
1610
+ }
1611
+ return chosen.map((set) => parts.filter((part) => !isMaybe(part) || set.has(part)));
1612
+ }