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,518 @@
1
+ // Patch shape (§5 Partially resolved writes): whether a write site whose holder the analyzer can't resolve keeps the
2
+ // original of the slot it writes, so that it may patch a native. A site keeps it when it reads the same (holder,
3
+ // member) first, in the same function, and that read is:
4
+ //
5
+ // - called, or `.call`/`.apply`/`.bind`-ed
6
+ // - passed to a call whose callee isn't a catalog native (`wrap(o.fetch)`, not `String(o.fetch)`)
7
+ // - stored: a variable's initializer, an assignment's value, an object literal's field or an array element
8
+ // - kept when present: `o.m = o.m || p`, `o.m ||= p`, `if (!o.m) o.m = p`, `o.m || (o.m = p)`
9
+ //
10
+ // A define also counts when its descriptor gives `get` or `set`, or when a `getOwnPropertyDescriptor` of the same slot
11
+ // comes before it. A delete counts only after the original was captured, by such a read or a descriptor read.
12
+ //
13
+ // The same (holder, member) is the same root variable, parameter or `this`, through the same constant property chain
14
+ // after aliases, with no redefinition of a root variable between the read and the write. A holder chosen by `?:`,
15
+ // `&&`, `||` or `??` among such expressions is the same when every part is.
16
+ import { calleeExpression, walkTree } from "./ast.js";
17
+ import { literalKey } from "./keys.js";
18
+ /** Functions, whose own code is apart from the code around them. */
19
+ const functionTypes = new Set(['FunctionDeclaration', 'FunctionExpression', 'ArrowFunctionExpression']);
20
+ export function slotSignatures(context) {
21
+ /** The constant key a member expression reads: its name, or a computed key that folds. */
22
+ const keyOf = (member) => {
23
+ const property = member['property'];
24
+ if (member['computed'] === true)
25
+ return context.fold(property);
26
+ return property.type === 'Identifier' ? property['name'] : undefined;
27
+ };
28
+ /** The variable an identifier names, followed through aliases that have one definition, a plain name or member. */
29
+ const rooted = (id, roots, depth) => {
30
+ const key = context.definitions.keyOf(id);
31
+ if (key === undefined)
32
+ return undefined;
33
+ const definitions = context.definitions.of(key);
34
+ const [only] = definitions;
35
+ if (definitions.length === 1 &&
36
+ only?.site.type === 'VariableDeclarator' &&
37
+ only.pattern.type === 'Identifier' &&
38
+ only.source !== undefined) {
39
+ const source = unwrapped(only.source);
40
+ if (source.type === 'Identifier' || source.type === 'MemberExpression' || source.type === 'ThisExpression') {
41
+ // An alias past the cap is taken as the root, a hit of the cap (§18).
42
+ if (depth >= context.aliasDepth)
43
+ context.aliasCut(id);
44
+ else {
45
+ const followed = signature(source, roots, depth + 1);
46
+ if (followed !== undefined)
47
+ return followed;
48
+ }
49
+ }
50
+ }
51
+ roots.push(key);
52
+ return `v(${key.scope === 'global' ? 'global' : key.scope.start}:${key.name})`;
53
+ };
54
+ /**
55
+ * A holder expression's signature, the same for two expressions that give the same holder, with the variables it
56
+ * reads in `roots`; none when it is no such expression (a call, a literal).
57
+ */
58
+ const signature = (expression, roots, depth = 0) => {
59
+ const node = unwrapped(expression);
60
+ switch (node.type) {
61
+ case 'Identifier':
62
+ return rooted(node, roots, depth);
63
+ case 'ThisExpression':
64
+ return 'this';
65
+ case 'MemberExpression': {
66
+ const object = signature(node['object'], roots, depth);
67
+ const key = keyOf(node);
68
+ return object === undefined || key === undefined ? undefined : `m(${object},${keySignature(key)})`;
69
+ }
70
+ case 'ConditionalExpression': {
71
+ const parts = ['test', 'consequent', 'alternate'].map((part) => signature(node[part], roots, depth));
72
+ return parts.every((part) => part !== undefined) ? `c(${parts.join(',')})` : undefined;
73
+ }
74
+ case 'LogicalExpression': {
75
+ const parts = ['left', 'right'].map((part) => signature(node[part], roots, depth));
76
+ return parts.every((part) => part !== undefined) ? `l${node['operator']}(${parts.join(',')})` : undefined;
77
+ }
78
+ default:
79
+ return undefined;
80
+ }
81
+ };
82
+ /** Whether a root variable is defined again between `from` and `to`. */
83
+ const redefined = (roots, from, to) => roots.some((key) => context.definitions.of(key).some(({ site }) => site.start >= from && site.start < to));
84
+ /** A key expression's signature (`Slots.keySignature`). */
85
+ const keyExpression = (expression, roots) => {
86
+ const node = unwrapped(expression);
87
+ switch (node.type) {
88
+ case 'Literal': {
89
+ const literal = literalKey(node);
90
+ return literal === undefined ? undefined : keySignature(literal);
91
+ }
92
+ case 'Identifier':
93
+ return rooted(node, roots, 0);
94
+ case 'ThisExpression':
95
+ return 'this';
96
+ case 'MemberExpression': {
97
+ const object = keyExpression(node['object'], roots);
98
+ const property = node['property'];
99
+ const key = node['computed'] === true
100
+ ? keyExpression(property, roots)
101
+ : property.type === 'Identifier'
102
+ ? JSON.stringify(property['name'])
103
+ : undefined;
104
+ return object === undefined || key === undefined ? undefined : `m(${object},${key})`;
105
+ }
106
+ // A key built from such parts by `+` or a template, as a prefix and a name (`"get" + k`).
107
+ case 'BinaryExpression': {
108
+ if (node['operator'] !== '+')
109
+ return undefined;
110
+ const parts = ['left', 'right'].map((part) => keyExpression(node[part], roots));
111
+ return parts.every((part) => part !== undefined) ? `+(${parts.join(',')})` : undefined;
112
+ }
113
+ case 'TemplateLiteral': {
114
+ const quasis = node['quasis'].map((quasi) => quasi['value'].cooked);
115
+ const expressions = node['expressions'].map((each) => keyExpression(each, roots));
116
+ if (quasis.some((cooked) => cooked === null) || expressions.some((each) => each === undefined))
117
+ return undefined;
118
+ return `t(${JSON.stringify(quasis)},${expressions.join(',')})`;
119
+ }
120
+ default:
121
+ return undefined;
122
+ }
123
+ };
124
+ return { signature: (expression, roots) => signature(expression, roots), keySignature: keyExpression, redefined };
125
+ }
126
+ /** Patch-shape and key-reuse judgements over one program: its parents, computed once. */
127
+ export function patchShapes(context) {
128
+ /** The constant key a member expression reads: its name, or a computed key that folds. */
129
+ const keyOf = (member) => {
130
+ const property = member['property'];
131
+ if (member['computed'] === true)
132
+ return context.fold(property);
133
+ return property.type === 'Identifier' ? property['name'] : undefined;
134
+ };
135
+ const { signature, keySignature: keyExpression, redefined } = slotSignatures(context);
136
+ let parents;
137
+ const parentOf = (node) => {
138
+ if (parents === undefined) {
139
+ const built = new Map();
140
+ walkTree(context.program, {
141
+ enter(child, parent) {
142
+ if (parent !== null)
143
+ built.set(child, parent);
144
+ },
145
+ });
146
+ parents = built;
147
+ }
148
+ return parents.get(node);
149
+ };
150
+ /** The function (or the program) whose own code a node is in. */
151
+ const ownerOf = (node) => {
152
+ let at = parentOf(node);
153
+ while (at !== undefined && !functionTypes.has(at.type))
154
+ at = parentOf(at);
155
+ return at ?? context.program;
156
+ };
157
+ /** The member expressions and calls in `owner`'s own code, nested functions left out, in document order. */
158
+ const members = new Map();
159
+ const membersOf = (owner) => {
160
+ const cached = members.get(owner);
161
+ if (cached !== undefined)
162
+ return cached;
163
+ const found = [];
164
+ walkTree(owner, {
165
+ enter(node) {
166
+ if (node !== owner && functionTypes.has(node.type))
167
+ return false;
168
+ if (node.type === 'MemberExpression' || node.type === 'CallExpression')
169
+ found.push(node);
170
+ else if (node.type === 'BinaryExpression' && node['operator'] === 'in')
171
+ found.push(node);
172
+ },
173
+ });
174
+ members.set(owner, found);
175
+ return found;
176
+ };
177
+ /**
178
+ * The expression a value's use is judged at: past the parentheses, optional chains, `?:` arms, the last expression
179
+ * of a comma sequence and the operands of `&&`, `||` and `??` it sits in. `present` says it passed the left of an
180
+ * `||` or `??`, which keeps it when present.
181
+ */
182
+ const useOf = (node) => {
183
+ let use = node;
184
+ let present = false;
185
+ for (;;) {
186
+ const parent = parentOf(use);
187
+ if (parent === undefined)
188
+ return { use, parent, present };
189
+ const transparent = parent.type === 'ParenthesizedExpression' ||
190
+ parent.type === 'ChainExpression' ||
191
+ (parent.type === 'ConditionalExpression' && parent['test'] !== use) ||
192
+ (parent.type === 'SequenceExpression' && parent['expressions'].at(-1) === use) ||
193
+ parent.type === 'LogicalExpression';
194
+ if (!transparent)
195
+ return { use, parent, present };
196
+ if (parent.type === 'LogicalExpression' && parent['left'] === use && parent['operator'] !== '&&')
197
+ present = true;
198
+ use = parent;
199
+ }
200
+ };
201
+ /** Whether `node` lies within `outer`. */
202
+ const within = (node, outer) => node.start >= outer.start && node.end <= outer.end;
203
+ /** Whether a read of the original, `read`, keeps it for the write `write`. */
204
+ const keeps = (read, write) => {
205
+ const { use, parent, present } = useOf(read);
206
+ if (parent === undefined)
207
+ return false;
208
+ // Kept when present: `o.m = o.m || p`.
209
+ if (parent === write.site && use === write.value && present)
210
+ return true;
211
+ // Tested before the write: `if (!o.m) o.m = p`, `o.m || (o.m = p)`, `o.m ? … : (o.m = p)`.
212
+ if (testedBefore(read, write.site))
213
+ return true;
214
+ switch (parent.type) {
215
+ case 'CallExpression':
216
+ case 'NewExpression':
217
+ if (parent['callee'] === use)
218
+ return true;
219
+ return parent['arguments'].includes(use) && !context.isCatalogNative(parent['callee']);
220
+ case 'MemberExpression': {
221
+ if (parent['object'] !== use)
222
+ return false;
223
+ const key = keyOf(parent);
224
+ return key === 'call' || key === 'apply' || key === 'bind';
225
+ }
226
+ case 'VariableDeclarator':
227
+ return parent['init'] === use;
228
+ case 'AssignmentExpression':
229
+ // The write's own value is no store: `o.m = o.m` writes it back.
230
+ return parent['right'] === use && parent !== write.site;
231
+ case 'Property':
232
+ // A bulk mechanism's own field is no store: `Object.assign(o, { m: o.m })` writes it back.
233
+ return parent['value'] === use && parent !== write.site && parentOf(parent)?.type === 'ObjectExpression';
234
+ case 'ArrayExpression':
235
+ return true;
236
+ default:
237
+ return false;
238
+ }
239
+ };
240
+ /**
241
+ * Whether `read` is tested for presence so that `site` runs only when the original is absent, which keeps it when
242
+ * present: the read itself, negated, its `typeof` compared with a string, or the read compared with `null`,
243
+ * `undefined`, `void` or a string, as the test of an `if` or `?:` with `site` in the arm that runs when it is absent,
244
+ * or as the left of `&&`, `||` or `??` with `site` on the right (`if (!o.m) o.m = p`, `o.m || (o.m = p)`, not
245
+ * `if (o.m) o.m = p` nor `o.m && (o.m = p)`). Parts of a test joined by `&&` or `||` carry what they imply.
246
+ */
247
+ const testedBefore = (read, site) => {
248
+ // What the value at `child` being truthy, or falsy, says of the original, and what that value is: the read itself,
249
+ // its `typeof`, or a boolean derived from it.
250
+ let sense = { truthy: 'present', falsy: 'absent' };
251
+ let mode = 'read';
252
+ for (let child = read, at = parentOf(read); at !== undefined; child = at, at = parentOf(at)) {
253
+ switch (at.type) {
254
+ case 'ParenthesizedExpression':
255
+ case 'ChainExpression':
256
+ continue;
257
+ case 'UnaryExpression':
258
+ if (mode === 'read' && at['operator'] === 'typeof')
259
+ mode = 'typeof';
260
+ else if (mode !== 'typeof' && at['operator'] === '!') {
261
+ sense = { truthy: sense.falsy, falsy: sense.truthy };
262
+ mode = 'boolean';
263
+ }
264
+ else
265
+ return false;
266
+ continue;
267
+ case 'BinaryExpression': {
268
+ if (mode === 'boolean')
269
+ return false;
270
+ const compared = comparison(at, child, mode);
271
+ if (compared === undefined)
272
+ return false;
273
+ sense = compared;
274
+ mode = 'boolean';
275
+ continue;
276
+ }
277
+ case 'LogicalExpression': {
278
+ if (mode === 'typeof')
279
+ return false;
280
+ const operator = at['operator'];
281
+ if (at['left'] === child && within(site, at['right'])) {
282
+ if (operator === '&&')
283
+ return sense.truthy === 'absent';
284
+ if (operator === '||')
285
+ return sense.falsy === 'absent';
286
+ return mode === 'read';
287
+ }
288
+ // A part of a larger test: the whole being truthy says each part of an `&&` is, and falsy each part of an `||`.
289
+ if (operator === '&&')
290
+ sense = { truthy: sense.truthy, falsy: undefined };
291
+ else if (operator === '||')
292
+ sense = { truthy: undefined, falsy: sense.falsy };
293
+ else
294
+ return false;
295
+ mode = 'boolean';
296
+ continue;
297
+ }
298
+ case 'IfStatement':
299
+ case 'ConditionalExpression': {
300
+ if (at['test'] !== child || mode === 'typeof')
301
+ return false;
302
+ const [consequent, alternate] = [at['consequent'], at['alternate']];
303
+ if (consequent != null && within(site, consequent))
304
+ return sense.truthy === 'absent';
305
+ if (alternate != null && within(site, alternate))
306
+ return sense.falsy === 'absent';
307
+ return false;
308
+ }
309
+ default:
310
+ return false;
311
+ }
312
+ }
313
+ return false;
314
+ };
315
+ const patchShaped = (write) => {
316
+ const roots = [];
317
+ const holder = signature(write.holder, roots);
318
+ if (holder === undefined)
319
+ return false;
320
+ const slot = `m(${holder},${keySignature(write.change.member)})`;
321
+ const change = write.change;
322
+ if (change.kind === 'define' && (change.descriptor.get || change.descriptor.set))
323
+ return true;
324
+ const owner = ownerOf(write.site);
325
+ for (const node of membersOf(owner)) {
326
+ // Only what comes before the write, or is part of it, counts, and never the member expression written.
327
+ if (node.start >= write.site.end || node === write.target)
328
+ continue;
329
+ if (write.target !== undefined && within(node, write.target))
330
+ continue;
331
+ if (redefined(roots, node.end, write.site.start))
332
+ continue;
333
+ if (node.type === 'BinaryExpression')
334
+ continue;
335
+ if (node.type === 'CallExpression') {
336
+ // A descriptor read of the same slot captures the original for a define or a delete.
337
+ if (change.kind === 'set' || !context.isDescriptorReader(calleeExpression(node['callee'])))
338
+ continue;
339
+ const [object, key] = node['arguments'];
340
+ if (object === undefined || key === undefined || node.end > write.site.start)
341
+ continue;
342
+ const readRoots = [];
343
+ const read = signature(object, readRoots);
344
+ const member = context.fold(key);
345
+ if (read === holder && member !== undefined && keySignature(member) === keySignature(change.member))
346
+ return true;
347
+ continue;
348
+ }
349
+ const readRoots = [];
350
+ const object = signature(node['object'], readRoots);
351
+ const key = keyOf(node);
352
+ if (object === undefined || key === undefined || `m(${object},${keySignature(key)})` !== slot)
353
+ continue;
354
+ if (keeps(node, write))
355
+ return true;
356
+ }
357
+ return write.keepsPresent === true;
358
+ };
359
+ /**
360
+ * Whether a function reads the slot of its `index`th parameter, as a key, from the holder `holder` signs: through
361
+ * another parameter whose argument in `args` gives that holder, or through an expression that gives it.
362
+ */
363
+ const readsParameter = (fn, index, args, holder) => {
364
+ const params = fn['params'];
365
+ /** The parameter an identifier in the function's own code reads, by its index; -1 for none. */
366
+ const parameterOf = (id) => {
367
+ const variable = context.definitions.keyOf(id);
368
+ if (variable?.scope !== fn)
369
+ return -1;
370
+ return params.findIndex((param) => param.type === 'Identifier' && param['name'] === variable.name);
371
+ };
372
+ if (params[index]?.type !== 'Identifier')
373
+ return false;
374
+ return membersOf(fn).some((node) => {
375
+ if (node.type !== 'MemberExpression' || node['computed'] !== true)
376
+ return false;
377
+ const property = unwrapped(node['property']);
378
+ if (property.type !== 'Identifier' || parameterOf(property) !== index)
379
+ return false;
380
+ const object = unwrapped(node['object']);
381
+ const at = object.type === 'Identifier' ? parameterOf(object) : -1;
382
+ const arg = at < 0 ? undefined : args[at];
383
+ if (at >= 0)
384
+ return arg !== undefined && arg.type !== 'SpreadElement' && signature(arg, []) === holder;
385
+ return signature(object, []) === holder;
386
+ });
387
+ };
388
+ const reuse = (write) => {
389
+ const roots = [];
390
+ const holder = signature(write.object, roots);
391
+ const key = holder === undefined ? undefined : keyExpression(write.key, roots);
392
+ if (holder === undefined || key === undefined)
393
+ return undefined;
394
+ const slot = `m(${holder},${key})`;
395
+ const signs = (object, member) => {
396
+ if (member === undefined || signature(object, []) !== holder)
397
+ return false;
398
+ return keyExpression(member, []) === key;
399
+ };
400
+ /** The key a member expression reads, signed as `keyExpression` signs it. */
401
+ const memberKey = (member) => {
402
+ const property = member['property'];
403
+ if (member['computed'] === true)
404
+ return keyExpression(property, []);
405
+ return property.type === 'Identifier' ? JSON.stringify(property['name']) : undefined;
406
+ };
407
+ const reads = new Set();
408
+ const descriptors = new Set();
409
+ let keptWhenPresent = write.keepsPresent === true;
410
+ const owner = ownerOf(write.site);
411
+ for (const node of membersOf(owner)) {
412
+ // Only what comes before the write, or is part of it, counts, and never the member expression written.
413
+ if (node.start >= write.site.end || node === write.target)
414
+ continue;
415
+ if (write.target !== undefined && within(node, write.target))
416
+ continue;
417
+ if (redefined(roots, node.end, write.site.start))
418
+ continue;
419
+ if (node.type === 'BinaryExpression') {
420
+ // `k in o` tests presence: the write keeps the member when it runs only on its absence.
421
+ if (signs(node['right'], node['left']) && testedBefore(node, write.site))
422
+ keptWhenPresent = true;
423
+ continue;
424
+ }
425
+ if (node.type === 'CallExpression') {
426
+ if (node.end > write.site.start)
427
+ continue;
428
+ const args = node['arguments'];
429
+ const callee = calleeExpression(node['callee']);
430
+ if (context.isDescriptorReader(callee)) {
431
+ if (signs(args[0], args[1]))
432
+ descriptors.add(node);
433
+ continue;
434
+ }
435
+ // A read in a summarized callee whose parameter is bound to the same key binding.
436
+ const index = args.findIndex((arg) => arg.type !== 'SpreadElement' && keyExpression(arg, []) === key);
437
+ if (index < 0)
438
+ continue;
439
+ if (context.calleeFunctions(callee).some((fn) => readsParameter(fn, index, args, holder)))
440
+ reads.add(node);
441
+ continue;
442
+ }
443
+ const object = signature(node['object'], []);
444
+ const member = memberKey(node);
445
+ if (object === undefined || member === undefined || `m(${object},${member})` !== slot)
446
+ continue;
447
+ reads.add(node);
448
+ const { use, parent, present } = useOf(node);
449
+ if ((parent === write.site && use === write.value && present) || testedBefore(node, write.site))
450
+ keptWhenPresent = true;
451
+ }
452
+ if (reads.size === 0 && descriptors.size === 0 && !keptWhenPresent)
453
+ return undefined;
454
+ const value = write.value === undefined ? undefined : unwrapped(write.value);
455
+ const writesBack = value !== undefined && value.type === 'MemberExpression' && reads.has(value);
456
+ /** Whether an expression gives a descriptor fetched from the slot: the call, or a variable it alone defines. */
457
+ const fetched = (node) => {
458
+ const expression = unwrapped(node);
459
+ if (descriptors.has(expression))
460
+ return true;
461
+ if (expression.type !== 'Identifier')
462
+ return false;
463
+ const variable = context.definitions.keyOf(expression);
464
+ const definitions = variable === undefined ? [] : context.definitions.of(variable);
465
+ return (definitions.length > 0 &&
466
+ definitions.every((each) => each.source !== undefined && descriptors.has(unwrapped(each.source))));
467
+ };
468
+ const isOriginal = (node) => {
469
+ const expression = unwrapped(node);
470
+ if (reads.has(expression))
471
+ return true;
472
+ if (expression.type !== 'MemberExpression' || expression['computed'] === true)
473
+ return false;
474
+ const property = expression['property'];
475
+ return property.type === 'Identifier' && property['name'] === 'value' && fetched(expression['object']);
476
+ };
477
+ return { isOriginal, keptWhenPresent, writesBack };
478
+ };
479
+ return { patchShaped, reuse };
480
+ }
481
+ /** The operators that compare a value with `null`, `undefined` or a string. */
482
+ const equalities = new Set(['==', '!=', '===', '!==']);
483
+ /**
484
+ * What a comparison of `child` (the read, or its `typeof`) says of the original: with `null`, `undefined` or `void`,
485
+ * that it is absent or present; with a string, the read's `typeof` equal to `'undefined'` that it is absent, and any
486
+ * other string equal that it is present. None for any other comparison.
487
+ */
488
+ function comparison(node, child, mode) {
489
+ const operator = node['operator'];
490
+ if (!equalities.has(operator))
491
+ return undefined;
492
+ const other = (node['left'] === child ? node['right'] : node['left']);
493
+ const nullish = (other.type === 'Literal' && other['value'] === null && other['raw'] === 'null') ||
494
+ (other.type === 'Identifier' && other['name'] === 'undefined') ||
495
+ (other.type === 'UnaryExpression' && other['operator'] === 'void');
496
+ const string = other.type === 'Literal' && typeof other['value'] === 'string' ? other['value'] : undefined;
497
+ let equal;
498
+ if (mode === 'read' && nullish)
499
+ equal = { truthy: 'absent', falsy: 'present' };
500
+ else if (string === undefined)
501
+ return undefined;
502
+ else if (mode === 'typeof' && string === 'undefined')
503
+ equal = { truthy: 'absent', falsy: 'present' };
504
+ else
505
+ equal = { truthy: 'present', falsy: undefined };
506
+ return operator.startsWith('!') ? { truthy: equal.falsy, falsy: equal.truthy } : equal;
507
+ }
508
+ /** A key's signature: a string's JSON, or a well-known symbol's description. */
509
+ function keySignature(key) {
510
+ return typeof key === 'symbol' ? `s:${key.description ?? ''}` : JSON.stringify(key);
511
+ }
512
+ /** An expression past its parentheses and optional chain. */
513
+ function unwrapped(node) {
514
+ let at = node;
515
+ while (at.type === 'ParenthesizedExpression' || at.type === 'ChainExpression')
516
+ at = at['expression'];
517
+ return at;
518
+ }
@@ -0,0 +1,3 @@
1
+ import type { AstNode } from './ast.ts';
2
+ /** An expression printed from its syntax, cut at 120 characters. */
3
+ export declare function printed(node: AstNode): string;
@@ -0,0 +1,150 @@
1
+ // Expressions as a trace shows them (§14): printed from the syntax, so code nested in a string literal (§2) prints as
2
+ // the code it is, and cut to a short line.
3
+ /** How long a printed expression may be. A longer one is cut, and ends in an ellipsis. */
4
+ const maxLength = 120;
5
+ /** An expression printed from its syntax, cut at 120 characters. */
6
+ export function printed(node) {
7
+ const text = print(node);
8
+ return text.length > maxLength ? `${text.slice(0, maxLength - 1)}…` : text;
9
+ }
10
+ /** Operators written as words, which need a space before their operand. */
11
+ const wordOperators = new Set(['typeof', 'void', 'delete']);
12
+ /** Expressions that bind less tightly than any operand, so an operand of one of them is parenthesized. */
13
+ const loose = new Set([
14
+ 'LogicalExpression',
15
+ 'ConditionalExpression',
16
+ 'SequenceExpression',
17
+ 'AssignmentExpression',
18
+ 'ArrowFunctionExpression',
19
+ 'YieldExpression',
20
+ ]);
21
+ function print(node) {
22
+ switch (node.type) {
23
+ case 'Literal':
24
+ return literal(node);
25
+ case 'TemplateLiteral': {
26
+ const quasis = node['quasis'].map((quasi) => quasi['value'].raw);
27
+ const expressions = node['expressions'];
28
+ return `\`${quasis.map((quasi, index) => (index < expressions.length ? `${quasi}\${${print(expressions[index])}}` : quasi)).join('')}\``;
29
+ }
30
+ case 'Identifier':
31
+ return node['name'];
32
+ case 'PrivateIdentifier':
33
+ return `#${node['name']}`;
34
+ case 'ThisExpression':
35
+ return 'this';
36
+ case 'Super':
37
+ return 'super';
38
+ case 'BinaryExpression':
39
+ case 'LogicalExpression': {
40
+ // A left operand with the same operator groups the way it is written: `"a" + "b" + "c"`. The chain is printed from
41
+ // a list rather than by recursion, because minified code builds long ones, and only up to a trace line.
42
+ const operator = node['operator'];
43
+ const rights = [];
44
+ let left = node;
45
+ while (left.type === node.type && left['operator'] === operator) {
46
+ rights.push(left['right']);
47
+ left = left['left'];
48
+ }
49
+ let text = operand(left, false);
50
+ for (const right of rights.reverse()) {
51
+ if (text.length > maxLength)
52
+ break;
53
+ text += ` ${operator} ${operand(right, false)}`;
54
+ }
55
+ return text;
56
+ }
57
+ case 'UnaryExpression': {
58
+ const operator = node['operator'];
59
+ const argument = operand(node['argument'], false);
60
+ return wordOperators.has(operator) ? `${operator} ${argument}` : `${operator}${argument}`;
61
+ }
62
+ case 'ConditionalExpression':
63
+ return `${operand(node['test'], false)} ? ${print(node['consequent'])} : ${print(node['alternate'])}`;
64
+ case 'SequenceExpression':
65
+ return `(${list(node['expressions'])})`;
66
+ case 'AssignmentExpression':
67
+ return `${print(node['left'])} ${node['operator']} ${print(node['right'])}`;
68
+ case 'AssignmentPattern':
69
+ return `${print(node['left'])} = ${print(node['right'])}`;
70
+ case 'MemberExpression': {
71
+ const object = operand(node['object'], false);
72
+ const property = node['property'];
73
+ const optional = node['optional'] === true ? '?.' : '';
74
+ if (node['computed'] === true)
75
+ return `${object}${optional}[${print(property)}]`;
76
+ return `${object}${optional === '' ? '.' : optional}${print(property)}`;
77
+ }
78
+ case 'ChainExpression':
79
+ return print(node['expression']);
80
+ case 'CallExpression':
81
+ return `${operand(node['callee'], false)}(${list(node['arguments'])})`;
82
+ case 'NewExpression':
83
+ return `new ${operand(node['callee'], false)}(${list(node['arguments'])})`;
84
+ case 'ArrayExpression':
85
+ return `[${list(node['elements'])}]`;
86
+ case 'ObjectExpression':
87
+ return node['properties'].length === 0 ? '{}' : '{…}';
88
+ case 'SpreadElement':
89
+ case 'RestElement':
90
+ return `...${print(node['argument'])}`;
91
+ case 'VariableDeclarator': {
92
+ // A definition (§14 `alias`): the declarator's pattern and its initializer.
93
+ const init = node['init'];
94
+ return init === null ? print(node['id']) : `${print(node['id'])} = ${print(init)}`;
95
+ }
96
+ case 'VariableDeclaration':
97
+ return `${node['kind']} ${list(node['declarations'])}`;
98
+ case 'UpdateExpression': {
99
+ const argument = print(node['argument']);
100
+ const operator = node['operator'];
101
+ return node['prefix'] === true ? `${operator}${argument}` : `${argument}${operator}`;
102
+ }
103
+ case 'ForInStatement':
104
+ case 'ForOfStatement':
105
+ return `for (${print(node['left'])} ${node.type === 'ForInStatement' ? 'in' : 'of'} ${print(node['right'])})`;
106
+ case 'ObjectPattern':
107
+ return `{ ${list(node['properties'])} }`;
108
+ case 'ArrayPattern':
109
+ return `[${list(node['elements'])}]`;
110
+ case 'Property': {
111
+ const key = node['key'];
112
+ const value = print(node['value']);
113
+ if (node['shorthand'] === true)
114
+ return value;
115
+ return `${node['computed'] === true ? `[${print(key)}]` : print(key)}: ${value}`;
116
+ }
117
+ default:
118
+ return '…';
119
+ }
120
+ }
121
+ /** An operand, parenthesized when it binds less tightly than the expression it is in. */
122
+ function operand(node, tight) {
123
+ const text = print(node);
124
+ const parenthesized = loose.has(node.type) || (!tight && node.type === 'BinaryExpression');
125
+ return parenthesized ? `(${text})` : text;
126
+ }
127
+ /** A comma-separated list, with holes left empty, that stops once it is longer than a trace line. */
128
+ function list(nodes) {
129
+ let text = '';
130
+ for (const [index, node] of nodes.entries()) {
131
+ if (index > 0)
132
+ text += ', ';
133
+ if (text.length > maxLength)
134
+ return `${text}…`;
135
+ if (node !== null)
136
+ text += print(node);
137
+ }
138
+ return text;
139
+ }
140
+ function literal(node) {
141
+ const regex = node['regex'];
142
+ if (regex !== null && regex !== undefined)
143
+ return `/${regex.pattern}/${regex.flags}`;
144
+ const bigint = node['bigint'];
145
+ if (bigint !== null && bigint !== undefined)
146
+ return `${bigint}n`;
147
+ const value = node['value'];
148
+ // A long string is cut before it is quoted, so a trace never copies a huge literal.
149
+ return typeof value === 'string' ? JSON.stringify(value.length > maxLength ? value.slice(0, maxLength) : value) : String(value);
150
+ }