@rangojs/router 0.22.1 → 0.24.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 (259) hide show
  1. package/dist/bin/rango.js +10 -4
  2. package/dist/testing/vitest.js +31 -1
  3. package/dist/types/browser/defer.d.ts +48 -0
  4. package/dist/types/browser/document-revealed.d.ts +17 -0
  5. package/dist/types/browser/event-controller.d.ts +7 -1
  6. package/dist/types/browser/merge-segment-loaders.d.ts +5 -2
  7. package/dist/types/browser/navigation-transaction.d.ts +21 -7
  8. package/dist/types/browser/partial-update.d.ts +39 -8
  9. package/dist/types/browser/react/NavigationProvider.d.ts +19 -5
  10. package/dist/types/browser/react/context.d.ts +3 -2
  11. package/dist/types/browser/react/filter-segment-order.d.ts +7 -0
  12. package/dist/types/browser/response-adapter.d.ts +7 -0
  13. package/dist/types/browser/types.d.ts +24 -10
  14. package/dist/types/browser/validate-redirect-origin.d.ts +11 -0
  15. package/dist/types/build/generate-route-types.d.ts +1 -1
  16. package/dist/types/build/route-types/router-processing.d.ts +1 -1
  17. package/dist/types/build/route-types/scan-filter.d.ts +22 -2
  18. package/dist/types/cache/cf/cf-cache-store.d.ts +16 -1
  19. package/dist/types/cache/cf/cf-cache-types.d.ts +23 -2
  20. package/dist/types/cache/document-cache-policy.d.ts +24 -0
  21. package/dist/types/cache/document-cache.d.ts +2 -10
  22. package/dist/types/client-urls/client-urls.d.ts +1 -1
  23. package/dist/types/client-urls/hoisted-revalidate.d.ts +13 -0
  24. package/dist/types/client-urls/navigation.d.ts +0 -37
  25. package/dist/types/client-urls/revalidate-registry.d.ts +4 -0
  26. package/dist/types/client-urls/server-projection.d.ts +29 -1
  27. package/dist/types/client-urls/types.d.ts +34 -69
  28. package/dist/types/client.d.ts +0 -1
  29. package/dist/types/context-var.d.ts +12 -0
  30. package/dist/types/errors.d.ts +15 -0
  31. package/dist/types/handles/deferred-resolution.d.ts +2 -1
  32. package/dist/types/handles/pushed-by.d.ts +31 -0
  33. package/dist/types/index.d.ts +4 -0
  34. package/dist/types/index.rsc.d.ts +2 -0
  35. package/dist/types/loader-store.d.ts +8 -0
  36. package/dist/types/prefetch-markers.d.ts +11 -0
  37. package/dist/types/response-utils.d.ts +7 -0
  38. package/dist/types/reverse.d.ts +8 -8
  39. package/dist/types/route-definition/helpers-types.d.ts +27 -0
  40. package/dist/types/route-definition/rewrite.d.ts +56 -0
  41. package/dist/types/route-map-builder.d.ts +7 -2
  42. package/dist/types/router/basename.d.ts +7 -0
  43. package/dist/types/router/handler-context.d.ts +1 -5
  44. package/dist/types/router/internal-params.d.ts +8 -0
  45. package/dist/types/router/is-action.d.ts +2 -1
  46. package/dist/types/router/middleware-types.d.ts +15 -0
  47. package/dist/types/router/revalidation.d.ts +1 -2
  48. package/dist/types/router/route-trie-builder.d.ts +10 -0
  49. package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -2
  50. package/dist/types/router/segment-resolution/mask-nested.d.ts +4 -2
  51. package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +264 -0
  52. package/dist/types/rsc/full-payload.d.ts +11 -0
  53. package/dist/types/rsc/handler.d.ts +33 -0
  54. package/dist/types/rsc/helpers.d.ts +8 -0
  55. package/dist/types/rsc/loader-fetch.d.ts +16 -0
  56. package/dist/types/rsc/rewrite-passes.d.ts +74 -0
  57. package/dist/types/rsc/ssr-setup.d.ts +3 -1
  58. package/dist/types/server/context.d.ts +21 -1
  59. package/dist/types/server/handle-store.d.ts +9 -1
  60. package/dist/types/server/request-context.d.ts +52 -2
  61. package/dist/types/server/rewrite-trail.d.ts +49 -0
  62. package/dist/types/server/slot-accessors.d.ts +29 -0
  63. package/dist/types/server.d.ts +1 -0
  64. package/dist/types/testing/flight.entry.d.ts +1 -1
  65. package/dist/types/testing/index.d.ts +4 -4
  66. package/dist/types/testing/internal/action-id.d.ts +10 -1
  67. package/dist/types/testing/internal/client-urls-projection.d.ts +16 -0
  68. package/dist/types/testing/internal/test-action.d.ts +8 -0
  69. package/dist/types/testing/render-route.d.ts +8 -0
  70. package/dist/types/testing/run-loader.d.ts +2 -1
  71. package/dist/types/testing/run-middleware.d.ts +20 -0
  72. package/dist/types/testing/run-revalidate.d.ts +74 -0
  73. package/dist/types/testing/run-transition-when.d.ts +2 -3
  74. package/dist/types/testing/serve-shell-request.d.ts +40 -1
  75. package/dist/types/testing/vitest.d.ts +11 -0
  76. package/dist/types/thenable-status.d.ts +6 -0
  77. package/dist/types/types/boundaries.d.ts +9 -0
  78. package/dist/types/types/handler-context.d.ts +14 -0
  79. package/dist/types/types/loader-types.d.ts +30 -0
  80. package/dist/types/types/request-scope.d.ts +7 -0
  81. package/dist/types/types/segments.d.ts +21 -4
  82. package/dist/types/urls/path-helper-types.d.ts +25 -4
  83. package/dist/types/urls/type-extraction.d.ts +6 -2
  84. package/dist/types/vite/discovery/discover-routers.d.ts +9 -0
  85. package/dist/types/vite/discovery/state.d.ts +14 -4
  86. package/dist/types/vite/plugins/client-urls-revalidate-hoist.d.ts +54 -0
  87. package/dist/types/vite/plugins/hoist/literal.d.ts +83 -0
  88. package/dist/types/vite/plugins/hoist/scope.d.ts +40 -0
  89. package/dist/types/vite/plugins/hoist/source.d.ts +41 -0
  90. package/dist/types/vite/plugins/transition-when-hoist.d.ts +8 -11
  91. package/dist/types/vite/router-discovery.d.ts +10 -0
  92. package/dist/vite/index.js +974 -379
  93. package/package.json +1 -1
  94. package/skills/caching/SKILL.md +54 -5
  95. package/skills/catalog.json +3 -3
  96. package/skills/client-urls/SKILL.md +197 -24
  97. package/skills/cloudflare/SKILL.md +14 -9
  98. package/skills/hooks/SKILL.md +16 -3
  99. package/skills/hooks/data.md +3 -1
  100. package/skills/links/SKILL.md +24 -0
  101. package/skills/loader/SKILL.md +199 -2
  102. package/skills/middleware/SKILL.md +183 -2
  103. package/skills/migrate-nextjs/SKILL.md +2 -2
  104. package/skills/migrate-react-router/SKILL.md +10 -7
  105. package/skills/migrate-react-router/data-and-actions.md +1 -1
  106. package/skills/ppr/SKILL.md +22 -0
  107. package/skills/prerender/SKILL.md +22 -8
  108. package/skills/rango/SKILL.md +13 -7
  109. package/skills/route/SKILL.md +139 -0
  110. package/skills/server-actions/SKILL.md +27 -0
  111. package/skills/testing/SKILL.md +3 -2
  112. package/skills/testing/client-components.md +2 -1
  113. package/skills/testing/middleware.md +31 -10
  114. package/skills/testing/predicates.md +67 -32
  115. package/skills/testing/response-routes.md +1 -0
  116. package/skills/testing/serve-shell-request.md +107 -19
  117. package/skills/testing/setup.md +2 -2
  118. package/skills/typesafety/route-types.md +2 -2
  119. package/skills/use-cache/SKILL.md +4 -2
  120. package/skills/view-transitions/SKILL.md +26 -0
  121. package/src/__augment-tests__/augmented.check.ts +20 -0
  122. package/src/browser/defer.ts +82 -0
  123. package/src/browser/document-revealed.ts +52 -0
  124. package/src/browser/event-controller.ts +46 -32
  125. package/src/browser/merge-segment-loaders.ts +31 -18
  126. package/src/browser/navigation-bridge.ts +82 -16
  127. package/src/browser/navigation-client.ts +48 -36
  128. package/src/browser/navigation-store.ts +13 -0
  129. package/src/browser/navigation-transaction.ts +25 -8
  130. package/src/browser/partial-update.ts +554 -146
  131. package/src/browser/prefetch/fetch.ts +2 -1
  132. package/src/browser/react/Link.tsx +7 -9
  133. package/src/browser/react/NavigationProvider.tsx +88 -49
  134. package/src/browser/react/context.ts +3 -2
  135. package/src/browser/react/filter-segment-order.ts +13 -2
  136. package/src/browser/react/use-router.ts +2 -4
  137. package/src/browser/response-adapter.ts +20 -0
  138. package/src/browser/rsc-router.tsx +17 -45
  139. package/src/browser/server-action-bridge.ts +25 -63
  140. package/src/browser/types.ts +24 -10
  141. package/src/browser/validate-redirect-origin.ts +16 -0
  142. package/src/build/generate-route-types.ts +1 -0
  143. package/src/build/route-types/router-processing.ts +3 -6
  144. package/src/build/route-types/scan-filter.ts +45 -11
  145. package/src/cache/cf/cf-cache-store.ts +59 -21
  146. package/src/cache/cf/cf-cache-types.ts +24 -2
  147. package/src/cache/document-cache-policy.ts +131 -0
  148. package/src/cache/document-cache.ts +21 -107
  149. package/src/client-urls/client-urls.ts +22 -6
  150. package/src/client-urls/hoisted-revalidate.ts +25 -0
  151. package/src/client-urls/navigation.ts +0 -96
  152. package/src/client-urls/revalidate-registry.ts +30 -0
  153. package/src/client-urls/server-projection.ts +151 -65
  154. package/src/client-urls/types.ts +39 -71
  155. package/src/client.tsx +9 -5
  156. package/src/context-var.ts +32 -2
  157. package/src/errors.ts +34 -0
  158. package/src/handles/deferred-resolution.ts +28 -5
  159. package/src/handles/pushed-by.ts +148 -0
  160. package/src/index.rsc.ts +8 -0
  161. package/src/index.ts +7 -0
  162. package/src/loader-store.ts +18 -7
  163. package/src/prefetch-markers.ts +13 -0
  164. package/src/response-utils.ts +19 -0
  165. package/src/reverse.ts +17 -14
  166. package/src/route-content-wrapper.tsx +18 -4
  167. package/src/route-definition/dsl-helpers.ts +27 -4
  168. package/src/route-definition/helpers-types.ts +27 -1
  169. package/src/route-definition/redirect.ts +2 -15
  170. package/src/route-definition/rewrite.ts +146 -0
  171. package/src/route-map-builder.ts +14 -11
  172. package/src/router/basename.ts +20 -0
  173. package/src/router/handler-context.ts +3 -13
  174. package/src/router/intercept-resolution.ts +35 -16
  175. package/src/router/internal-params.ts +16 -0
  176. package/src/router/is-action.ts +2 -1
  177. package/src/router/loader-resolution.ts +85 -4
  178. package/src/router/match-api.ts +36 -1
  179. package/src/router/match-middleware/background-revalidation.ts +16 -4
  180. package/src/router/match-middleware/cache-lookup.ts +35 -7
  181. package/src/router/match-middleware/cache-store.ts +15 -1
  182. package/src/router/match-middleware/intercept-resolution.ts +4 -2
  183. package/src/router/match-result.ts +14 -2
  184. package/src/router/middleware-types.ts +16 -0
  185. package/src/router/middleware.ts +127 -49
  186. package/src/router/navigation-snapshot.ts +2 -1
  187. package/src/router/revalidation.ts +1 -1
  188. package/src/router/route-trie-builder.ts +28 -1
  189. package/src/router/segment-resolution/fresh.ts +22 -18
  190. package/src/router/segment-resolution/loader-cache.ts +2 -1
  191. package/src/router/segment-resolution/loader-mask.ts +5 -3
  192. package/src/router/segment-resolution/loader-snapshot.ts +18 -4
  193. package/src/router/segment-resolution/mask-nested.ts +8 -4
  194. package/src/router/segment-resolution/prefetch-deferral.ts +583 -0
  195. package/src/router/segment-resolution/revalidation.ts +215 -82
  196. package/src/rsc/full-payload.ts +37 -5
  197. package/src/rsc/handler.ts +82 -55
  198. package/src/rsc/helpers.ts +24 -0
  199. package/src/rsc/loader-fetch.ts +79 -24
  200. package/src/rsc/response-cache-serve.ts +18 -2
  201. package/src/rsc/response-route-handler.ts +4 -7
  202. package/src/rsc/rewrite-passes.ts +203 -0
  203. package/src/rsc/rsc-rendering.ts +54 -9
  204. package/src/rsc/shell-capture.ts +2 -1
  205. package/src/rsc/ssr-setup.ts +11 -5
  206. package/src/segment-loader-promise.ts +23 -20
  207. package/src/segment-system.tsx +60 -26
  208. package/src/server/context.ts +39 -5
  209. package/src/server/cookie-parse.ts +36 -12
  210. package/src/server/cookie-store.ts +7 -3
  211. package/src/server/handle-store.ts +152 -25
  212. package/src/server/request-context.ts +175 -64
  213. package/src/server/request-headers.ts +4 -14
  214. package/src/server/rewrite-trail.ts +190 -0
  215. package/src/server/slot-accessors.ts +57 -0
  216. package/src/server.ts +4 -0
  217. package/src/ssr/index.tsx +118 -16
  218. package/src/testing/action-form.ts +2 -11
  219. package/src/testing/dispatch.ts +138 -67
  220. package/src/testing/e2e/page-helpers.ts +3 -1
  221. package/src/testing/flight.entry.ts +1 -0
  222. package/src/testing/index.ts +7 -3
  223. package/src/testing/internal/action-id.ts +16 -1
  224. package/src/testing/internal/client-urls-projection.ts +70 -0
  225. package/src/testing/internal/test-action.ts +25 -0
  226. package/src/testing/render-route.tsx +16 -3
  227. package/src/testing/run-loader.ts +4 -1
  228. package/src/testing/run-middleware.ts +56 -11
  229. package/src/testing/run-revalidate.ts +142 -0
  230. package/src/testing/run-transition-when.ts +7 -8
  231. package/src/testing/serve-shell-request.ts +131 -8
  232. package/src/testing/vitest.ts +67 -1
  233. package/src/thenable-status.ts +11 -0
  234. package/src/types/boundaries.ts +12 -0
  235. package/src/types/handler-context.ts +14 -0
  236. package/src/types/loader-types.ts +30 -0
  237. package/src/types/request-scope.ts +8 -0
  238. package/src/types/segments.ts +26 -4
  239. package/src/urls/path-helper-types.ts +27 -5
  240. package/src/urls/type-extraction.ts +30 -10
  241. package/src/use-loader.tsx +18 -13
  242. package/src/vite/discovery/client-urls-projection.ts +20 -21
  243. package/src/vite/discovery/discover-routers.ts +94 -26
  244. package/src/vite/discovery/state.ts +28 -4
  245. package/src/vite/discovery/virtual-module-codegen.ts +22 -17
  246. package/src/vite/plugins/client-urls-revalidate-hoist.ts +564 -0
  247. package/src/vite/plugins/expose-internal-ids.ts +2 -3
  248. package/src/vite/plugins/hoist/literal.ts +417 -0
  249. package/src/vite/plugins/hoist/scope.ts +370 -0
  250. package/src/vite/plugins/hoist/source.ts +84 -0
  251. package/src/vite/plugins/transition-when-hoist.ts +113 -779
  252. package/src/vite/rango.ts +2 -0
  253. package/src/vite/router-discovery.ts +116 -53
  254. package/dist/types/client-urls/revalidate-chain.d.ts +0 -33
  255. package/dist/types/client-urls/revalidation-protocol.d.ts +0 -25
  256. package/dist/types/testing/run-client-revalidate.d.ts +0 -43
  257. package/src/client-urls/revalidate-chain.ts +0 -83
  258. package/src/client-urls/revalidation-protocol.ts +0 -56
  259. package/src/testing/run-client-revalidate.ts +0 -92
