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,5 @@
1
+ // The binding-resolution seam (§19): it sits inside the JS resolver, between parsing and monkeypatchlist's own scope rules.
2
+ // `eslint-scope` is the implementation monkeypatchlist ships; `@typescript-eslint/scope-manager` is a verified drop-in.
3
+ // monkeypatchlist's four post-passes (§5) run above the seam, so swapping implementations never touches them.
4
+ export { eslintScopeBindings, scopeKinds, } from "./lib/binding.js";
5
+ export { createJsResolver } from "./lib/resolver.js";
@@ -0,0 +1,10 @@
1
+ export { codeCallees, effects, guardClasses, globalForms, ineffectiveReasons, installContexts, limitationKinds, mechanisms, reachabilities, throwsValues, traceStepKinds, type AliasStep, type AnalyzeGroupInput, type AnalyzeGroupOutput, type ArtifactRef, type ArtifactSource, type BindingStep, type CallStep, type CodeCallee, type ConfidenceReasonStep, type CreationSiteStep, type DiscoveredReference, type Effect, type FoldStep, type GeneratedLocation, type GuardClass, type GuardStep, type GlobalDeclarationKind, type GlobalForm, type IneffectiveReason, type IneffectiveWrite, type LexicalCollisionStep, type LexicalDeclaration, type Limitation, type LimitationKind, type InstallContext, type Locks, type Lookup, type Mechanism, type ModuleLinking, type ModuleLinkStep, type NestedCodeStep, type RestoreStep, type RewrittenDeleteStep, type OutcomeStep, type RawOccurrence, type Reachability, type ReachabilityBasis, type RealmInstance, type Resolver, type Strictness, type Target, type TraceStep, type TraceStepKind, type Throws, } from './lib/types.ts';
2
+ export { defaultBudget, type Budget, type Caps } from './lib/budget.ts';
3
+ export { chunkRegistries } from './lib/chunk-registry.ts';
4
+ export { lexicalCollisions, type RealmDeclaration, type RealmOccurrence } from './lib/collisions.ts';
5
+ import type { Resolver } from './lib/types.ts';
6
+ /**
7
+ * The JavaScript resolver: `oxc-parser` with an explicit `sourceType`, `eslint-scope` behind the binding-resolution
8
+ * seam, and monkeypatchlist's scope post-passes above it (§5). `binding.ts` builds one with another binding resolver.
9
+ */
10
+ export declare const jsResolver: Resolver;
@@ -0,0 +1,13 @@
1
+ // Analysis (§19, module 4): the resolver seam. It takes link groups, the catalog and a budget, and returns raw
2
+ // occurrences, ineffective writes, limitations and discovered references. Everything that crosses it is plain
3
+ // JSON-serializable data, so that a native analyzer (§18) can replace this module and nothing else.
4
+ export { codeCallees, effects, guardClasses, globalForms, ineffectiveReasons, installContexts, limitationKinds, mechanisms, reachabilities, throwsValues, traceStepKinds, } from "./lib/types.js";
5
+ export { defaultBudget } from "./lib/budget.js";
6
+ export { chunkRegistries } from "./lib/chunk-registry.js";
7
+ export { lexicalCollisions } from "./lib/collisions.js";
8
+ import { createJsResolver } from "./lib/resolver.js";
9
+ /**
10
+ * The JavaScript resolver: `oxc-parser` with an explicit `sourceType`, `eslint-scope` behind the binding-resolution
11
+ * seam, and monkeypatchlist's scope post-passes above it (§5). `binding.ts` builds one with another binding resolver.
12
+ */
13
+ export const jsResolver = createJsResolver();
@@ -0,0 +1,33 @@
1
+ import type { SerializedCatalog } from '../../catalog.ts';
2
+ import type { Budget } from './lib/budget.ts';
3
+ import type { AnalyzeGroupOutput, ArtifactRef, ArtifactSource, Resolver } from './lib/types.ts';
4
+ /** One link group's analysis, as plain JSON-serializable data. */
5
+ export interface GroupJob {
6
+ entry: ArtifactRef;
7
+ /** Each name `artifact(name)` may be asked for, with what it returns, in the order they were found. */
8
+ artifacts: [name: string, source: ArtifactSource][];
9
+ /** Each module request `resolveImport(from, specifier)` resolves, with the name it resolves to. */
10
+ imports: [from: string, specifier: string, to: string][];
11
+ /** Each chunk lookup `chunks(registry, id)` answers (§6 idiom 6), with the names it returns; `null` is every id. */
12
+ chunks?: [registry: string, id: string | null, names: string[]][];
13
+ /** Each lookup `initialChunks(registry)` answers, with the names it returns. */
14
+ initialChunks?: [registry: string, names: string[]][];
15
+ }
16
+ /**
17
+ * Writes the group of `entry` out as a job: every artifact the seam's callbacks can reach from it, through the module
18
+ * requests `requestsOf` lists for each importer and the chunk registries each one's loaders read, and what each request
19
+ * and chunk lookup resolves to. A request `requestsOf` doesn't list resolves to nothing, as `resolveImport` must say
20
+ * for it too.
21
+ */
22
+ export declare function groupJob(input: {
23
+ entry: ArtifactRef;
24
+ artifact(name: string): ArtifactSource | undefined;
25
+ resolveImport(from: string, specifier: string): string | undefined;
26
+ requestsOf(from: string): readonly string[];
27
+ chunks?(registry: string, id: string | null): readonly string[];
28
+ initialChunks?(registry: string): readonly string[];
29
+ }): GroupJob;
30
+ /** The UTF-8 bytes of a job's artifacts, which its step budget and its estimated peak heap scale with (§18). */
31
+ export declare function jobBytes(job: GroupJob): number;
32
+ /** Analyzes a job with `resolver`, answering the seam's callbacks from the job's tables. */
33
+ export declare function analyzeJob(resolver: Resolver, job: GroupJob, catalog: SerializedCatalog, budget: Budget): AnalyzeGroupOutput;
@@ -0,0 +1,85 @@
1
+ // A link group as plain data (§18 Parallelism, §19 Serializable seams): the resolver seam's callbacks, `artifact`,
2
+ // `resolveImport`, `chunks` and `initialChunks`, written out as the tables they read, so that a group can cross a
3
+ // worker boundary as JSON and be analyzed there exactly as it would be in process.
4
+ import { chunkRegistries } from "./lib/chunk-registry.js";
5
+ /**
6
+ * Writes the group of `entry` out as a job: every artifact the seam's callbacks can reach from it, through the module
7
+ * requests `requestsOf` lists for each importer and the chunk registries each one's loaders read, and what each request
8
+ * and chunk lookup resolves to. A request `requestsOf` doesn't list resolves to nothing, as `resolveImport` must say
9
+ * for it too.
10
+ */
11
+ export function groupJob(input) {
12
+ const artifacts = new Map();
13
+ const imports = [];
14
+ const chunks = new Map();
15
+ const initialChunks = new Map();
16
+ const queue = [input.entry.url];
17
+ const seen = new Set(queue);
18
+ const reach = (to) => {
19
+ if (seen.has(to))
20
+ return;
21
+ seen.add(to);
22
+ queue.push(to);
23
+ };
24
+ for (let next = 0; next < queue.length; next++) {
25
+ const name = queue[next];
26
+ const source = input.artifact(name);
27
+ if (source === undefined)
28
+ continue;
29
+ artifacts.set(name, source);
30
+ for (const specifier of input.requestsOf(source.url)) {
31
+ const to = input.resolveImport(source.url, specifier);
32
+ if (to === undefined)
33
+ continue;
34
+ imports.push([source.url, specifier, to]);
35
+ reach(to);
36
+ }
37
+ if (input.chunks === undefined && input.initialChunks === undefined)
38
+ continue;
39
+ // The chunks the source's loaders link (§6 idiom 6), looked up as the link group looks them up.
40
+ for (const { registry, ids } of chunkRegistries(source.text, source.sourceType).loads) {
41
+ if (input.initialChunks !== undefined && !initialChunks.has(registry)) {
42
+ const names = [...input.initialChunks(registry)];
43
+ initialChunks.set(registry, names);
44
+ names.forEach(reach);
45
+ }
46
+ if (input.chunks === undefined)
47
+ continue;
48
+ for (const id of ids ?? [null]) {
49
+ const key = JSON.stringify([registry, id]);
50
+ if (chunks.has(key))
51
+ continue;
52
+ const names = [...input.chunks(registry, id)];
53
+ chunks.set(key, [registry, id, names]);
54
+ names.forEach(reach);
55
+ }
56
+ }
57
+ }
58
+ return {
59
+ entry: input.entry,
60
+ artifacts: [...artifacts],
61
+ imports,
62
+ ...(chunks.size === 0 ? {} : { chunks: [...chunks.values()] }),
63
+ ...(initialChunks.size === 0 ? {} : { initialChunks: [...initialChunks] }),
64
+ };
65
+ }
66
+ /** The UTF-8 bytes of a job's artifacts, which its step budget and its estimated peak heap scale with (§18). */
67
+ export function jobBytes(job) {
68
+ return job.artifacts.reduce((bytes, [, { text }]) => bytes + Buffer.byteLength(text, 'utf8'), 0);
69
+ }
70
+ /** Analyzes a job with `resolver`, answering the seam's callbacks from the job's tables. */
71
+ export function analyzeJob(resolver, job, catalog, budget) {
72
+ const artifacts = new Map(job.artifacts);
73
+ const imports = new Map(job.imports.map(([from, specifier, to]) => [JSON.stringify([from, specifier]), to]));
74
+ const chunks = new Map((job.chunks ?? []).map(([registry, id, names]) => [JSON.stringify([registry, id]), names]));
75
+ const initialChunks = new Map(job.initialChunks ?? []);
76
+ return resolver.analyzeGroup({
77
+ entry: job.entry,
78
+ artifact: (name) => artifacts.get(name),
79
+ resolveImport: (from, specifier) => imports.get(JSON.stringify([from, specifier])),
80
+ chunks: (registry, id) => chunks.get(JSON.stringify([registry, id])) ?? [],
81
+ initialChunks: (registry) => initialChunks.get(registry) ?? [],
82
+ catalog,
83
+ budget,
84
+ });
85
+ }
@@ -0,0 +1,34 @@
1
+ import type { Realm, SerializedCatalog } from '../../../catalog.ts';
2
+ import { type AstNode } from './ast.ts';
3
+ import type { Definitions } from './definitions.ts';
4
+ import { type GlobalResolution, type Position } from './global-object.ts';
5
+ import { type Judged, type Judgement } from './reachability.ts';
6
+ import type { Replaced } from './wrap.ts';
7
+ import type { Source, Write } from './writes.ts';
8
+ import type { Effect, GuardStep } from './types.ts';
9
+ export interface TagContext {
10
+ resolution: GlobalResolution;
11
+ definitions: Definitions;
12
+ catalog: SerializedCatalog;
13
+ realm: Realm;
14
+ /** The realm's global holder, e.g. `Window`. */
15
+ global: string;
16
+ /** The function whose own code holds a node, if any. */
17
+ functionOf(node: AstNode): AstNode | undefined;
18
+ /** The judgement of a write at `site` reached at `at`, along its call chain to its install entry (§7). */
19
+ judge(site: AstNode, at: Position, target: Judged['target']): Judgement;
20
+ /** Wrap evidence (§5 Resolver capabilities 10): whether each alternative of a value calls the original. */
21
+ evidence(sources: readonly Source[], replaced: Replaced): Set<boolean>;
22
+ }
23
+ /** The outcome a write gives: its target, and the effect the alternatives of its value must give to count. */
24
+ export interface Tagged {
25
+ name: string;
26
+ holder: string;
27
+ shadows?: string | undefined;
28
+ effect: Effect;
29
+ }
30
+ /**
31
+ * The absence tags of writes over one program: for a write, the outcome it gives and where it is, the `guard` steps of
32
+ * the absence tests that tag it, or undefined when it keeps no tag.
33
+ */
34
+ export declare function absenceTags(context: TagContext): (write: Write, outcome: Tagged, at: Position) => GuardStep[] | undefined;
@@ -0,0 +1,340 @@
1
+ // Absence tags on values (§5 Resolver capabilities 7, §7): a holder or value chosen on a branch where a native is
2
+ // absent keeps `guarded-native-absence` wherever it is later written, whether it travelled through variables,
3
+ // parameters or heap fields. The absence of any native counts, not only the target's.
4
+ //
5
+ // A write's holder and its written value are followed back, through the same routes the resolver evaluates them by, to
6
+ // the expressions they are chosen from: the arms of `?:`, `||`, `&&` and `??`, the reaching definitions of a variable,
7
+ // the arguments a parameter takes at each resolved call, and the expressions a heap field is given. Each expression on a
8
+ // route is judged as a write there would be (§7: the guards between it and its install entry), and a route is tagged
9
+ // when the guards along it, together, give `guarded-native-absence`. The alternatives that give the write combine by the
10
+ // OR rule: the write keeps the tag only when every one of them is tagged.
11
+ import { calleeExpression } from "./ast.js";
12
+ import { truthiness, windowOf } from "./global-object.js";
13
+ import { memberName } from "./keys.js";
14
+ import { along } from "./reachability.js";
15
+ import { frameOf, isFunction } from "./summaries.js";
16
+ const absent = 'guarded-native-absence';
17
+ /**
18
+ * The absence tags of writes over one program: for a write, the outcome it gives and where it is, the `guard` steps of
19
+ * the absence tests that tag it, or undefined when it keeps no tag.
20
+ */
21
+ export function absenceTags(context) {
22
+ const { resolution, definitions } = context;
23
+ const targets = context.catalog.targets[context.realm];
24
+ const target = (name) => (Object.hasOwn(targets, name) ? targets[name] : undefined);
25
+ /** The position code owned by `owner` is evaluated at: in the frame of `at`, or one around it, that instantiates it. */
26
+ const framed = (owner, at) => {
27
+ for (let frame = at.frame; frame !== undefined; frame = frame.outer) {
28
+ if (frame.fn === owner)
29
+ return { guard: () => undefined, topLevel: false, frame };
30
+ }
31
+ return { guard: () => undefined, topLevel: false };
32
+ };
33
+ /** Where a call is: its `typeof` guards, whether it is top-level, and its caller's frame. */
34
+ const positionOf = ({ site, caller }) => ({
35
+ guard: (name) => site.guards.get(name),
36
+ topLevel: site.topLevel,
37
+ frame: caller,
38
+ });
39
+ /** The resolved calls a function's parameters take their arguments from: its frame's, or every resolved call of it. */
40
+ const bindingsOf = (fn, frame) => {
41
+ const own = frameOf(frame, fn);
42
+ if (own === undefined)
43
+ return resolution.callersOf(fn);
44
+ return own.how === 'creation' ? [] : own.bindings;
45
+ };
46
+ /** The index of the parameter an identifier reads, when it is a plain parameter of the function it is in. */
47
+ const parameterOf = (id) => {
48
+ const fn = context.functionOf(id);
49
+ if (fn === undefined || !isFunction(fn))
50
+ return undefined;
51
+ const key = definitions.keyOf(id);
52
+ if (key === undefined || definitions.of(key).length > 0)
53
+ return undefined;
54
+ const index = fn['params'].findIndex((param) => param.type === 'Identifier' && param['name'] === id['name']);
55
+ return index < 0 ? undefined : { fn, index };
56
+ };
57
+ /**
58
+ * The expressions a holder or value is chosen from. `object` follows a member read's object when the heap doesn't
59
+ * give the read and the object is a native, as a holder written through a chain (`h.prototype`) is chosen where `h`
60
+ * is. `followed` ends a cycle.
61
+ */
62
+ const alternatives = (node, at, hops, object, followed) => {
63
+ const seen = `${node.start}:${node.end}@${at.frame?.id ?? ''}`;
64
+ if (followed.has(seen))
65
+ return [];
66
+ followed.add(seen);
67
+ const route = [...hops, { node, at, site: node }];
68
+ switch (node.type) {
69
+ case 'ParenthesizedExpression':
70
+ case 'ChainExpression':
71
+ case 'TSAsExpression':
72
+ case 'TSNonNullExpression':
73
+ return alternatives(node['expression'], at, hops, object, followed);
74
+ case 'ConditionalExpression':
75
+ return [
76
+ ...alternatives(node['consequent'], at, hops, object, followed),
77
+ ...alternatives(node['alternate'], at, hops, object, followed),
78
+ ];
79
+ case 'LogicalExpression': {
80
+ // A literal left side that can't be the result (a falsy one of `||`, a truthy one of `&&`, a nullish one of
81
+ // `??`) is no alternative: the right side's value is.
82
+ const operator = node['operator'];
83
+ const kept = (alternative) => {
84
+ if (alternative.start === true)
85
+ return operator === '&&';
86
+ if (!literal(alternative.node))
87
+ return true;
88
+ const values = resolution.evaluate(alternative.node, alternative.at);
89
+ if (operator === '??') {
90
+ return !values.every((value) => value.kind === 'constant' && (value.value === null || value.value === undefined));
91
+ }
92
+ const truthy = values.length === 0 ? undefined : truthiness(values);
93
+ return truthy === undefined || truthy === (operator === '||');
94
+ };
95
+ return [
96
+ ...alternatives(node['left'], at, hops, object, followed).filter(kept),
97
+ ...alternatives(node['right'], at, hops, object, followed),
98
+ ];
99
+ }
100
+ case 'SequenceExpression':
101
+ return alternatives(node['expressions'].at(-1), at, hops, object, followed);
102
+ case 'AssignmentExpression':
103
+ if (node['operator'] !== '=')
104
+ break;
105
+ return alternatives(node['right'], at, hops, object, followed);
106
+ case 'Identifier': {
107
+ const parameter = parameterOf(node);
108
+ if (parameter !== undefined) {
109
+ const bindings = bindingsOf(parameter.fn, at.frame);
110
+ if (bindings.length === 0)
111
+ break;
112
+ return bindings.flatMap((binding) => {
113
+ // An iteration native's callback is passed the element it visits (§5 Resolver capabilities 4).
114
+ if (binding.element !== undefined) {
115
+ const given = resolution.givenSource(binding, parameter.index);
116
+ if (given === undefined)
117
+ return [{ node, at, hops: route }];
118
+ return alternatives(given.node, given.at, route, object, followed);
119
+ }
120
+ const argument = binding.site.args[parameter.index];
121
+ const listed = binding.site.unlisted === undefined || parameter.index < binding.site.unlisted;
122
+ if (argument === undefined || argument.type === 'SpreadElement' || !listed)
123
+ return [{ node, at, hops: route }];
124
+ return alternatives(argument, positionOf(binding), route, object, followed);
125
+ });
126
+ }
127
+ const key = definitions.keyOf(node);
128
+ if (key === undefined)
129
+ break;
130
+ const reaching = definitions.reaching(node, key);
131
+ if (reaching.definitions.length === 0)
132
+ break;
133
+ const found = reaching.start ? [{ node, at, hops: route, start: true }] : [];
134
+ for (const definition of reaching.definitions) {
135
+ const where = framed(definition.owner, at);
136
+ if (definition.pattern.type !== 'Identifier' || definition.source === undefined) {
137
+ found.push({ node, at, hops: route });
138
+ continue;
139
+ }
140
+ // A logical assignment (`x ||= p`) chooses its source on its own test, and keeps the value it had otherwise.
141
+ const hop = definition.logical ? [...route, { node: definition.source, at: where, site: definition.site }] : route;
142
+ found.push(...alternatives(definition.source, where, hop, object, followed));
143
+ if (definition.logical)
144
+ found.push(...alternatives(definition.pattern, where, route, object, followed));
145
+ }
146
+ return found;
147
+ }
148
+ case 'MemberExpression': {
149
+ const sources = resolution.fieldSources(node, at);
150
+ if (sources !== undefined) {
151
+ return sources.flatMap((source) => alternatives(source.node, source.at, route, object, followed));
152
+ }
153
+ // A read in a function the position has no frame of is read in each frame the function is instantiated in.
154
+ const fn = context.functionOf(node);
155
+ if (fn !== undefined && frameOf(at.frame, fn) === undefined) {
156
+ const frames = resolution.framesOf(fn);
157
+ if (frames.length > 0) {
158
+ followed.delete(seen);
159
+ return frames.flatMap((frame) => alternatives(node, { ...at, frame }, hops, object, followed));
160
+ }
161
+ }
162
+ // A member of a native read from it: the holder is chosen where the object it is read from is.
163
+ const from = node['object'];
164
+ const native = (value) => value.kind === 'holder' || value.kind === 'native' || value.kind === 'member';
165
+ if (object && from.type !== 'Super' && resolution.evaluate(from, at).some(native)) {
166
+ return alternatives(from, at, route, object, followed).map((alternative) => ({
167
+ ...alternative,
168
+ descended: true,
169
+ }));
170
+ }
171
+ break;
172
+ }
173
+ }
174
+ return [{ node, at, hops: route }];
175
+ };
176
+ /** The `guard` steps that tag a route, or undefined when the guards along it don't give `guarded-native-absence`. */
177
+ const tagOf = (alternative, tested) => {
178
+ let reachability = 'unconditional';
179
+ const steps = [];
180
+ for (const hop of alternative.hops) {
181
+ const judged = context.judge(hop.site, hop.at, tested);
182
+ reachability = along(reachability, judged.reachability);
183
+ steps.push(...judged.steps.filter((step) => step.kind === 'guard' && step.result === absent));
184
+ }
185
+ return reachability === absent ? steps : undefined;
186
+ };
187
+ /**
188
+ * Whether a value is the native the target holds before the write, read through the window written, which writing
189
+ * back changes nothing. Another window's copy is no write-back.
190
+ */
191
+ const isOriginal = (value, original, window) => {
192
+ if (window === undefined || !('leaves' in value) || windowOf(value.leaves) !== window)
193
+ return false;
194
+ switch (value.kind) {
195
+ case 'member':
196
+ return value.target === original;
197
+ case 'native':
198
+ return value.name === original || memberName(context.global, value.name) === original;
199
+ case 'holder':
200
+ return target(original)?.holds === value.holder;
201
+ default:
202
+ return false;
203
+ }
204
+ };
205
+ /**
206
+ * Whether a holder's alternative can give the holder written: it holds a leaf the holder was reached from, or it may
207
+ * be a native, and, unless the holder is read from it, it may be that holder.
208
+ */
209
+ const givesHolder = (alternative, write) => {
210
+ if (alternative.start === true)
211
+ return false;
212
+ if (write.leaves.some((leaf) => within(leaf.node, alternative.node)))
213
+ return true;
214
+ const values = resolution.evaluate(alternative.node, alternative.at);
215
+ if (values.length > 0 && values.every((value) => value.kind === 'object' || value.kind === 'constant'))
216
+ return false;
217
+ if (alternative.descended === true)
218
+ return false;
219
+ return !(values.length > 0 && values.every((value) => value.kind === 'holder' && value.holder !== write.holder));
220
+ };
221
+ /** The tag every alternative carries, with the steps of the first; undefined when some alternative has none. */
222
+ const everyTagged = (found, tested) => {
223
+ if (found.length === 0)
224
+ return undefined;
225
+ let first;
226
+ for (const alternative of found) {
227
+ if (alternative.start === true)
228
+ return undefined;
229
+ const steps = tagOf(alternative, tested);
230
+ if (steps === undefined)
231
+ return undefined;
232
+ first ??= steps;
233
+ }
234
+ return first;
235
+ };
236
+ return (write, outcome, at) => {
237
+ const tested = { name: outcome.name, ...(outcome.shadows === undefined ? {} : { shadows: outcome.shadows }) };
238
+ const original = outcome.shadows ?? outcome.name;
239
+ const window = windowOf(write.leaves);
240
+ // The holder: the alternatives that can give the holder written.
241
+ const holder = holderExpression(write.site, resolution);
242
+ if (holder !== undefined) {
243
+ const found = alternatives(holder, at, [], true, new Set()).filter((alternative) => givesHolder(alternative, write));
244
+ const steps = everyTagged(found, tested);
245
+ if (steps !== undefined)
246
+ return steps;
247
+ }
248
+ // The value: the alternatives that change the target (no write-back of the original) and give the outcome's effect.
249
+ const sources = valueSources(write);
250
+ if (sources.length === 0)
251
+ return undefined;
252
+ const found = sources
253
+ .flatMap((source) => alternatives(source.node, source.at, [], false, new Set()))
254
+ .filter((alternative) => {
255
+ if (alternative.start === true)
256
+ return true;
257
+ const values = resolution.evaluate(alternative.node, alternative.at);
258
+ return values.length === 0 || !values.every((value) => isOriginal(value, original, window));
259
+ });
260
+ // Only an alternative that is tagged can tag the write, so the effect of each is judged only when one is.
261
+ if (!found.some((alternative) => alternative.start !== true && tagOf(alternative, tested) !== undefined))
262
+ return undefined;
263
+ const replaced = { holder: write.holder, original, leaves: write.leaves };
264
+ const giving = outcome.effect === 'wrap' || outcome.effect === 'replace'
265
+ ? found.filter((alternative) => {
266
+ if (alternative.start === true)
267
+ return outcome.effect === 'replace';
268
+ const answers = context.evidence([{ node: alternative.node, at: alternative.at }], replaced);
269
+ return answers.has(outcome.effect === 'wrap');
270
+ })
271
+ : found;
272
+ return everyTagged(giving, tested);
273
+ };
274
+ }
275
+ /** The expressions a write's new value comes from: an assigned value, or a defined descriptor's `value`. */
276
+ function valueSources(write) {
277
+ const { change } = write;
278
+ if (change.kind === 'set')
279
+ return change.sources ?? [];
280
+ if (change.kind !== 'define')
281
+ return [];
282
+ const descriptors = change.alternatives ?? [change.descriptor];
283
+ return descriptors.flatMap((descriptor) => (descriptor === 'refused' ? [] : (descriptor.value?.sources ?? [])));
284
+ }
285
+ /**
286
+ * The expression that gives the object a write writes: the object of the member an assignment, update or `delete`
287
+ * writes, the object a legacy accessor method is called on, or the first argument of a writer call (`Object.
288
+ * defineProperty(h, …)`, `Reflect.set(h, …)`, `Object.assign(h, …)`). Undefined for a write that names none.
289
+ */
290
+ function holderExpression(site, resolution) {
291
+ const objectOf = (member) => {
292
+ let node = member;
293
+ while (node.type === 'ChainExpression' || node.type === 'ParenthesizedExpression')
294
+ node = node['expression'];
295
+ return node.type === 'MemberExpression' ? node['object'] : undefined;
296
+ };
297
+ switch (site.type) {
298
+ case 'AssignmentExpression':
299
+ return objectOf(site['left']);
300
+ case 'UpdateExpression':
301
+ case 'UnaryExpression':
302
+ return objectOf(site['argument']);
303
+ case 'CallExpression': {
304
+ const callee = calleeExpression(site['callee']);
305
+ const key = callee.type === 'MemberExpression' && callee['computed'] !== true ? callee['property']['name'] : undefined;
306
+ if (typeof key === 'string' && key.startsWith('__'))
307
+ return callee['object'];
308
+ const [first] = site['arguments'];
309
+ return first === undefined || first.type === 'SpreadElement' ? undefined : first;
310
+ }
311
+ case 'Property': {
312
+ // A bulk mechanism's property: the object it is in is a call's argument, and the holder the call's first.
313
+ const literal = resolution.parentOf(site);
314
+ const call = literal === undefined ? undefined : resolution.parentOf(literal);
315
+ if (call?.type !== 'CallExpression')
316
+ return undefined;
317
+ const [first] = call['arguments'];
318
+ return first === undefined || first === literal || first.type === 'SpreadElement' ? undefined : first;
319
+ }
320
+ default:
321
+ return undefined;
322
+ }
323
+ }
324
+ /** Whether `node` lies within `outer`. */
325
+ const within = (node, outer) => node.start >= outer.start && node.end <= outer.end;
326
+ /** Whether an expression is a literal value: a literal, `undefined`, `void …`, or `!` of one. */
327
+ function literal(node) {
328
+ switch (node.type) {
329
+ case 'Literal':
330
+ return true;
331
+ case 'Identifier':
332
+ return node['name'] === 'undefined';
333
+ case 'TemplateLiteral':
334
+ return node['expressions'].length === 0;
335
+ case 'UnaryExpression':
336
+ return node['operator'] === 'void' || (node['operator'] === '!' && literal(node['argument']));
337
+ default:
338
+ return false;
339
+ }
340
+ }
@@ -0,0 +1,28 @@
1
+ export interface AstNode {
2
+ type: string;
3
+ start: number;
4
+ end: number;
5
+ [key: string]: unknown;
6
+ }
7
+ /**
8
+ * Visits every node in document order, without recursion: `enter` before a node's children, with its parent, and
9
+ * `leave` after them. When `enter` returns `false`, the node's children are skipped and `leave` is not called for it.
10
+ */
11
+ export declare function walkTree(root: AstNode, visitor: {
12
+ enter(node: AstNode, parent: AstNode | null): void | false;
13
+ leave?(node: AstNode): void;
14
+ }): void;
15
+ export declare function isNode(value: unknown): value is AstNode;
16
+ /** The expression a callee takes its function from: past the discarded operands of comma sequences, as in `(0, a.b)`. */
17
+ export declare function calleeExpression(callee: AstNode): AstNode;
18
+ /**
19
+ * A call that is a direct eval when its `eval` is the global one (ECMA-262 PerformEval): `eval(…)` through the plain
20
+ * name, parenthesized or not. An optional call (`eval?.(s)`) and a sequence (`(0, eval)(s)`) are indirect.
21
+ */
22
+ export declare function isEvalCall(node: AstNode): boolean;
23
+ /**
24
+ * Whether an object literal's property with key `key` is `__proto__: p`, which sets the literal's prototype rather than
25
+ * defining an own property (ECMA-262 PropertyDefinitionEvaluation). A computed key, a shorthand, a method or an
26
+ * accessor named `__proto__` defines an own property like any other.
27
+ */
28
+ export declare function setsLiteralPrototype(property: AstNode, key: string): boolean;
@@ -0,0 +1,64 @@
1
+ // oxc's ESTree nodes, walked generically through its visitor keys.
2
+ import { visitorKeys } from 'oxc-parser';
3
+ /**
4
+ * Visits every node in document order, without recursion: `enter` before a node's children, with its parent, and
5
+ * `leave` after them. When `enter` returns `false`, the node's children are skipped and `leave` is not called for it.
6
+ */
7
+ export function walkTree(root, visitor) {
8
+ const stack = [{ node: root, parent: null, leaving: false }];
9
+ while (stack.length > 0) {
10
+ const { node, parent, leaving } = stack.pop();
11
+ if (leaving) {
12
+ visitor.leave?.(node);
13
+ continue;
14
+ }
15
+ if (visitor.enter(node, parent) === false)
16
+ continue;
17
+ if (visitor.leave !== undefined)
18
+ stack.push({ node, parent, leaving: true });
19
+ const keys = visitorKeys[node.type] ?? [];
20
+ for (let k = keys.length - 1; k >= 0; k--) {
21
+ const child = node[keys[k]];
22
+ if (Array.isArray(child)) {
23
+ for (let c = child.length - 1; c >= 0; c--) {
24
+ if (isNode(child[c]))
25
+ stack.push({ node: child[c], parent: node, leaving: false });
26
+ }
27
+ }
28
+ else if (isNode(child))
29
+ stack.push({ node: child, parent: node, leaving: false });
30
+ }
31
+ }
32
+ }
33
+ export function isNode(value) {
34
+ return typeof value === 'object' && value !== null && typeof value.type === 'string';
35
+ }
36
+ /** The expression a callee takes its function from: past the discarded operands of comma sequences, as in `(0, a.b)`. */
37
+ export function calleeExpression(callee) {
38
+ let expression = callee;
39
+ while (expression.type === 'SequenceExpression')
40
+ expression = expression['expressions'].at(-1);
41
+ return expression;
42
+ }
43
+ /**
44
+ * A call that is a direct eval when its `eval` is the global one (ECMA-262 PerformEval): `eval(…)` through the plain
45
+ * name, parenthesized or not. An optional call (`eval?.(s)`) and a sequence (`(0, eval)(s)`) are indirect.
46
+ */
47
+ export function isEvalCall(node) {
48
+ if (node.type !== 'CallExpression' || node['optional'] === true)
49
+ return false;
50
+ const callee = node['callee'];
51
+ return callee.type === 'Identifier' && callee['name'] === 'eval';
52
+ }
53
+ /**
54
+ * Whether an object literal's property with key `key` is `__proto__: p`, which sets the literal's prototype rather than
55
+ * defining an own property (ECMA-262 PropertyDefinitionEvaluation). A computed key, a shorthand, a method or an
56
+ * accessor named `__proto__` defines an own property like any other.
57
+ */
58
+ export function setsLiteralPrototype(property, key) {
59
+ return (key === '__proto__' &&
60
+ property['kind'] === 'init' &&
61
+ property['computed'] !== true &&
62
+ property['shorthand'] !== true &&
63
+ property['method'] !== true);
64
+ }