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,36 @@
1
+ import { type ArrowFunctionExpression, type CatchClause, type Class, type Function as FunctionNode, type IdentifierReference, type ImportDeclaration, type Program, type VariableDeclaration } from 'oxc-parser';
2
+ /** A program as `oxc-parser` returns it, parsed with the options §5 fixes. */
3
+ export type OxcProgram = Program;
4
+ /** An identifier in a reference position: `fetch` in `fetch = w`, `window` in `window.fetch = w`. */
5
+ export type IdentifierNode = IdentifierReference;
6
+ /**
7
+ * The node that declares a name:
8
+ * - a `var`, `let`, `const` or `using` declaration: its `VariableDeclaration`, whose `kind` says which
9
+ * - a function's name: its `FunctionDeclaration` (declared in the enclosing scope) or `FunctionExpression` (declared in
10
+ * a `function-expression-name` scope)
11
+ * - a parameter, or a function's implicit `arguments`: the function, declared in its `function` scope
12
+ * - a class's name: its class
13
+ * - a catch parameter: its `CatchClause`
14
+ * - an import binding: its `ImportDeclaration`
15
+ */
16
+ export type DeclarationNode = VariableDeclaration | FunctionNode | ArrowFunctionExpression | Class | CatchClause | ImportDeclaration;
17
+ /** The kind of scope a name is declared in, as ES2015+ scoping gives it. */
18
+ export declare const scopeKinds: readonly ["global", "module", "function", "function-expression-name", "block", "switch", "for", "catch", "with", "class", "class-field-initializer", "class-static-block"];
19
+ export type ScopeKind = (typeof scopeKinds)[number];
20
+ /** What a reference resolves to: a declaration and the kind of scope it is in, or no declaration at all. */
21
+ export type Resolution = {
22
+ declaration: DeclarationNode;
23
+ scopeKind: ScopeKind;
24
+ } | 'unresolved';
25
+ /**
26
+ * The binding-resolution seam (§19). `resolve` scopes one program with its `sourceType`, and `reference` answers for
27
+ * any identifier reference in it. It knows nothing of monkeypatchlist's rules: a classic script's global `var` is simply a
28
+ * declaration in the `global` scope, and a block-level function is block-scoped (no Annex B).
29
+ */
30
+ export interface BindingResolver {
31
+ resolve(program: OxcProgram, sourceType: 'script' | 'module'): {
32
+ reference(id: IdentifierNode): Resolution;
33
+ };
34
+ }
35
+ /** `eslint-scope`, the implementation §5 names: `optimistic: true`, and `ecmaVersion` ≥ 2015 for block scoping. */
36
+ export declare const eslintScopeBindings: BindingResolver;
@@ -0,0 +1,66 @@
1
+ // The binding-resolution seam (§19): it sits inside the JS resolver, between parsing and monkeypatchlist's own scope rules.
2
+ // An implementation answers one question, what an identifier reference resolves to, under standard ES2015+ scoping.
3
+ // monkeypatchlist's four post-passes (§5) run above it, so swapping implementations never touches them.
4
+ import { analyze } from 'eslint-scope';
5
+ import { visitorKeys, } from 'oxc-parser';
6
+ /** The kind of scope a name is declared in, as ES2015+ scoping gives it. */
7
+ export const scopeKinds = [
8
+ 'global',
9
+ 'module',
10
+ 'function',
11
+ 'function-expression-name',
12
+ 'block',
13
+ 'switch',
14
+ 'for',
15
+ 'catch',
16
+ 'with',
17
+ 'class',
18
+ 'class-field-initializer',
19
+ 'class-static-block',
20
+ ];
21
+ /** `eslint-scope`, the implementation §5 names: `optimistic: true`, and `ecmaVersion` ≥ 2015 for block scoping. */
22
+ export const eslintScopeBindings = {
23
+ resolve(program, sourceType) {
24
+ const manager = analyze(program, {
25
+ ecmaVersion: 2025,
26
+ sourceType,
27
+ optimistic: true,
28
+ childVisitorKeys: visitorKeys,
29
+ fallback: 'iteration',
30
+ });
31
+ const references = new Map();
32
+ for (const scope of manager.scopes) {
33
+ for (const reference of scope.references)
34
+ references.set(reference.identifier, reference);
35
+ }
36
+ return {
37
+ reference(id) {
38
+ const reference = references.get(id);
39
+ if (reference === undefined)
40
+ throw new Error(`eslint-scope recorded no reference for \`${id.name}\` at ${id.start}`);
41
+ const variable = reference.resolved;
42
+ if (variable === null)
43
+ return 'unresolved';
44
+ return { declaration: declarationOf(variable), scopeKind: variable.scope.type };
45
+ },
46
+ };
47
+ },
48
+ };
49
+ /** The node that declares a variable: its first definition's, or for `arguments`, the function's. */
50
+ function declarationOf(variable) {
51
+ const [definition] = variable.defs;
52
+ if (definition === undefined)
53
+ return variable.scope.block;
54
+ switch (definition.type) {
55
+ case 'Variable':
56
+ case 'ImportBinding':
57
+ return definition.parent;
58
+ case 'FunctionName':
59
+ case 'ClassName':
60
+ case 'Parameter':
61
+ case 'CatchClause':
62
+ return definition.node;
63
+ case 'ImplicitGlobalVariable':
64
+ throw new Error(`eslint-scope resolved \`${variable.name}\` to an implicit global`);
65
+ }
66
+ }
@@ -0,0 +1,80 @@
1
+ /** The caps (§18 Context policy), each a count. A cap hit is counted in the scan statistics, whatever it cuts. */
2
+ export interface Caps {
3
+ /**
4
+ * The `alternatives` cap: more alternatives than this in one value collapse into unknown, all but its native holders.
5
+ * Chains of the global object's names have a handful.
6
+ */
7
+ alternatives: number;
8
+ /**
9
+ * The `fold-length` cap: a string fold longer than this, in UTF-16 code units, is unknown. No member is named by
10
+ * anything near it, and it keeps doubling folds (`const b = a + a, c = b + b, …`) from exhausting memory.
11
+ */
12
+ foldLength: number;
13
+ /**
14
+ * How many frames a chain holds before the next call widens: deep enough for helper chains 2–4 deep (§5), and for
15
+ * the chains of calls carrying no native that core-js's install helpers make.
16
+ */
17
+ depth: number;
18
+ /**
19
+ * How many frames of one function a write is instantiated in before the rest merge: enough for the per-element calls
20
+ * of iteration natives' callbacks (§5 Resolver capabilities 4) over the corpus's constant lists.
21
+ * A frame that carries a native function (the original `window.fetch`) is kept apart before the others.
22
+ */
23
+ contexts: number;
24
+ /**
25
+ * The iteration cap: how many iterations one loop, or one iteration native's callback, is unrolled for in one frame
26
+ * of the code around it (§5 Resolver capabilities 4). A loop that runs past it is cut there (Principle 5).
27
+ */
28
+ iterations: number;
29
+ /**
30
+ * How many constants are evaluated one within another before the next one read waits for them to stop: deep enough
31
+ * for any constant a person writes, and shallow enough for the stack (§5 Resolver capabilities 1).
32
+ */
33
+ nesting: number;
34
+ /**
35
+ * How many rounds the dynamic-call index is built in, each with the frames the last one found. Each round reaches one
36
+ * call further along a chain of calls the syntax doesn't name. Rounds that run out are a cap hit, counted on the
37
+ * calls of the frames the last round found whose code calls on, and raised as `analysis-budget` when one of them
38
+ * carries a native (Principle 5).
39
+ */
40
+ indexRounds: number;
41
+ /** How many aliases deep a patch-shaped write's original is followed (§5 Partially resolved writes). */
42
+ aliasDepth: number;
43
+ /** How deep a write-back is followed through heap fields whose values are heap fields (§3). */
44
+ heapDepth: number;
45
+ /**
46
+ * The descriptor-alternatives cap: how many combinations of maybe-absent fields one descriptor object is expanded
47
+ * into.
48
+ */
49
+ descriptorAlternatives: number;
50
+ /**
51
+ * The candidate-runs cap: how many runs of `writesOf`, one per combination of alternatives, a node's candidate sets
52
+ * are worked out in (§5 Candidate sets). Past it, the node's candidate sites give nothing: counted, and raised as
53
+ * `analysis-budget` when a holder they write can be the global object or a native holder (Principle 5).
54
+ */
55
+ candidateRuns: number;
56
+ /** The test-reads cap: how many reads of a guard's test the classifier evaluates to find a native read in it (§7). */
57
+ testReads: number;
58
+ /** The throw-depth cap: how many calls into the code's own functions are followed to judge that a `try` can't throw. */
59
+ throwDepth: number;
60
+ /** The flag-depth cap: how many calls deep a once-flag's assignment is followed from the path its test lets through. */
61
+ flagDepth: number;
62
+ }
63
+ /** What bounds one link group's analysis. */
64
+ export interface Budget {
65
+ /**
66
+ * The step budget per source byte (UTF-8) of a link group's members: a group of `n` bytes has `n *
67
+ * stepsPerByte` steps, rounded down. Set with margin over the validation corpus, the scale fixture and the test
68
+ * fixtures, so that it never fires on them (§18).
69
+ */
70
+ stepsPerByte: number;
71
+ caps: Caps;
72
+ }
73
+ /**
74
+ * The budget monkeypatchlist ships. The costliest group per byte measured on 2026-09-29 took 162 steps per byte on the
75
+ * validation corpus (a 4 KB zone.js-style sweep), 130 on the scale fixture (react-dom) and 45 in the test fixtures, so
76
+ * 1024 left about a sixfold margin. At `depth` 24 / `contexts` 256, webpack promise-polyfill takes about 3,150
77
+ * steps per byte (its recursion found provisionally once per heap instance), so R1 raises the budget to 4096, where it
78
+ * fires on no corpus site, rather than lose results to it.
79
+ */
80
+ export declare const defaultBudget: Budget;
@@ -0,0 +1,31 @@
1
+ // Budgets and caps (§18 Budgets are deterministic, Context policy). Each link group has a step budget, scaled with its
2
+ // size: `stepsPerByte` steps per source byte of its members. The caps bound single pieces of work. All of them
3
+ // are implementation detail, tuned on the corpus, and never a user flag: the scan's deps can replace them, so the cost
4
+ // benchmark can rerun the corpus with each one multiplied on its own.
5
+ /**
6
+ * The budget monkeypatchlist ships. The costliest group per byte measured on 2026-09-29 took 162 steps per byte on the
7
+ * validation corpus (a 4 KB zone.js-style sweep), 130 on the scale fixture (react-dom) and 45 in the test fixtures, so
8
+ * 1024 left about a sixfold margin. At `depth` 24 / `contexts` 256, webpack promise-polyfill takes about 3,150
9
+ * steps per byte (its recursion found provisionally once per heap instance), so R1 raises the budget to 4096, where it
10
+ * fires on no corpus site, rather than lose results to it.
11
+ */
12
+ export const defaultBudget = {
13
+ stepsPerByte: 4096,
14
+ caps: {
15
+ alternatives: 16,
16
+ foldLength: 1 << 20,
17
+ // Where the corpus results converge: raising both ×4 again changes nothing more.
18
+ depth: 24,
19
+ contexts: 256,
20
+ iterations: 256,
21
+ nesting: 64,
22
+ indexRounds: 3,
23
+ aliasDepth: 8,
24
+ heapDepth: 8,
25
+ descriptorAlternatives: 64,
26
+ candidateRuns: 256,
27
+ testReads: 64,
28
+ throwDepth: 4,
29
+ flagDepth: 4,
30
+ },
31
+ };
@@ -0,0 +1,19 @@
1
+ import type { CatalogTarget } from '../../../catalog.ts';
2
+ import type { AstNode } from './ast.ts';
3
+ import { type Value } from './global-object.ts';
4
+ import { type Key } from './keys.ts';
5
+ /** What judging a `try` block needs: values where it is, the catalog, and the declarations names resolve to. */
6
+ export interface ThrowContext {
7
+ /** The values an expression can have where the `try` is. */
8
+ evaluate(node: AstNode): Value[];
9
+ /** The catalog target `key` names on `holder`'s chain in the unmutated catalog, if one does. */
10
+ target(holder: string, key: Key): CatalogTarget | undefined;
11
+ /** The declaration a name resolves to, or none for a free name. */
12
+ declaration(id: AstNode): AstNode | undefined;
13
+ /** How deep calls into the code's own functions are followed (the `throwDepth` cap, §18). */
14
+ maxDepth: number;
15
+ /** Counts a hit of the `throwDepth` cap at the function it kept from being followed (§18). */
16
+ depthCut(fn: AstNode): void;
17
+ }
18
+ /** Whether a `try` block may throw where it is: false only when every statement in it is shown not to. */
19
+ export declare function mayThrow(block: AstNode, context: ThrowContext): boolean;
@@ -0,0 +1,351 @@
1
+ // Catch arms (§5 Resolver capabilities 9): a write in a `catch` arm counts only
2
+ // when its `try` block may throw, judged against the unmutated catalog. A `try` whose statements are reads and calls
3
+ // the catalog shows can't throw for the members swept never reaches its `catch`: rrweb's canvas observers read
4
+ // `typeof proto[k]`, which throws only for an accessor, whose getter runs on the prototype, and call `patch`, which
5
+ // catches internally, so its `catch` redefines only the accessors.
6
+ //
7
+ // The judgement is conservative: a statement or expression throws unless a rule below shows it can't, so any code the
8
+ // analyzer can't see through keeps its `catch` reachable, as before.
9
+ import { isHeap } from "./global-object.js";
10
+ import { toPropertyKey } from "./keys.js";
11
+ import { isFunction } from "./summaries.js";
12
+ /** Whether a `try` block may throw where it is: false only when every statement in it is shown not to. */
13
+ export function mayThrow(block, context) {
14
+ const judge = judgeOf(context);
15
+ return !judge.statement(block, 0);
16
+ }
17
+ function judgeOf(context) {
18
+ // The functions being followed, whose calls through themselves are taken to throw.
19
+ const active = new Set();
20
+ /** Whether a statement can't throw. */
21
+ const statement = (node, depth) => {
22
+ switch (node.type) {
23
+ case 'EmptyStatement':
24
+ case 'ContinueStatement':
25
+ case 'BreakStatement':
26
+ case 'FunctionDeclaration':
27
+ return true;
28
+ case 'BlockStatement':
29
+ return node['body'].every((each) => statement(each, depth));
30
+ case 'ReturnStatement': {
31
+ const argument = node['argument'];
32
+ return argument === null || expression(argument, depth);
33
+ }
34
+ case 'ExpressionStatement':
35
+ return expression(node['expression'], depth);
36
+ case 'IfStatement': {
37
+ const alternate = node['alternate'];
38
+ return (expression(node['test'], depth) &&
39
+ statement(node['consequent'], depth) &&
40
+ (alternate === null || statement(alternate, depth)));
41
+ }
42
+ case 'VariableDeclaration':
43
+ return node['declarations'].every((declarator) => {
44
+ const init = declarator['init'];
45
+ return declarator['id'].type === 'Identifier' && (init === null || expression(init, depth));
46
+ });
47
+ case 'TryStatement': {
48
+ // A `try` throws only when its block throws and its `catch` does too, or its `finally` does.
49
+ const handler = node['handler'];
50
+ const finalizer = node['finalizer'];
51
+ const block = statement(node['block'], depth);
52
+ // A destructuring catch parameter throws on a thrown value it can't destructure.
53
+ const param = handler === null ? null : handler['param'];
54
+ const catches = handler !== null && (param === null || param.type === 'Identifier') && statement(handler['body'], depth);
55
+ const caught = handler === null ? block : block || catches;
56
+ return caught && (finalizer === null || statement(finalizer, depth));
57
+ }
58
+ default:
59
+ return false;
60
+ }
61
+ };
62
+ /** Whether an expression can't throw. */
63
+ const expression = (node, depth) => {
64
+ switch (node.type) {
65
+ case 'Literal':
66
+ case 'ThisExpression':
67
+ case 'FunctionExpression':
68
+ case 'ArrowFunctionExpression':
69
+ return true;
70
+ case 'TemplateLiteral':
71
+ return node['expressions'].length === 0;
72
+ case 'Identifier':
73
+ return readable(node);
74
+ case 'ChainExpression':
75
+ return expression(node['expression'], depth);
76
+ case 'SequenceExpression':
77
+ return node['expressions'].every((each) => expression(each, depth));
78
+ case 'LogicalExpression':
79
+ return expression(node['left'], depth) && expression(node['right'], depth);
80
+ case 'ConditionalExpression':
81
+ return ['test', 'consequent', 'alternate'].every((key) => expression(node[key], depth));
82
+ case 'UnaryExpression': {
83
+ const argument = node['argument'];
84
+ const operator = node['operator'];
85
+ // `typeof` of a free name never throws.
86
+ if (operator === 'typeof' && argument.type === 'Identifier')
87
+ return true;
88
+ return (operator === 'typeof' || operator === '!' || operator === 'void') && expression(argument, depth);
89
+ }
90
+ case 'BinaryExpression': {
91
+ const operator = node['operator'];
92
+ const left = node['left'];
93
+ const right = node['right'];
94
+ if (!expression(left, depth) || !expression(right, depth))
95
+ return false;
96
+ // Strict equality never converts its operands, loose equality between primitives doesn't either, and `in`
97
+ // throws only on a right side that is no object.
98
+ if (operator === '===' || operator === '!==')
99
+ return true;
100
+ if (operator === '==' || operator === '!=')
101
+ return primitive(left) && primitive(right);
102
+ // `in` converts its left side to a key, which can throw for an object: only constants are shown not to.
103
+ if (operator === 'in')
104
+ return constants(context.evaluate(left)) && objects(context.evaluate(right));
105
+ return false;
106
+ }
107
+ case 'ArrayExpression':
108
+ return node['elements'].every((element) => element === null || (element.type !== 'SpreadElement' && expression(element, depth)));
109
+ case 'ObjectExpression':
110
+ return node['properties'].every((property) => property.type === 'Property' &&
111
+ (property['computed'] !== true || expression(property['key'], depth)) &&
112
+ expression(property['value'], depth));
113
+ case 'MemberExpression':
114
+ return memberRead(node, depth);
115
+ case 'AssignmentExpression':
116
+ return node['operator'] === '=' && assigned(node['left'], depth) && expression(node['right'], depth);
117
+ case 'CallExpression':
118
+ return call(node, depth);
119
+ default:
120
+ return false;
121
+ }
122
+ };
123
+ /** Whether reading a name can't throw: a declaration it resolves to, or a free name the catalog has. */
124
+ const readable = (id) => {
125
+ if (id['name'] === 'undefined')
126
+ return true;
127
+ const declaration = context.declaration(id);
128
+ // A `let`, `const` or `class` read before its declaration throws.
129
+ if (declaration !== undefined)
130
+ return isFunction(declaration) || declaration.start <= id.start;
131
+ const values = context.evaluate(id);
132
+ return values.length > 0 && values.every((value) => value.kind === 'holder' || value.kind === 'native' || value.kind === 'member');
133
+ };
134
+ /** The constant keys a member expression reads, or undefined when one isn't constant. */
135
+ const keysOf = (node, depth) => {
136
+ const property = node['property'];
137
+ if (node['computed'] !== true)
138
+ return property.type === 'Identifier' ? [property['name']] : undefined;
139
+ if (!expression(property, depth))
140
+ return undefined;
141
+ const keys = context.evaluate(property);
142
+ if (keys.length === 0 || !keys.every((key) => key.kind === 'constant'))
143
+ return undefined;
144
+ return keys.map((key) => toPropertyKey(key.value));
145
+ };
146
+ /**
147
+ * Whether reading a member can't throw: a data member, or none, of a native holder in the unmutated catalog (an
148
+ * accessor's getter runs, and throws on a prototype), or any member of an array or function the code made.
149
+ */
150
+ const memberRead = (node, depth) => {
151
+ const object = node['object'];
152
+ if (object.type === 'Super' || !expression(object, depth))
153
+ return false;
154
+ const keys = keysOf(node, depth);
155
+ if (keys === undefined)
156
+ return false;
157
+ const values = context.evaluate(object);
158
+ if (values.length === 0)
159
+ return false;
160
+ return values.every((value) => {
161
+ if (made(value))
162
+ return keys.every((key) => !poisoned(value, key));
163
+ if (value.kind !== 'holder')
164
+ return false;
165
+ return keys.every((key) => context.target(value.holder, key)?.descriptor.type !== 'accessor');
166
+ });
167
+ };
168
+ /**
169
+ * Whether an assignment target can't throw: a declared variable, a writable data member of a native holder (an
170
+ * accessor's setter runs, and throws on a prototype), or any member of an array or function the code made.
171
+ */
172
+ const assigned = (left, depth) => {
173
+ if (left.type === 'Identifier') {
174
+ const declaration = context.declaration(left);
175
+ return declaration !== undefined && declaration.start <= left.start && mutable(left, declaration);
176
+ }
177
+ if (left.type !== 'MemberExpression')
178
+ return false;
179
+ const object = left['object'];
180
+ if (object.type === 'Super' || !expression(object, depth))
181
+ return false;
182
+ const keys = keysOf(left, depth);
183
+ if (keys === undefined)
184
+ return false;
185
+ const values = context.evaluate(object);
186
+ if (values.length === 0)
187
+ return false;
188
+ return values.every((value) => {
189
+ if (made(value))
190
+ return keys.every((key) => !poisoned(value, key) && !fixed(value, key));
191
+ if (value.kind !== 'holder')
192
+ return false;
193
+ return keys.every((key) => {
194
+ const descriptor = context.target(value.holder, key)?.descriptor;
195
+ return descriptor !== undefined && descriptor.type === 'data' && descriptor.writable;
196
+ });
197
+ });
198
+ };
199
+ /**
200
+ * Whether a call can't throw: its arguments can't, and it calls a function the code made whose body can't throw
201
+ * (one that catches internally), `push` on an array the code made, or a descriptor read or a define of a configurable
202
+ * member on a native holder.
203
+ */
204
+ const call = (node, depth) => {
205
+ const args = node['arguments'];
206
+ if (args.some((arg) => arg.type === 'SpreadElement' || !expression(arg, depth)))
207
+ return false;
208
+ const callee = node['callee'];
209
+ if (callee.type === 'MemberExpression' && callee['computed'] !== true) {
210
+ const name = callee['property']['name'];
211
+ if (name === 'push' && expression(callee['object'], depth)) {
212
+ const values = context.evaluate(callee['object']);
213
+ if (values.length > 0 && values.every((value) => isHeap(value) && value.heap.kind === 'array'))
214
+ return true;
215
+ }
216
+ }
217
+ if (callee.type !== 'Identifier' && !expression(callee, depth))
218
+ return false;
219
+ if (callee.type === 'Identifier' && !readable(callee))
220
+ return false;
221
+ const values = context.evaluate(callee);
222
+ if (values.length === 0)
223
+ return false;
224
+ return values.every((value) => {
225
+ if (value.kind === 'native')
226
+ return nativeCall(value.name, args);
227
+ if (!isHeap(value) || value.heap.kind !== 'function')
228
+ return false;
229
+ return body(value.heap.node, depth);
230
+ });
231
+ };
232
+ /** Whether a native call can't throw on a native holder's member, as the unmutated catalog has it. */
233
+ const nativeCall = (name, args) => {
234
+ if (name !== 'Object.getOwnPropertyDescriptor' && name !== 'Object.defineProperty' && name !== 'Reflect.defineProperty') {
235
+ return false;
236
+ }
237
+ const [object, key, descriptor] = args;
238
+ if (object === undefined || key === undefined)
239
+ return false;
240
+ const holders = context.evaluate(object);
241
+ if (holders.length === 0 || !holders.every((value) => value.kind === 'holder'))
242
+ return false;
243
+ // Both convert the key, which can throw for an object: only constants are shown not to.
244
+ const keys = context.evaluate(key);
245
+ if (!constants(keys))
246
+ return false;
247
+ if (name === 'Object.getOwnPropertyDescriptor')
248
+ return true;
249
+ // A define throws when its descriptor is no object or mixes accessor and data fields, and `Object.defineProperty`
250
+ // when the member is there and can't be configured.
251
+ if (descriptor === undefined || descriptor.type !== 'ObjectExpression')
252
+ return false;
253
+ // Each field a plain one, named by an identifier: an accessor field's getter would run, and can throw.
254
+ const properties = descriptor['properties'];
255
+ const fields = properties.map((property) => property.type === 'Property' && property['computed'] !== true && property['kind'] === 'init'
256
+ ? property['key']['name']
257
+ : undefined);
258
+ if (fields.includes(undefined))
259
+ return false;
260
+ // A getter or setter that is neither a function nor `undefined` throws.
261
+ const callable = (value) => isFunction(value) || (value.type === 'Identifier' && value['name'] === 'undefined');
262
+ if (properties.some((property, index) => (fields[index] === 'get' || fields[index] === 'set') && !callable(property['value']))) {
263
+ return false;
264
+ }
265
+ if (fields.some((field) => field === 'get' || field === 'set') && fields.some((field) => field === 'value' || field === 'writable')) {
266
+ return false;
267
+ }
268
+ if (name === 'Reflect.defineProperty')
269
+ return true;
270
+ return holders.every((value) => keys.every((each) => {
271
+ const target = context.target(value.holder, toPropertyKey(each.value));
272
+ return target === undefined || target.descriptor.configurable;
273
+ }));
274
+ };
275
+ /** Whether calling a function the code made can't throw: simple parameters, and a body that can't. */
276
+ const body = (fn, depth) => {
277
+ if (active.has(fn) || !isFunction(fn))
278
+ return false;
279
+ if (depth >= context.maxDepth) {
280
+ context.depthCut(fn);
281
+ return false;
282
+ }
283
+ if (fn['async'] === true || fn['generator'] === true)
284
+ return false;
285
+ if (!fn['params'].every((param) => param.type === 'Identifier' || param.type === 'RestElement')) {
286
+ return false;
287
+ }
288
+ active.add(fn);
289
+ try {
290
+ const inside = fn['body'];
291
+ return inside.type === 'BlockStatement' ? statement(inside, depth + 1) : expression(inside, depth + 1);
292
+ }
293
+ finally {
294
+ active.delete(fn);
295
+ }
296
+ };
297
+ return { statement, expression };
298
+ }
299
+ /** Whether an expression is a primitive by its form: a literal other than a regular expression, or a `typeof`. */
300
+ function primitive(node) {
301
+ if (node.type === 'Literal')
302
+ return node['regex'] === undefined;
303
+ return node.type === 'UnaryExpression' && node['operator'] === 'typeof';
304
+ }
305
+ /** Whether every value is a constant, which converting to a property key never throws on. */
306
+ function constants(values) {
307
+ return values.length > 0 && values.every((value) => value.kind === 'constant');
308
+ }
309
+ /**
310
+ * Whether assigning a name can't throw for what declares it: not a `const` (or `using`), an import, a class's name or
311
+ * a function expression's own name, which strict code can't assign.
312
+ */
313
+ function mutable(id, declaration) {
314
+ switch (declaration.type) {
315
+ case 'VariableDeclaration':
316
+ return declaration['kind'] === 'var' || declaration['kind'] === 'let';
317
+ case 'ImportDeclaration':
318
+ case 'ClassDeclaration':
319
+ case 'ClassExpression':
320
+ return false;
321
+ case 'FunctionExpression': {
322
+ // The seam gives the function for its parameters too: a parameter of that name is assignable.
323
+ if (declaration['id']?.['name'] !== id['name'])
324
+ return true;
325
+ return declaration['params'].some((param) => param.type === 'Identifier' && param['name'] === id['name']);
326
+ }
327
+ default:
328
+ return true;
329
+ }
330
+ }
331
+ /** Whether reading `key` off a function the code made can throw: a strict function's `caller` and `arguments`. */
332
+ function poisoned(value, key) {
333
+ return isHeap(value) && value.heap.kind === 'function' && (key === 'caller' || key === 'arguments');
334
+ }
335
+ /**
336
+ * Whether writing `key` on an array or function the code made can throw: a function's non-writable `name` and
337
+ * `length` in strict code, and an array's `length`, which throws on a value that is no valid length.
338
+ */
339
+ function fixed(value, key) {
340
+ if (!isHeap(value))
341
+ return true;
342
+ return key === 'length' || (value.heap.kind === 'function' && key === 'name');
343
+ }
344
+ /** Whether every value is an object, which `in` never throws on: a native holder, or one the code made. */
345
+ function objects(values) {
346
+ return values.length > 0 && values.every((value) => value.kind === 'holder' || isHeap(value));
347
+ }
348
+ /** Whether a value is an array or a function the code made, whose own members have no accessors it doesn't show. */
349
+ function made(value) {
350
+ return isHeap(value) && (value.heap.kind === 'array' || value.heap.kind === 'function');
351
+ }