@@ -0,0 +1,417 @@
1
+ /**
2
+ * Literal-level helpers shared by the build-time predicate hoists
3
+ * (transition-when-hoist.ts moves a `when` from a server module into a
4
+ * "use client" one; client-urls-revalidate-hoist.ts moves a `revalidate()`
5
+ * from a "use client" module into a server one): finding a urls-callback
6
+ * helper's calls, the lexical-capture check, and the free imports a literal
7
+ * re-emits in its new module.
8
+ */
9
+
10
+ import {
11
+ TYPE_KEYS,
12
+ type ImportBinding,
13
+ type Node,
14
+ type Reference,
15
+ type Scope,
16
+ } from "./scope.js";
17
+
18
+ /** The offset after the directive prologue (0 when there is none). */
19
+ export function directivePrologueEnd(program: Node): number {
20
+ let end = 0;
21
+ for (const statement of program.body) {
22
+ if (
23
+ statement.type !== "ExpressionStatement" ||
24
+ typeof statement.directive !== "string"
25
+ ) {
26
+ break;
27
+ }
28
+ end = statement.end;
29
+ }
30
+ return end;
31
+ }
32
+
33
+ export function parseLang(file: string): "ts" | "tsx" | "jsx" {
34
+ // A .ts file parses as ts: tsx would reject `<T>expr` and `<T>(x) => x`.
35
+ if (/\.[cm]?ts$/.test(file)) return "ts";
36
+ if (/\.[cm]?jsx?$/.test(file)) return "jsx";
37
+ return "tsx";
38
+ }
39
+
40
+ const EXPRESSION_WRAPPERS = new Set([
41
+ "TSAsExpression",
42
+ "TSSatisfiesExpression",
43
+ "TSNonNullExpression",
44
+ "TSTypeAssertion",
45
+ "ParenthesizedExpression",
46
+ ]);
47
+
48
+ export function unwrapExpression(node: Node): Node {
49
+ while (node && EXPRESSION_WRAPPERS.has(node.type)) node = node.expression;
50
+ return node;
51
+ }
52
+
53
+ export function isFunctionLiteral(node: Node): boolean {
54
+ return (
55
+ node?.type === "ArrowFunctionExpression" ||
56
+ node?.type === "FunctionExpression"
57
+ );
58
+ }
59
+
60
+ export function propertyKeyIs(prop: Node, name: string): boolean {
61
+ return (
62
+ prop?.type === "Property" &&
63
+ !prop.computed &&
64
+ ((prop.key.type === "Identifier" && prop.key.name === name) ||
65
+ (prop.key.type === "Literal" && prop.key.value === name))
66
+ );
67
+ }
68
+
69
+ /** Which helper calls {@link findHelperCalls} looks for. */
70
+ interface HelperCallSpec {
71
+ /** Module specifiers the builder (and a direct helper import) come from. */
72
+ readonly sources: ReadonlySet<string>;
73
+ /** The builder export whose callback's first parameter hands out `helper`. */
74
+ readonly builder: string;
75
+ readonly helper: string;
76
+ /** A direct import of `helper` from `sources` counts too. */
77
+ readonly directImport: boolean;
78
+ }
79
+
80
+ interface HelperCall {
81
+ readonly call: Node;
82
+ readonly scope: Scope;
83
+ }
84
+
85
+ /** What a builder callback's first parameter binds the helper to. */
86
+ interface HelperBindings {
87
+ /** `({ helper })`, `({ helper: h })`. */
88
+ readonly names: Set<string>;
89
+ /** `(helpers) => helpers.helper(...)`. */
90
+ readonly objects: Set<string>;
91
+ }
92
+
93
+ /**
94
+ * Every call of `spec.helper` the builder's callback hands out: destructured,
95
+ * renamed or as `helpers.<helper>`, at any depth of the callback, plus a
96
+ * direct import when `spec.directImport`. A builder is recognised by its
97
+ * import (named, renamed, or through a namespace); any other function of the
98
+ * same name is left alone.
99
+ */
100
+ export function findHelperCalls(
101
+ program: Node,
102
+ scopeOf: Map<Node, Scope>,
103
+ moduleScope: Scope,
104
+ imports: Map<string, ImportBinding>,
105
+ spec: HelperCallSpec,
106
+ ): HelperCall[] {
107
+ const calls: HelperCall[] = [];
108
+ const helpers = new Map<Scope, HelperBindings>();
109
+
110
+ // The export a callee names: `urls`, `transition as t`, `Rango.urls`.
111
+ const sourceExport = (callee: Node, scope: Scope): string | null => {
112
+ const sourceImport = (id: Node): ImportBinding | null => {
113
+ if (id?.type !== "Identifier") return null;
114
+ if (scope.lookup(id.name) !== moduleScope) return null;
115
+ const binding = imports.get(id.name);
116
+ return binding && spec.sources.has(binding.source) && !binding.typeOnly
117
+ ? binding
118
+ : null;
119
+ };
120
+ if (callee.type === "Identifier") {
121
+ const imported = sourceImport(callee)?.imported;
122
+ return imported && imported !== "*" && imported !== "default"
123
+ ? imported
124
+ : null;
125
+ }
126
+ if (
127
+ callee.type === "MemberExpression" &&
128
+ !callee.computed &&
129
+ callee.property.type === "Identifier" &&
130
+ sourceImport(callee.object)?.imported === "*"
131
+ ) {
132
+ return callee.property.name;
133
+ }
134
+ return null;
135
+ };
136
+
137
+ const registerBuilderCallback = (callback: Node): void => {
138
+ const fnScope = scopeOf.get(callback);
139
+ let param = callback.params[0];
140
+ if (param?.type === "AssignmentPattern") param = param.left;
141
+ if (!fnScope || !param) return;
142
+ const bindings: HelperBindings = { names: new Set(), objects: new Set() };
143
+ if (param.type === "Identifier") bindings.objects.add(param.name);
144
+ if (param.type === "ObjectPattern") {
145
+ for (const prop of param.properties) {
146
+ if (!propertyKeyIs(prop, spec.helper)) continue;
147
+ const target =
148
+ prop.value.type === "AssignmentPattern"
149
+ ? prop.value.left
150
+ : prop.value;
151
+ if (target.type === "Identifier") bindings.names.add(target.name);
152
+ }
153
+ }
154
+ helpers.set(fnScope, bindings);
155
+ };
156
+
157
+ const isHelper = (callee: Node, scope: Scope): boolean => {
158
+ if (spec.directImport && sourceExport(callee, scope) === spec.helper) {
159
+ return true;
160
+ }
161
+ if (callee.type === "Identifier") {
162
+ const decl = scope.lookup(callee.name);
163
+ return !!decl && !!helpers.get(decl)?.names.has(callee.name);
164
+ }
165
+ if (
166
+ callee.type === "MemberExpression" &&
167
+ !callee.computed &&
168
+ callee.property.type === "Identifier" &&
169
+ callee.property.name === spec.helper &&
170
+ callee.object.type === "Identifier"
171
+ ) {
172
+ const decl = scope.lookup(callee.object.name);
173
+ return !!decl && !!helpers.get(decl)?.objects.has(callee.object.name);
174
+ }
175
+ return false;
176
+ };
177
+
178
+ const visit = (node: Node, scope: Scope): void => {
179
+ if (!node || typeof node !== "object") return;
180
+ if (Array.isArray(node)) {
181
+ for (const item of node) visit(item, scope);
182
+ return;
183
+ }
184
+ if (typeof node.type !== "string") return;
185
+ const own = scopeOf.get(node) ?? scope;
186
+ if (node.type === "CallExpression") {
187
+ // Registered before the callback body is visited, which is where its
188
+ // helper calls are.
189
+ const callback = node.arguments[0];
190
+ if (
191
+ isFunctionLiteral(callback) &&
192
+ sourceExport(node.callee, own) === spec.builder
193
+ ) {
194
+ registerBuilderCallback(callback);
195
+ }
196
+ if (isHelper(node.callee, own)) calls.push({ call: node, scope: own });
197
+ }
198
+ for (const key of Object.keys(node)) {
199
+ if (key === "type" || key === "start" || key === "end") continue;
200
+ visit(node[key], own);
201
+ }
202
+ };
203
+ visit(program.body, moduleScope);
204
+ return calls;
205
+ }
206
+
207
+ /**
208
+ * What a lexical-capture error says, per hoist direction. `lexical` explains
209
+ * each construct (`this`, `arguments`, `super`, `new.target`); `captureHint`
210
+ * and `loadsHint` follow every capture and module-load message;
211
+ * `importMetaWhy`, `importMetaMemberWhy` and `dynamicImportWhy` explain the
212
+ * module-loading expressions.
213
+ */
214
+ export interface LexicalCaptureMessages {
215
+ readonly lexical: Readonly<Record<string, string>>;
216
+ readonly captureHint: string;
217
+ readonly loadsHint: string;
218
+ readonly importMetaWhy: string;
219
+ readonly importMetaMemberWhy: string;
220
+ readonly dynamicImportWhy: string;
221
+ }
222
+
223
+ /**
224
+ * The first use in a literal of a lexical context it would lose when hoisted
225
+ * (`this`, `arguments`, `super`, `new.target` of an enclosing function or
226
+ * object), or of a module-loading expression the build cannot check
227
+ * (`import()`, `import.meta` other than `import.meta.env`). A nested
228
+ * non-arrow function (or class body) owns its `this`/`arguments`, so uses
229
+ * inside one are fine; a method literal owns them too, but its `super`
230
+ * loses the object it was defined on.
231
+ */
232
+ export function findLexicalCapture(
233
+ literal: Node,
234
+ references: readonly Reference[],
235
+ code: string,
236
+ messages: LexicalCaptureMessages,
237
+ ): { node: Node; detail: string } | null {
238
+ const ownArguments: Node[] = [];
239
+ let found: { node: Node; detail: string } | null = null;
240
+ const capture = (node: Node, what: string): void => {
241
+ found = {
242
+ node,
243
+ detail: `${messages.lexical[what]} ${messages.captureHint}`,
244
+ };
245
+ };
246
+ const loads = (node: Node, expression: string, why: string): void => {
247
+ found = {
248
+ node,
249
+ detail: `\`${expression}\` ${why} ${messages.loadsHint}`,
250
+ };
251
+ };
252
+
253
+ const visit = (node: Node, boundary: Node | null): void => {
254
+ if (found || !node || typeof node !== "object") return;
255
+ if (Array.isArray(node)) {
256
+ for (const item of node) visit(item, boundary);
257
+ return;
258
+ }
259
+ if (typeof node.type !== "string") return;
260
+ switch (node.type) {
261
+ case "FunctionExpression":
262
+ case "FunctionDeclaration":
263
+ ownArguments.push(node);
264
+ boundary = node;
265
+ break;
266
+ case "ClassBody":
267
+ boundary = node;
268
+ break;
269
+ case "ThisExpression":
270
+ if (!boundary) capture(node, "this");
271
+ return;
272
+ case "Super":
273
+ if (!boundary || boundary === literal) capture(node, "super");
274
+ return;
275
+ case "JSXMemberExpression": {
276
+ let root = node;
277
+ while (root.type === "JSXMemberExpression") root = root.object;
278
+ if (root.name === "this" && !boundary) capture(root, "this");
279
+ return;
280
+ }
281
+ case "MetaProperty":
282
+ if (node.meta.name === "import") {
283
+ loads(node, "import.meta", messages.importMetaWhy);
284
+ } else if (!boundary) {
285
+ capture(node, "new.target");
286
+ }
287
+ return;
288
+ case "MemberExpression":
289
+ if (node.object?.type === "MetaProperty") {
290
+ if (node.object.meta.name !== "import") break;
291
+ if (!node.computed && node.property.name === "env") return;
292
+ loads(
293
+ node,
294
+ node.computed
295
+ ? "import.meta[...]"
296
+ : `import.meta.${node.property.name}`,
297
+ messages.importMetaMemberWhy,
298
+ );
299
+ return;
300
+ }
301
+ break;
302
+ case "ImportExpression":
303
+ loads(
304
+ node,
305
+ `import(${code.slice(node.source.start, node.source.end)})`,
306
+ messages.dynamicImportWhy,
307
+ );
308
+ return;
309
+ }
310
+ for (const key of Object.keys(node)) {
311
+ if (key === "type" || key === "start" || key === "end") continue;
312
+ if (TYPE_KEYS.has(key)) continue;
313
+ visit(node[key], boundary);
314
+ }
315
+ };
316
+ visit(literal, null);
317
+ if (found) return found;
318
+
319
+ for (const ref of references) {
320
+ if (ref.typeOnly || ref.node.name !== "arguments") continue;
321
+ if (ref.node.start < literal.start || ref.node.end > literal.end) continue;
322
+ const owned = ownArguments.some(
323
+ (fn) => fn.start <= ref.node.start && ref.node.end <= fn.end,
324
+ );
325
+ if (!owned) {
326
+ capture(ref.node, "arguments");
327
+ return found;
328
+ }
329
+ }
330
+ return null;
331
+ }
332
+
333
+ /** The free references of a literal, by what they resolve to. */
334
+ interface FreeReferences {
335
+ /** Import local name -> whether any use is a value use. */
336
+ readonly imports: Map<string, boolean>;
337
+ /** Value references to a binding declared outside the literal that is not an import. */
338
+ readonly bindings: readonly Reference[];
339
+ /** References declared nowhere (window, URL, console, ...). */
340
+ readonly globals: readonly Reference[];
341
+ }
342
+
343
+ /** Every reference inside `literal` to something declared outside it. */
344
+ export function collectFreeReferences(
345
+ literal: Node,
346
+ references: readonly Reference[],
347
+ moduleScope: Scope,
348
+ imports: Map<string, ImportBinding>,
349
+ ): FreeReferences {
350
+ const used = new Map<string, boolean>();
351
+ const bindings: Reference[] = [];
352
+ const globals: Reference[] = [];
353
+ for (const ref of references) {
354
+ if (ref.node.start < literal.start || ref.node.end > literal.end) continue;
355
+ const decl = ref.scope.lookup(ref.node.name);
356
+ if (!decl) {
357
+ if (!ref.typeOnly) globals.push(ref);
358
+ continue;
359
+ }
360
+ if (
361
+ decl.node.start >= literal.start &&
362
+ decl.node.end <= literal.end &&
363
+ decl !== moduleScope
364
+ ) {
365
+ continue; // declared inside the literal
366
+ }
367
+ if (decl === moduleScope && imports.has(ref.node.name)) {
368
+ const valueUse = !ref.typeOnly && !imports.get(ref.node.name)!.typeOnly;
369
+ used.set(ref.node.name, (used.get(ref.node.name) ?? false) || valueUse);
370
+ continue;
371
+ }
372
+ if (ref.typeOnly) continue; // a local type: erased
373
+ bindings.push(ref);
374
+ }
375
+ return { imports: used, bindings, globals };
376
+ }
377
+
378
+ /**
379
+ * Re-emit the imports a literal uses, grouped per source, type-only uses as
380
+ * `import type`. `checkValueImport` runs first for each value use and throws
381
+ * to reject one.
382
+ */
383
+ export async function emitImports(
384
+ used: Map<string, boolean>,
385
+ imports: Map<string, ImportBinding>,
386
+ checkValueImport: (
387
+ local: string,
388
+ binding: ImportBinding,
389
+ ) => Promise<void> | void,
390
+ ): Promise<string[]> {
391
+ const bySource = new Map<string, string[]>();
392
+ for (const [local, valueUse] of used) {
393
+ const binding = imports.get(local)!;
394
+ if (valueUse) await checkValueImport(local, binding);
395
+ const lines = bySource.get(binding.source) ?? [];
396
+ const typePrefix = valueUse ? "" : "type ";
397
+ if (binding.imported === "*") {
398
+ lines.push(
399
+ `import ${typePrefix}* as ${local} from ${JSON.stringify(binding.source)};`,
400
+ );
401
+ } else if (binding.imported === "default") {
402
+ lines.push(
403
+ `import ${typePrefix}${local} from ${JSON.stringify(binding.source)};`,
404
+ );
405
+ } else {
406
+ const spec =
407
+ binding.imported === local
408
+ ? local
409
+ : `${JSON.stringify(binding.imported)} as ${local}`;
410
+ lines.push(
411
+ `import ${typePrefix}{ ${spec} } from ${JSON.stringify(binding.source)};`,
412
+ );
413
+ }
414
+ bySource.set(binding.source, lines);
415
+ }
416
+ return [...bySource.values()].flat();
417
+ }