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,800 @@
1
+ // Effects (§8 Effect and mechanism, ADR 0011): what a write does to its target, decided against the unmutated catalog.
2
+ // The effect follows the member's new value, not the attributes that change with it.
3
+ //
4
+ // Writes that are API use give nothing (§3): a [[Set]] follows the member's `assign` kind. A write that can't change
5
+ // its target is an ineffective write (§3 Ineffective writes), with its reason and whether it throws. A define whose
6
+ // descriptor the analyzer can't see (§3 Undecidable outcomes) is a `descriptor-change` or `replace` of a catalog
7
+ // member, with `outcome-undecidable` and `locks: unknown`, or an `add`. A `Reflect.set` whose receiver resolves and
8
+ // whose lookup object doesn't is worked out as if that object lacked the member, with `outcome-undecidable` and
9
+ // `locks: false`. A write of a member the holder only inherits is a shadowing write (§3, §4, ADR 0002), named on the
10
+ // object written, or an expando that gives nothing.
11
+ import { ecmascriptSpecs, namedPropertiesObject, } from "../../../catalog.js";
12
+ import { opaque } from "./global-object.js";
13
+ import { memberName, wellKnownSymbol } from "./keys.js";
14
+ import { descriptorFields, } from "./writes.js";
15
+ /** Whether an outcome is an ineffective write. */
16
+ export const isIneffective = (outcome) => 'reason' in outcome;
17
+ /**
18
+ * Whether a write that can't change its target throws (§3): a `[LegacyLenientSetter]` setter never does. Otherwise it
19
+ * follows what makes the write. The `Reflect` functions return false, and never throw, except that `Reflect.set`
20
+ * of `__proto__` runs `Object.prototype.__proto__`'s setter, which throws. `Object.defineProperty`,
21
+ * `Object.defineProperties`, `__defineGetter__`, `__defineSetter__`, `Object.setPrototypeOf` and `Object.assign`, and
22
+ * the `__proto__` setter, always throw. The `=` and `delete` operators throw only in strict code.
23
+ */
24
+ function throwsOf(write, refused) {
25
+ if (refused.quiet === true)
26
+ return 'never';
27
+ // A function or lexical declaration that can't bind its name fails GlobalDeclarationInstantiation, with a TypeError or
28
+ // SyntaxError, before the script runs (§3). An Annex B block function sets its binding with S false, which never
29
+ // throws. A `var` initializer's [[Set]] throws as `=` does.
30
+ const declared = write.declaration?.kind;
31
+ if (declared === 'block-function')
32
+ return 'never';
33
+ if (declared !== undefined && declared !== 'var')
34
+ return 'always';
35
+ if (write.change.kind === 'prototype' && write.change.setter)
36
+ return 'always';
37
+ if (write.writer === undefined)
38
+ return 'strict-only';
39
+ return write.writer.startsWith('Reflect.') ? 'never' : 'always';
40
+ }
41
+ /**
42
+ * The name of the target a `prototype-change` of a holder names: `<holder>.[[Prototype]]`, as the validation corpus
43
+ * names it.
44
+ */
45
+ function prototypeTarget(holder) {
46
+ return `${holder}.[[Prototype]]`;
47
+ }
48
+ /** The kinds of data property whose value is a function: callable, and an object, so truthy. */
49
+ const callableKinds = new Set(['function', 'operation', 'interface']);
50
+ /**
51
+ * The descriptor fields (`value`, `get`, `set`, `writable`, `enumerable`, `configurable`) a native holder supplies when
52
+ * it is a descriptor's prototype, as ECMA-262 ToPropertyDescriptor reads them through its chain in the unmutated catalog
53
+ * (§3 Undecidable outcomes). `functions` are the fields whose value is a function. `throwing` are the fields that are a
54
+ * Web IDL attribute: its getter runs on the descriptor, which implements no interface, so it throws a TypeError.
55
+ * `Object.prototype` supplies none. Undefined when the analyzer can't read them: a field that is any other accessor, or
56
+ * a data property that isn't a function.
57
+ */
58
+ export function descriptorFieldsOf(holder, catalog, realm) {
59
+ const holders = catalog.holders[realm];
60
+ const targets = catalog.targets[realm];
61
+ const functions = new Set();
62
+ const throwing = new Set();
63
+ for (let at = holder; at !== null; at = holders[at].proto) {
64
+ if (!Object.hasOwn(holders, at))
65
+ return undefined;
66
+ for (const field of descriptorFields) {
67
+ const name = memberName(at, field);
68
+ if (functions.has(field) || throwing.has(field) || !Object.hasOwn(targets, name))
69
+ continue;
70
+ const target = targets[name];
71
+ if (target.descriptor.type === 'data' && callableKinds.has(target.kind))
72
+ functions.add(field);
73
+ else if (target.descriptor.type === 'accessor' && target.kind === 'attribute')
74
+ throwing.add(field);
75
+ else
76
+ return undefined;
77
+ }
78
+ }
79
+ return { functions, throwing };
80
+ }
81
+ /**
82
+ * The outcomes of a write (§3): the platform mutations it makes, or the ineffective writes it is. A define whose
83
+ * descriptor can be several descriptors (several objects the heap holds, or fields that may be absent, §5 Resolver
84
+ * capabilities 5) is judged per descriptor:
85
+ * - When some can change the target, the write can, so it is no ineffective write (§3): it gives one mutation per
86
+ * effect they give. When they don't all give the same effect, or some give none or are ineffective, each has
87
+ * `outcome-undecidable`, with what the outcome depends on as its evidence, and `locks` from the descriptors that give
88
+ * its effect.
89
+ * - When none can, and some are ineffective, the write is ineffective (§3 No-op or ineffective): one ineffective
90
+ * outcome per effect they intend, which may change nothing when any descriptor changes nothing.
91
+ */
92
+ export function outcomesOf(write, catalog, realm) {
93
+ const { change } = write;
94
+ if (change.kind !== 'define' || change.alternatives === undefined) {
95
+ const outcome = mutationOf(write, catalog, realm);
96
+ return outcome === undefined ? [] : [outcome];
97
+ }
98
+ const { alternatives, ...single } = change;
99
+ // A descriptor ToPropertyDescriptor refuses throws before the define runs, so it gives nothing (Principle 1).
100
+ const outcomes = alternatives.map((descriptor) => descriptor === 'refused' ? 'refused' : mutationOf({ ...write, change: { ...single, descriptor } }, catalog, realm));
101
+ const effective = outcomes.filter((outcome) => typeof outcome === 'object' && !isIneffective(outcome));
102
+ if (effective.length === 0) {
103
+ const refused = outcomes.filter((outcome) => typeof outcome === 'object');
104
+ const noOp = outcomes.some((outcome) => outcome === undefined) || refused.some((each) => each.mayChangeNothing);
105
+ return [...byEffect(refused).values()].map(([first]) => ({ ...first, mayChangeNothing: noOp }));
106
+ }
107
+ const effects = byEffect(effective);
108
+ const decided = effects.size === 1 && effective.length === outcomes.length;
109
+ const evidence = alternatives.flatMap((descriptor) => (descriptor === 'refused' ? [] : (descriptor.evidence ?? [])));
110
+ return [...effects.values()].map((group) => {
111
+ const [first] = group;
112
+ // When the descriptors that give the effect disagree, the most-locking wins, as for occurrences (§8).
113
+ const locks = mostLocking(group.map((each) => each.locks));
114
+ const own = group.flatMap((each) => each.undecidable?.sites ?? []);
115
+ const sites = [...new Set([...(decided ? [] : evidence), ...own])].sort((a, b) => a.start - b.start);
116
+ const mutation = { ...first, locks };
117
+ delete mutation.undecidable;
118
+ const on = group.find((each) => each.undecidable !== undefined)?.undecidable?.on ?? 'descriptor';
119
+ return decided && own.length === 0 ? mutation : { ...mutation, undecidable: { on, sites } };
120
+ });
121
+ }
122
+ /** Outcomes grouped by their effect, in the order the effects first appear. */
123
+ function byEffect(outcomes) {
124
+ const effects = new Map();
125
+ for (const outcome of outcomes) {
126
+ const same = effects.get(outcome.effect);
127
+ if (same === undefined)
128
+ effects.set(outcome.effect, [outcome]);
129
+ else
130
+ same.push(outcome);
131
+ }
132
+ return effects;
133
+ }
134
+ /** The most-locking of several `locks`: `true` over `unknown` over `false` (§8). */
135
+ function mostLocking(all) {
136
+ return all.includes(true) ? true : all.includes('unknown') ? 'unknown' : false;
137
+ }
138
+ /**
139
+ * The outcome of a write with one descriptor (§3): the platform mutation it makes, if §3 plainly makes it one, or the
140
+ * ineffective write it is, when it can't change its target. Undefined when it is neither.
141
+ */
142
+ export function mutationOf(write, catalog, realm) {
143
+ const outcome = decided(write, catalog, realm);
144
+ if (outcome === undefined || !('reason' in outcome))
145
+ return outcome;
146
+ const { name, holder, shadows, effect, reason, mayChangeNothing } = outcome;
147
+ const named = { name, holder, ...(shadows === undefined ? {} : { shadows }) };
148
+ return { ...named, effect, reason, throws: throwsOf(write, outcome), mayChangeNothing };
149
+ }
150
+ function decided(write, catalog, realm) {
151
+ const { holder, change } = write;
152
+ const holders = catalog.holders[realm];
153
+ const targets = catalog.targets[realm];
154
+ const record = Object.hasOwn(holders, holder) ? holders[holder] : undefined;
155
+ if (record === undefined)
156
+ return undefined;
157
+ /** The holders on the prototype chain of `start`, starting from it, as the unmutated catalog has them. */
158
+ const chainOf = (start) => {
159
+ const chain = [];
160
+ for (let at = start; at !== null; at = Object.hasOwn(holders, at) ? holders[at].proto : null) {
161
+ chain.push(at);
162
+ }
163
+ return chain;
164
+ };
165
+ const chain = chainOf(holder);
166
+ const target = (name) => (Object.hasOwn(targets, name) ? targets[name] : undefined);
167
+ const webIdl = webIdlOf(catalog);
168
+ if (change.kind === 'prototype') {
169
+ // `h.__proto__ = p` changes h's prototype only through the setter `Object.prototype.__proto__`, the first
170
+ // `__proto__` on h's chain, or on the chain of the holder a `Reflect.set` receiver's [[Set]] looks it up on.
171
+ if (change.setter) {
172
+ const lookup = change.through === undefined ? chain : chainOf(change.through);
173
+ const accessor = lookup.map((at) => `${at}.__proto__`).find((name) => target(name) !== undefined);
174
+ if (accessor !== 'Object.prototype.__proto__' || target(accessor).descriptor.type !== 'accessor')
175
+ return undefined;
176
+ }
177
+ return prototypeChange(holder, record, change);
178
+ }
179
+ if (change.kind === 'set' && change.through !== undefined) {
180
+ const { through } = change;
181
+ if ('holder' in through)
182
+ return receiverSet(change, { holder, record, chain }, chainOf(through.holder), target, webIdl);
183
+ // A lookup object that denotes no native holder: R is written as if its chain lacked the member (§3).
184
+ const mutation = receiverSet(change, { holder, record, chain }, [], target, webIdl, true);
185
+ return mutation && { ...mutation, undecidable: { on: 'lookup', sites: [through.unresolved] } };
186
+ }
187
+ // `h.__defineGetter__(k, g)` defines only through the method `Object.prototype` gives, the first on h's chain.
188
+ if (change.kind === 'define' && change.via !== undefined) {
189
+ const method = chain.map((at) => memberName(at, change.via)).find((name) => target(name) !== undefined);
190
+ if (method !== memberName('Object.prototype', change.via))
191
+ return undefined;
192
+ }
193
+ if (write.declaration !== undefined && change.kind === 'set') {
194
+ const named = memberName(holder, change.member);
195
+ const own = target(named);
196
+ if (own !== undefined) {
197
+ const outcome = declaredEffect(write.declaration, change, own);
198
+ return outcome && { name: named, holder: own.holder, ...outcome };
199
+ }
200
+ const shadows = inheritedMember(chain, change.member, target);
201
+ if (shadows === undefined || !shadowable(record.kind, target(shadows), webIdl))
202
+ return undefined;
203
+ const outcome = declaredOverInherited(write.declaration, change);
204
+ return outcome && { name: named, holder: record.kind, shadows, ...outcome };
205
+ }
206
+ const name = memberName(holder, change.member);
207
+ const member = target(name);
208
+ if (member !== undefined) {
209
+ // The object written is the holder itself: a platform object only when it is the global or a singleton.
210
+ const outcome = memberEffect(change, member, platformObjects.has(member.holder));
211
+ return outcome && { name, holder: member.holder, ...outcome };
212
+ }
213
+ // A member the catalog doesn't list on the holder. A delete of one it only inherits changes no native (§3).
214
+ if (change.kind === 'delete')
215
+ return undefined;
216
+ if (typeof change.member === 'string' && structural[record.kind]?.has(change.member))
217
+ return undefined;
218
+ // One it inherits makes the write a shadowing write, or an expando that gives nothing (§3, §4).
219
+ const shadows = inheritedMember(chain, change.member, target);
220
+ if (shadows !== undefined)
221
+ return shadowingWrite(change, name, record.kind, shadows, target(shadows), webIdl);
222
+ // Otherwise an add, on a holder that takes one. Every define it takes adds the member, whatever the descriptor gives,
223
+ // so an unseen one leaves only `locks` unknown.
224
+ if (!addable.has(record.kind) || namedProperties(holder, record))
225
+ return undefined;
226
+ return { name, holder: record.kind, effect: 'add', locks: change.kind === 'define' ? locksOf(change.descriptor, created) : false };
227
+ }
228
+ /**
229
+ * The outcome of a declaration of a name the global object has as its own member (§3 Declarations in classic scripts),
230
+ * as ECMA-262's declaration instantiation gives it:
231
+ * - A `var`, or an Annex B block function, leaves the property as it is and [[Set]]s its value: the `assign` row. A
232
+ * bare `var x;` sets nothing.
233
+ * - A function needs CanDeclareGlobalFunction: a configurable property, or a writable, enumerable data property. It then
234
+ * defines a data property holding the function, writable, enumerable and configurable only in eval code: a `replace`
235
+ * that locks in a script. Any other property makes the script throw a TypeError: `non-configurable`.
236
+ * - A `let`, `const` or `class` leaves the property alone and hides it from bare-name callers: a `replace` unless its
237
+ * value reads the same slot, with `locks: false`. A non-configurable property makes the script throw a
238
+ * SyntaxError (HasRestrictedGlobalProperty): `non-configurable`.
239
+ */
240
+ function declaredEffect(declaration, change, member) {
241
+ const { descriptor } = member;
242
+ switch (declaration.kind) {
243
+ case 'var':
244
+ case 'block-function':
245
+ return declaration.bare === true ? undefined : setEffect(change, member, true);
246
+ case 'function': {
247
+ if (descriptor.configurable)
248
+ return { effect: 'replace', locks: !declaration.configurable };
249
+ if (descriptor.type === 'data' && descriptor.writable && descriptor.enumerable)
250
+ return { effect: 'replace', locks: true };
251
+ return { effect: 'replace', reason: 'non-configurable', mayChangeNothing: false };
252
+ }
253
+ default:
254
+ if (!descriptor.configurable)
255
+ return { effect: 'replace', reason: 'non-configurable', mayChangeNothing: false };
256
+ return change.same ? undefined : { effect: 'replace', locks: false };
257
+ }
258
+ }
259
+ /**
260
+ * The outcome of a declaration of a name the global object only inherits (§3), which it can shadow: a shadowing
261
+ * `replace`. A `var` or block function defines an own data property first (CreateGlobalVarBinding), holding `undefined`
262
+ * until its value is set, and a function one holding the function (CreateGlobalFunctionBinding): each is writable and
263
+ * enumerable, and configurable only in eval code, so it locks in a script. A `let`, `const` or `class` hides the member
264
+ * from bare-name callers, with `locks: false`, unless its value reads the same slot.
265
+ */
266
+ function declaredOverInherited(declaration, change) {
267
+ if (declaration.kind === 'let' || declaration.kind === 'const' || declaration.kind === 'class') {
268
+ return change.same ? undefined : { effect: 'replace', locks: false };
269
+ }
270
+ return { effect: 'replace', locks: !declaration.configurable };
271
+ }
272
+ /** The first member up the chain from the holder (`chain[0]`) that the unmutated catalog lists, by its name. */
273
+ function inheritedMember(chain, member, target) {
274
+ return chain
275
+ .slice(1)
276
+ .map((at) => memberName(at, member))
277
+ .find((inherited) => target(inherited) !== undefined);
278
+ }
279
+ /**
280
+ * Whether a holder of this kind can shadow this inherited member (§3 Shadowing writes, §4 naming rules): a
281
+ * shadowing target is valid only when the member resolves along the written object's IDL chain. The global object and
282
+ * the platform singletons shadow only a Web IDL member: a name only the realm's ECMAScript tail supplies
283
+ * (`Object.prototype.toString`) is an expando on them. A prototype, constructor or namespace shadows any member its
284
+ * chain has, the ES tail's among them (`XMLHttpRequest.prototype.toString = f`, `Uint8Array.prototype.at = f`), and so
285
+ * does `console`, a singleton whose holder is a namespace.
286
+ */
287
+ function shadowable(holder, inherited, webIdl) {
288
+ return !platformObjects.has(holder) || webIdl(inherited);
289
+ }
290
+ /**
291
+ * Whether a holder is a `[Global]` interface's named properties object (`WindowProperties`), the prototype holder the
292
+ * catalog names by `namedPropertiesObject`. It takes a define only of a name visible on its chain: Chromium refuses any
293
+ * other ("Named property setter is not supported"), so such a write changes nothing and is no `add` (§3). A write of a
294
+ * name visible on its chain is a shadowing write like any other (§3, §4).
295
+ */
296
+ function namedProperties(holder, record) {
297
+ return record.kind === 'prototype' && holder === namedPropertiesObject(record.owner);
298
+ }
299
+ /**
300
+ * A write on a holder of a member it only inherits (§3 Shadowing writes, ADR 0002), named on the object written, with
301
+ * `shadows` naming the inherited member: the first one up the chain in the unmutated catalog. `self.fetch = w` in a
302
+ * dedicated worker writes `DedicatedWorkerGlobalScope.fetch`, shadowing `WorkerGlobalScope.prototype.fetch`,
303
+ * `history.pushState = w` writes `history.pushState`, shadowing `History.prototype.pushState`, and
304
+ * `Window.prototype.addEventListener = w` writes `Window.prototype.addEventListener`, shadowing
305
+ * `EventTarget.prototype.addEventListener`.
306
+ * - A [[Set]] creates an own data property when the inherited member is a writable data property, and on the global
307
+ * object or a singleton, which implement the interfaces on their chain, when it is a `[Replaceable]` attribute,
308
+ * whose setter defines the property on the object written. Any other inherited accessor's setter runs (API use, as
309
+ * `document.title = t` is), or throws on an object that doesn't implement its interface.
310
+ * - A define always creates the own property, as each object written is extensible (`shadowingDefine`).
311
+ */
312
+ function shadowingWrite(change, name, holder, shadows, inherited, webIdl) {
313
+ if (!shadowable(holder, inherited, webIdl))
314
+ return undefined;
315
+ const outcome = change.kind === 'set'
316
+ ? setEffect(change, inherited, platformObjects.has(holder))
317
+ : shadowingDefine(change.descriptor, inherited.descriptor);
318
+ return outcome && { name, holder, shadows, ...outcome };
319
+ }
320
+ /**
321
+ * The outcome of a define that creates an own property over an inherited member whose descriptor in the unmutated
322
+ * catalog is `inherited`. The effect follows the new property's value (ADR 0011): a getter or setter gives a
323
+ * `descriptor-change`; a data property holding anything but the inherited member's value, `undefined` when the
324
+ * descriptor gives no `value`, a `replace`; and one holding the inherited data value a `descriptor-change` when an
325
+ * attribute differs, a field the descriptor lacks being `false` on a new property. A descriptor the analyzer can't see
326
+ * wholly is a `descriptor-change`, or a `replace` when a new `value` comes after every part it can't see, with `locks:
327
+ * unknown` and no `outcome-undecidable`, as the define can't be refused (§3 Undecidable outcomes).
328
+ */
329
+ function shadowingDefine(defined, inherited) {
330
+ if (defined.unseen.length > 0) {
331
+ return { effect: defined.value !== undefined && !defined.value.same ? 'replace' : 'descriptor-change', locks: 'unknown' };
332
+ }
333
+ const locks = locksOf(defined, created);
334
+ if (isAccessor(defined))
335
+ return { effect: 'descriptor-change', locks };
336
+ if (defined.value === undefined || !defined.value.same || inherited.type === 'accessor')
337
+ return { effect: 'replace', locks };
338
+ const { writable = false, enumerable = false, configurable = false } = defined;
339
+ return attributesChange({ ...defined, writable, enumerable, configurable }, inherited, locks);
340
+ }
341
+ /**
342
+ * A [[Set]] that looks the member up on one holder's chain (`lookup`) and writes another, its receiver
343
+ * (`Reflect.set(T, k, v, R)`, ECMA-262 OrdinarySetWithOwnDescriptor). What T's chain gives decides whether R is
344
+ * written: a non-writable data property writes nothing, and an accessor's setter runs with R as `this`, which writes R
345
+ * only for a `[Replaceable]` attribute of an interface R implements. A writable data property, or none, writes R's own
346
+ * property: its own data property's value when it is writable, and otherwise a new own data property, when it has none.
347
+ * The mutation is named on R: a replace of its own member, a shadowing write, or an add. An empty `lookup` is a chain
348
+ * without the member, as §3 works out the write when T denotes no native holder.
349
+ */
350
+ function receiverSet(change, written, lookup, target, webIdl, unresolvedLookup = false) {
351
+ const { holder, record, chain } = written;
352
+ const name = memberName(holder, change.member);
353
+ const own = target(name);
354
+ const at = lookup.find((each) => target(memberName(each, change.member)) !== undefined);
355
+ const found = at === undefined ? undefined : target(memberName(at, change.member));
356
+ let create = false;
357
+ if (found?.descriptor.type === 'accessor') {
358
+ // The [Replaceable] setter checks that `this` implements the interface, then defines an own data property on it.
359
+ const implemented = platformObjects.has(record.kind) && chain.includes(at);
360
+ if (found.assign !== 'replaceable' || !implemented)
361
+ return undefined;
362
+ create = true;
363
+ }
364
+ else if (found?.descriptor.writable === false)
365
+ return undefined;
366
+ if (own !== undefined) {
367
+ const { descriptor } = own;
368
+ // A define replaces a configurable own property, and a [[Set]] only a writable data property's value.
369
+ const writable = create ? descriptor.configurable : descriptor.type === 'data' && descriptor.writable;
370
+ return writable && !change.same ? { name, holder: own.holder, effect: 'replace', locks: false } : undefined;
371
+ }
372
+ // A new own data property holding the new value: over a member R inherits, a shadowing write when R can shadow it
373
+ // (§4), and otherwise an add. When T denotes no native holder, §3 names only an inherited Web IDL member on a global
374
+ // or prototype holder a shadowing write.
375
+ const shadows = inheritedMember(chain, change.member, target);
376
+ if (shadows !== undefined) {
377
+ const inherited = target(shadows);
378
+ const shadowing = unresolvedLookup
379
+ ? (record.kind === 'global' || record.kind === 'prototype') && webIdl(inherited)
380
+ : shadowable(record.kind, inherited, webIdl);
381
+ return shadowing ? { name, holder: record.kind, shadows, effect: 'replace', locks: false } : undefined;
382
+ }
383
+ if (record.kind === 'global')
384
+ return undefined;
385
+ const structuralMember = typeof change.member === 'string' && structural[record.kind]?.has(change.member);
386
+ if (!addable.has(record.kind) || structuralMember || namedProperties(holder, record))
387
+ return undefined;
388
+ return { name, holder: record.kind, effect: 'add', locks: false };
389
+ }
390
+ /**
391
+ * Whether a catalog member is a Web IDL member: one that IDL declares, or that the Web IDL binding synthesizes (§4
392
+ * Sources: a prototype's `constructor`, a stringifier's `toString`, `@@iterator`), and not one of the ECMAScript
393
+ * intrinsics a Web IDL chain ends in (`Object.prototype.toString`), which are no part of its IDL chain.
394
+ */
395
+ function webIdlOf(catalog) {
396
+ let specs = ecmascriptSpecsOf.get(catalog);
397
+ if (specs === undefined)
398
+ ecmascriptSpecsOf.set(catalog, (specs = ecmascriptSpecs(catalog)));
399
+ const ecmascript = specs;
400
+ return (member) => !ecmascript.has(member.spec);
401
+ }
402
+ const ecmascriptSpecsOf = new WeakMap();
403
+ /**
404
+ * A change of the prototype of `holder`. One that sets the prototype the holder already has, or a value that is neither
405
+ * an object nor `null` (a TypeError, or ignored by the `__proto__` setter), changes nothing. The same holder in two
406
+ * windows is two objects, so a holder's prototype is known to be the one given only when both are reached through one
407
+ * window expression (§3 Undecidable outcomes, §4 Other windows). Any other on an immutable prototype exotic object is
408
+ * an ineffective write (§3), which may instead change nothing when the value may be the prototype it has (§3 No-op or
409
+ * ineffective).
410
+ */
411
+ function prototypeChange(holder, record, { proto, sameWindow }) {
412
+ const [only] = proto;
413
+ const isCurrent = (value) => value.kind === 'constant' ? value.value === null && record.proto === null : value.kind === 'holder' && value.holder === record.proto;
414
+ if (proto.length === 1 && only !== undefined && isCurrent(only) && (only.kind === 'constant' || sameWindow))
415
+ return undefined;
416
+ if (proto.every((value) => value.kind === 'constant' && value.value !== null))
417
+ return undefined;
418
+ const name = prototypeTarget(holder);
419
+ if (record.immutablePrototype === true) {
420
+ // What may be the prototype the holder has: a value the analyzer can't see, or its prototype, maybe another window's.
421
+ const mayChangeNothing = proto.some((value) => opaque(value) || isCurrent(value));
422
+ return { name, holder: record.kind, effect: 'prototype-change', reason: 'immutable-prototype', mayChangeNothing };
423
+ }
424
+ return { name, holder: record.kind, effect: 'prototype-change', locks: false };
425
+ }
426
+ /** The holders a member can be added to (§3): native prototypes, constructors and namespaces. */
427
+ const addable = new Set(['prototype', 'constructor', 'namespace']);
428
+ /**
429
+ * Members every constructor or prototype has natively, although the catalog doesn't list them as targets there (§4):
430
+ * a function's `length` and `name`, and a `prototype` and `constructor` where the catalog has none (a legacy callback
431
+ * interface object, a prototype without an interface object, `HTMLDocument.prototype`). Where Web IDL or ECMA-262
432
+ * defines them, `X.prototype` and `X.prototype.constructor` are targets, which a write finds first.
433
+ */
434
+ const structural = {
435
+ constructor: new Set(['length', 'name', 'prototype']),
436
+ prototype: new Set(['constructor']),
437
+ };
438
+ /**
439
+ * The holders that are platform objects implementing the interfaces of the attributes on them: the global object, and
440
+ * the platform singletons. Interface prototype objects, interface objects and namespaces implement none.
441
+ */
442
+ const platformObjects = new Set(['global', 'instance']);
443
+ /**
444
+ * The outcome of a write to a catalog member, or undefined when it has none that is plainly a mutation. `implemented`
445
+ * says the object written implements the interface of the member, as a Web IDL attribute's setter checks.
446
+ */
447
+ function memberEffect(change, member, implemented) {
448
+ const { descriptor } = member;
449
+ switch (change.kind) {
450
+ case 'set':
451
+ return setEffect(change, member, implemented);
452
+ case 'delete':
453
+ if (descriptor.configurable)
454
+ return { effect: 'delete', locks: false };
455
+ return { effect: 'delete', reason: 'non-configurable', mayChangeNothing: false };
456
+ case 'define':
457
+ return defineEffect(change.descriptor, descriptor);
458
+ }
459
+ }
460
+ /** The outcome of a define of a catalog member whose descriptor in the unmutated catalog is `current`. */
461
+ function defineEffect(defined, current) {
462
+ if (defined.unseen.length > 0) {
463
+ // §3 Undecidable outcomes: what the analyzer can't see may make the define throw, change nothing or change the
464
+ // member. A replace when an own `value` comes after every spread or key that doesn't fold, and doesn't read the
465
+ // same slot, and a descriptor-change otherwise. The evidence is every part it can't see or decide.
466
+ const effect = defined.value !== undefined && !defined.value.same ? 'replace' : 'descriptor-change';
467
+ // A non-configurable member it can't change: it is ineffective, or changes nothing (§3 No-op or ineffective).
468
+ if (!current.configurable && !redefinable(defined, current)) {
469
+ return { effect, reason: 'non-configurable', mayChangeNothing: true };
470
+ }
471
+ // A getter whose key doesn't fold is unseen, and may also be what an attribute's setter joins.
472
+ const sites = [...new Set([...defined.unseen, ...defined.undecided])].sort((a, b) => a.start - b.start);
473
+ return { effect, locks: 'unknown', undecidable: { on: 'descriptor', sites } };
474
+ }
475
+ if (!current.configurable)
476
+ return refusedDefine(defined, current);
477
+ const locks = locksOf(defined, current);
478
+ // A new getter/setter pair, or a data property turned into an accessor.
479
+ if (isAccessor(defined))
480
+ return { effect: 'descriptor-change', locks };
481
+ // A data descriptor over an accessor turns it into a data property, whose value is new (ADR 0011).
482
+ if (current.type === 'accessor' && isData(defined))
483
+ return { effect: 'replace', locks };
484
+ if (defined.value !== undefined && !defined.value.same)
485
+ return { effect: 'replace', locks };
486
+ return attributesChange(defined, current, locks);
487
+ }
488
+ /**
489
+ * A seen define of a non-configurable member (ECMA-262 ValidateAndApplyPropertyDescriptor): it is refused, an
490
+ * ineffective write, when it makes the member configurable, gives another `enumerable`, turns data into an accessor or
491
+ * back, gives an accessor a getter or setter, or gives a non-writable data property `writable: true` or a new value.
492
+ * When only fields the analyzer can't decide, or a value that may be the member's own, refuse it, it may instead change
493
+ * nothing (§3 No-op or ineffective). A define it doesn't refuse gives nothing here: that leaves out only a define that
494
+ * changes the one non-configurable writable data property, `Array.prototype.length`, as before this ticket.
495
+ */
496
+ function refusedDefine(defined, current) {
497
+ const differs = (given, native) => given === undefined ? false : given === 'unknown' ? 'unknown' : given !== native;
498
+ const refusals = [differs(defined.configurable, false), differs(defined.enumerable, current.enumerable)];
499
+ if (current.type === 'accessor')
500
+ refusals.push(isData(defined) || defined.get || defined.set);
501
+ else {
502
+ refusals.push(isAccessor(defined));
503
+ if (!current.writable) {
504
+ refusals.push(differs(defined.writable, false));
505
+ if (defined.value !== undefined && !defined.value.same)
506
+ refusals.push(defined.value.fresh || 'unknown');
507
+ }
508
+ }
509
+ if (!refusals.some((refused) => refused !== false))
510
+ return undefined;
511
+ // The effect it intended, as `defineEffect` gives it for a configurable member (ADR 0011): a new value is a replace,
512
+ // a getter or setter, or attributes alone, a descriptor-change.
513
+ const newValue = (current.type === 'accessor' && isData(defined)) || (defined.value !== undefined && !defined.value.same);
514
+ const effect = !isAccessor(defined) && newValue ? 'replace' : 'descriptor-change';
515
+ return { effect, reason: 'non-configurable', mayChangeNothing: !refusals.includes(true) };
516
+ }
517
+ /**
518
+ * Whether a define whose descriptor is partly unseen can change a non-configurable member (ECMA-262
519
+ * ValidateAndApplyPropertyDescriptor): only a writable data property can, and only when the fields the define shows
520
+ * don't make it throw (`configurable: true`, another `enumerable`, an accessor). Every other define of it throws or
521
+ * changes nothing: an ineffective write.
522
+ */
523
+ function redefinable(defined, current) {
524
+ if (current.type !== 'data' || !current.writable || isAccessor(defined) || defined.configurable === true)
525
+ return false;
526
+ return defined.enumerable === undefined || defined.enumerable === 'unknown' || defined.enumerable === current.enumerable;
527
+ }
528
+ /**
529
+ * The attributes a property a define creates starts from (ECMA-262 ValidateAndApplyPropertyDescriptor): a field the
530
+ * descriptor lacks is `false` on a new property, which is a data property unless the descriptor gives `get` or `set`.
531
+ */
532
+ const created = { type: 'data', writable: false, enumerable: false, configurable: false };
533
+ /**
534
+ * Whether a define leaves its target locked (§8): non-configurable, or a data property that is non-writable, once
535
+ * the define applies its descriptor over `base`, the member's descriptor in the unmutated catalog (or `created` for a
536
+ * new property). A field the descriptor lacks keeps `base`'s attribute. A configurable accessor never locks, with or
537
+ * without a setter. `unknown` when the descriptor has parts the analyzer can't see, or when an attribute that decides it
538
+ * can't be decided.
539
+ */
540
+ function locksOf(defined, base) {
541
+ if (defined.unseen.length > 0)
542
+ return 'unknown';
543
+ const configurable = defined.configurable ?? base.configurable;
544
+ const data = isAccessor(defined) ? false : isData(defined) || base.type === 'data';
545
+ const writable = data ? (defined.writable ?? (base.type === 'data' ? base.writable : false)) : true;
546
+ if (configurable === false || writable === false)
547
+ return true;
548
+ return configurable === 'unknown' || writable === 'unknown' ? 'unknown' : false;
549
+ }
550
+ /**
551
+ * The effect of a [[Set]] (`assign`, `bulk-assign`) of a catalog member (§3 What is a platform mutation):
552
+ * - A writable data property is replaced, unless the write puts back the value the member holds, which changes
553
+ * nothing.
554
+ * - A Web IDL attribute follows its `assign` kind (§4 Member data). A `[Replaceable]` setter replaces the attribute
555
+ * with an own data property of the object written, holding the new value, so it is a `replace` (ADR 0011). It first
556
+ * checks that the object written implements the interface, and throws a TypeError before writing when it doesn't: on
557
+ * an interface prototype object (`WorkerGlobalScope.prototype.performance = p`), it changes nothing. `setter`,
558
+ * `put-forwards` and `event-handler` run the attribute's setter: API use, and no mutation.
559
+ * `Window.opener`'s setter (IDL kind `setter`) redefines it as an own data property holding the value for any value
560
+ * but `null`, as a `[Replaceable]` setter does (§3): `null` is API use, a value shown to be non-null a
561
+ * `replace`, and a value that may be `null`, a write-back of what its getter returns among them, a `replace` whose
562
+ * outcome can't be decided.
563
+ * - An ECMAScript accessor with a setter is API use too (other than `Object.prototype.__proto__`, whose setter is a
564
+ * `set-prototype`, `writes.ts`).
565
+ * - A non-writable data property, a `readonly` or `lenient-setter` attribute, and an ECMAScript accessor without a
566
+ * setter can't change: ineffective writes (§3), `non-writable`, `lenient-setter` and `readonly`. A lenient setter
567
+ * first checks that the object written implements its interface, and throws before writing when it doesn't.
568
+ */
569
+ function setEffect(change, member, implemented) {
570
+ const { descriptor } = member;
571
+ const replace = { effect: 'replace', locks: false };
572
+ if (descriptor.type === 'data') {
573
+ if (!descriptor.writable)
574
+ return { effect: 'replace', reason: 'non-writable', mayChangeNothing: false };
575
+ return change.same ? undefined : replace;
576
+ }
577
+ if (member.assign === 'replaceable')
578
+ return implemented ? replace : undefined;
579
+ if (isOpener(member) && implemented && change.value !== undefined && change.value.isNull !== true) {
580
+ const { isNull, site } = change.value;
581
+ return isNull === false ? replace : { ...replace, undecidable: { on: 'value', sites: [site] } };
582
+ }
583
+ // A `[LegacyLenientSetter]` setter does nothing, once it has checked that the object written implements its interface.
584
+ if (member.assign === 'lenient-setter') {
585
+ return implemented ? { effect: 'replace', reason: 'lenient-setter', mayChangeNothing: false, quiet: true } : undefined;
586
+ }
587
+ // An accessor without a setter: a readonly attribute, or an ECMAScript getter.
588
+ if (!descriptor.setter)
589
+ return { effect: 'replace', reason: 'readonly', mayChangeNothing: false };
590
+ return undefined;
591
+ }
592
+ /** Whether a member is `Window.opener`, the one Web IDL attribute whose [[Set]] outcome follows the value (§3, §4). */
593
+ const isOpener = (member) => member.owner === 'Window' && member.member === 'opener' && member.kind === 'attribute';
594
+ /**
595
+ * ECMA-262 IsAccessorDescriptor and IsDataDescriptor. A descriptor never shows both (`writes.ts`): ToPropertyDescriptor
596
+ * refuses it.
597
+ */
598
+ const isAccessor = (defined) => defined.get || defined.set;
599
+ const isData = (defined) => defined.value !== undefined || defined.writable !== undefined;
600
+ /**
601
+ * The outcome of a define that keeps the member's value: a `descriptor-change` when an attribute it gives differs from
602
+ * the member's. When none plainly does, but one can't be decided, it is a `descriptor-change` whose outcome can't be
603
+ * decided (§3 Undecidable outcomes). One that changes nothing is no mutation.
604
+ */
605
+ function attributesChange(defined, descriptor, locks) {
606
+ const attributes = [
607
+ [defined.enumerable, descriptor.enumerable],
608
+ [defined.configurable, descriptor.configurable],
609
+ ...(descriptor.type === 'data' ? [[defined.writable, descriptor.writable]] : []),
610
+ ];
611
+ if (attributes.some(([given, native]) => given !== undefined && given !== 'unknown' && given !== native)) {
612
+ return { effect: 'descriptor-change', locks };
613
+ }
614
+ if (attributes.some(([given]) => given === 'unknown')) {
615
+ return { effect: 'descriptor-change', locks, undecidable: { on: 'descriptor', sites: defined.undecided } };
616
+ }
617
+ return undefined;
618
+ }
619
+ /**
620
+ * The catalog targets a write of a member on an object the analyzer can't resolve could mutate (§5 Partially resolved
621
+ * writes): every target of the realm with that member whose outcome §3's table could make a mutation, taking the object
622
+ * written to be one that implements the member's interface. API use, a constant member, a define that changes
623
+ * nothing and an ineffective write are left out. Sorted by name.
624
+ */
625
+ export function candidateTargets(change, catalog, realm) {
626
+ const spelling = typeof change.member === 'symbol' ? memberSpelling(change.member) : change.member;
627
+ // A define whose descriptor can be several could make the write a mutation through any of them.
628
+ const changes = change.kind === 'define' && change.alternatives !== undefined
629
+ ? change.alternatives.flatMap((descriptor) => (descriptor === 'refused' ? [] : [{ ...change, descriptor }]))
630
+ : [change];
631
+ return (targetsByMember(catalog, realm).get(spelling) ?? [])
632
+ .filter(([, target]) =>
633
+ // An ineffective write (§3) can't change its target, so it is no candidate either.
634
+ changes.some((each) => {
635
+ const outcome = memberEffect(each, target, true);
636
+ return outcome !== undefined && !('reason' in outcome);
637
+ }))
638
+ .map(([name]) => name);
639
+ }
640
+ /** How the catalog spells a member key: its string, or a well-known symbol's `@@` name. */
641
+ const memberSpelling = (key) => memberName('', key).slice(1);
642
+ /** A realm's targets, by the member they name, each list in name order. */
643
+ function targetsByMember(catalog, realm) {
644
+ let realms = targetIndexes.get(catalog);
645
+ if (realms === undefined)
646
+ targetIndexes.set(catalog, (realms = new Map()));
647
+ let index = realms.get(realm);
648
+ if (index === undefined) {
649
+ const built = new Map();
650
+ const targets = catalog.targets[realm];
651
+ for (const name of Object.keys(targets).sort()) {
652
+ const target = targets[name];
653
+ const list = built.get(target.member);
654
+ if (list === undefined)
655
+ built.set(target.member, [[name, target]]);
656
+ else
657
+ list.push([name, target]);
658
+ }
659
+ realms.set(realm, (index = built));
660
+ }
661
+ return index;
662
+ }
663
+ const targetIndexes = new WeakMap();
664
+ export function wildcardOutcome(write, effect, catalog, realm, members, prefixes) {
665
+ const holders = catalog.holders[realm];
666
+ const record = Object.hasOwn(holders, write.holder) ? holders[write.holder] : undefined;
667
+ // No wildcard lands on the global object.
668
+ if (record === undefined || record.kind === 'global')
669
+ return undefined;
670
+ if (effect === 'add') {
671
+ // An add on the global object or a singleton gives nothing.
672
+ if (!addable.has(record.kind) || namedProperties(write.holder, record))
673
+ return undefined;
674
+ const locks = write.change.kind === 'define' ? locksOf(write.change.descriptor, created) : false;
675
+ return { kind: 'finding', holder: record.kind, locks, mixed: false };
676
+ }
677
+ const { change } = write;
678
+ if (change.kind === 'prototype')
679
+ return undefined;
680
+ const effective = [];
681
+ const refused = [];
682
+ // A candidate set of members narrows the matched set to its candidates, and prefixes to the members of the holder and
683
+ // its chain that start with one of them (§5).
684
+ const matched = members === undefined && prefixes === undefined
685
+ ? wildcardMembers(write.holder, catalog, realm)
686
+ : narrowedMatches(write.holder, members, prefixes ?? [], catalog, realm).map(({ member }) => member);
687
+ for (const member of matched) {
688
+ for (const outcome of outcomesOf({ ...write, change: { ...change, member } }, catalog, realm)) {
689
+ if (isIneffective(outcome))
690
+ refused.push(outcome);
691
+ else
692
+ effective.push(outcome);
693
+ }
694
+ }
695
+ if (effective.length > 0) {
696
+ const locks = mostLocking(effective.map((each) => each.locks));
697
+ const undecidable = effective.find((each) => each.undecidable !== undefined)?.undecidable;
698
+ const mixed = refused.length > 0;
699
+ return { kind: 'finding', holder: record.kind, locks, mixed, ...(undecidable === undefined ? {} : { undecidable }) };
700
+ }
701
+ if (refused.length === 0)
702
+ return undefined;
703
+ const byReason = new Map();
704
+ for (const each of refused) {
705
+ const first = byReason.get(each.reason);
706
+ byReason.set(each.reason, first === undefined ? each : { ...first, mayChangeNothing: first.mayChangeNothing || each.mayChangeNothing });
707
+ }
708
+ return { kind: 'ineffective', holder: record.kind, refused: [...byReason.values()] };
709
+ }
710
+ /**
711
+ * The catalog members a wildcard over `holder` matches (§3): the holder's own members. A singleton instance also
712
+ * matches its interface's members, those of its interface prototype object (`History.prototype` for `history`,
713
+ * `Storage.prototype` for `localStorage`, `Navigator.prototype` with its mixins for `navigator`), without the structural
714
+ * `constructor` and `@@toStringTag`, which are no members of the interface, and not those of shared ancestors.
715
+ */
716
+ export function wildcardMembers(holder, catalog, realm) {
717
+ const own = ownMembers(holder, catalog, realm);
718
+ const record = catalog.holders[realm][holder];
719
+ const singleton = Object.hasOwn(catalog.singletons[realm], holder) ? catalog.singletons[realm][holder] : undefined;
720
+ if (record?.kind !== 'instance' || singleton === undefined || !('interface' in singleton))
721
+ return own;
722
+ const inherited = ownMembers(`${singleton.interface}.prototype`, catalog, realm).filter((member) => member !== 'constructor' && member !== wellKnownSymbol('toStringTag'));
723
+ return [...own, ...inherited.filter((member) => !own.includes(member))];
724
+ }
725
+ /**
726
+ * The catalog targets a wildcard over `holder` matches among `members` (§5), by name: the holder's own, and for a
727
+ * singleton instance, its interface prototype's.
728
+ */
729
+ export function wildcardTargets(holder, members, catalog, realm) {
730
+ const targets = catalog.targets[realm];
731
+ const singleton = Object.hasOwn(catalog.singletons[realm], holder) ? catalog.singletons[realm][holder] : undefined;
732
+ const prototype = singleton !== undefined && 'interface' in singleton ? `${singleton.interface}.prototype` : undefined;
733
+ return wildcardMembers(holder, catalog, realm)
734
+ .filter((member) => members.includes(member))
735
+ .map((member) => {
736
+ const own = memberName(holder, member);
737
+ return Object.hasOwn(targets, own) || prototype === undefined ? own : memberName(prototype, member);
738
+ });
739
+ }
740
+ /**
741
+ * The catalog members a wildcard over `holder` narrowed to a candidate set matches (§5), each once with its
742
+ * target: those among `members` (`wildcardTargets`), and those of the holder and its chain that start with one of
743
+ * `prefixes` (`prefixMembers`).
744
+ */
745
+ export function narrowedMatches(holder, members, prefixes, catalog, realm) {
746
+ const out = new Map();
747
+ if (members !== undefined) {
748
+ const keys = wildcardMembers(holder, catalog, realm).filter((member) => members.includes(member));
749
+ const targets = wildcardTargets(holder, members, catalog, realm);
750
+ keys.forEach((member, index) => out.set(member, targets[index]));
751
+ }
752
+ for (const prefix of prefixes) {
753
+ for (const { member, target } of prefixMembers(holder, prefix, catalog, realm))
754
+ if (!out.has(member))
755
+ out.set(member, target);
756
+ }
757
+ return [...out].map(([member, target]) => ({ member, target }));
758
+ }
759
+ /**
760
+ * The catalog members of `holder` and its catalog prototype chain whose key is a string that starts with `prefix`, each
761
+ * once, on its nearest owner, with the target that owner has: what a write there whose key starts with the prefix could
762
+ * name or shadow (§5 Pattern wildcards, ADR 0002). A well-known symbol member starts with no string.
763
+ */
764
+ export function prefixMembers(holder, prefix, catalog, realm) {
765
+ const holders = catalog.holders[realm];
766
+ const out = [];
767
+ const seen = new Set();
768
+ for (let at = holder; at !== null && Object.hasOwn(holders, at); at = holders[at].proto) {
769
+ for (const member of ownMembers(at, catalog, realm)) {
770
+ if (typeof member !== 'string' || !member.startsWith(prefix) || seen.has(member))
771
+ continue;
772
+ seen.add(member);
773
+ out.push({ member, target: memberName(at, member) });
774
+ }
775
+ }
776
+ return out;
777
+ }
778
+ /** The members the catalog lists on a holder itself, as keys, in name order. */
779
+ function ownMembers(holder, catalog, realm) {
780
+ let realms = ownMemberIndexes.get(catalog);
781
+ if (realms === undefined)
782
+ ownMemberIndexes.set(catalog, (realms = new Map()));
783
+ let index = realms.get(realm);
784
+ if (index === undefined)
785
+ realms.set(realm, (index = new Map()));
786
+ let members = index.get(holder);
787
+ if (members === undefined) {
788
+ const targets = catalog.targets[realm];
789
+ members = [];
790
+ for (const name of Object.keys(targets).sort()) {
791
+ const spelling = targets[name].member;
792
+ const key = spelling.startsWith('@@') ? wellKnownSymbol(spelling.slice(2)) : spelling;
793
+ if (memberName(holder, key) === name)
794
+ members.push(key);
795
+ }
796
+ index.set(holder, members);
797
+ }
798
+ return members;
799
+ }
800
+ const ownMemberIndexes = new WeakMap();