@abide/abide 0.49.0 → 0.50.1

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 (325) hide show
  1. package/AGENTS.md +564 -437
  2. package/CHANGELOG.md +183 -0
  3. package/README.md +165 -202
  4. package/package.json +14 -5
  5. package/src/abideLsp.ts +5 -6
  6. package/src/abideResolverPlugin.ts +197 -169
  7. package/src/build.ts +79 -56
  8. package/src/buildDisconnected.ts +0 -2
  9. package/src/checkAbide.ts +12 -2
  10. package/src/devEntry.ts +113 -28
  11. package/src/discoveryEntry.ts +3 -2
  12. package/src/lib/bundle/disconnected.abide +69 -73
  13. package/src/lib/bundle/infoPlist.ts +13 -7
  14. package/src/lib/bundle/spawnEmbeddedServer.ts +13 -4
  15. package/src/lib/bundle/waitForServer.ts +6 -1
  16. package/src/lib/mcp/mcpTools.ts +15 -6
  17. package/src/lib/server/error.ts +6 -6
  18. package/src/lib/server/json.ts +17 -3
  19. package/src/lib/server/rpc/defineRpc.ts +58 -24
  20. package/src/lib/server/rpc/parseArgs.ts +98 -13
  21. package/src/lib/server/rpc/resolveRpcJsonSchema.ts +28 -0
  22. package/src/lib/server/rpc/types/RpcHelper.ts +75 -105
  23. package/src/lib/server/rpc/types/RpcRegistryEntry.ts +24 -9
  24. package/src/lib/server/rpc/validationError.ts +1 -1
  25. package/src/lib/server/runtime/DEV_RELOAD_CLIENT_SCRIPT.ts +0 -15
  26. package/src/lib/server/runtime/buildCacheSnapshot.ts +10 -9
  27. package/src/lib/server/runtime/buildInspectorSurface.ts +6 -4
  28. package/src/lib/server/runtime/buildOpenApiSpec.ts +55 -11
  29. package/src/lib/server/runtime/buildPreloadManifest.ts +12 -10
  30. package/src/lib/server/runtime/createAppAssetServer.ts +18 -15
  31. package/src/lib/server/runtime/createAppRouteResolver.ts +145 -0
  32. package/src/lib/server/runtime/createPlumbingRouter.ts +212 -0
  33. package/src/lib/server/runtime/createRouteDispatcher.ts +4 -10
  34. package/src/lib/server/runtime/createServer.ts +120 -324
  35. package/src/lib/server/runtime/createUiPageRenderer.ts +137 -26
  36. package/src/lib/server/runtime/devClientFingerprint.ts +19 -34
  37. package/src/lib/server/runtime/installAmbientScopeStore.ts +30 -9
  38. package/src/lib/server/runtime/logExposedSurfaces.ts +8 -7
  39. package/src/lib/server/runtime/pathStore.ts +15 -0
  40. package/src/lib/server/runtime/registryManifests.ts +1 -1
  41. package/src/lib/server/runtime/renderCellBarrierStore.ts +15 -0
  42. package/src/lib/server/runtime/runWithRequestScope.ts +3 -0
  43. package/src/lib/server/runtime/serializeCacheSnapshot.ts +6 -4
  44. package/src/lib/server/runtime/snapshotEntryFromCache.ts +14 -14
  45. package/src/lib/server/runtime/streamCacheResolutions.ts +14 -7
  46. package/src/lib/server/runtime/streamFromIterator.ts +30 -2
  47. package/src/lib/server/runtime/textResponse.ts +23 -0
  48. package/src/lib/server/runtime/types/DevReloadStamp.ts +6 -10
  49. package/src/lib/server/runtime/types/InspectorCacheEntry.ts +1 -1
  50. package/src/lib/server/runtime/types/RequestStore.ts +27 -0
  51. package/src/lib/server/runtime/warnUnguardedMcp.ts +15 -5
  52. package/src/lib/server/sockets/createSocketDispatcher.ts +9 -11
  53. package/src/lib/server/sockets/defineSocket.ts +5 -4
  54. package/src/lib/server/sse.ts +5 -1
  55. package/src/lib/shared/ASYNC_CELL.ts +5 -0
  56. package/src/lib/shared/DEV_REBUILD_PATH.ts +6 -0
  57. package/src/lib/shared/HTTP_METHODS.ts +6 -0
  58. package/src/lib/shared/HttpError.ts +1 -5
  59. package/src/lib/shared/MCP_PATH.ts +6 -0
  60. package/src/lib/shared/PROXIED_SERVER_SUBDIRS.ts +15 -0
  61. package/src/lib/shared/REF_JSON_TAGS.ts +13 -1
  62. package/src/lib/shared/RPC_SHIM_GLOBALS.ts +9 -0
  63. package/src/lib/shared/activeCacheStore.ts +1 -0
  64. package/src/lib/shared/activePage.ts +1 -0
  65. package/src/lib/shared/buildArtifact.ts +4 -1
  66. package/src/lib/shared/buildSocketOverChannel.ts +4 -3
  67. package/src/lib/shared/bundleGraphFromMetafile.ts +68 -0
  68. package/src/lib/shared/cache.ts +156 -131
  69. package/src/lib/shared/changeAffectsClient.ts +12 -7
  70. package/src/lib/shared/createCacheStore.ts +31 -8
  71. package/src/lib/shared/createLifecycleChannel.ts +7 -1
  72. package/src/lib/shared/createReachable.ts +47 -78
  73. package/src/lib/shared/createRemoteFunction.ts +52 -30
  74. package/src/lib/shared/createRpcServerProgram.ts +820 -0
  75. package/src/lib/shared/decodeRefJson.ts +4 -4
  76. package/src/lib/shared/decodeResponse.ts +2 -2
  77. package/src/lib/shared/decodeWireBody.ts +35 -0
  78. package/src/lib/shared/detectRpcMethod.ts +8 -1
  79. package/src/lib/shared/done.ts +8 -2
  80. package/src/lib/shared/encodeRefJson.ts +4 -4
  81. package/src/lib/shared/encodeWireBody.ts +18 -0
  82. package/src/lib/{server/rpc → shared}/fieldErrorsFromIssues.ts +5 -1
  83. package/src/lib/shared/generateDeclarations.ts +72 -0
  84. package/src/lib/shared/hasSeedableRequest.ts +25 -0
  85. package/src/lib/shared/hydrationWindow.ts +53 -0
  86. package/src/lib/shared/isAsyncCell.ts +11 -0
  87. package/src/lib/shared/isAsyncIterable.ts +8 -0
  88. package/src/lib/shared/isSubscribable.ts +3 -3
  89. package/src/lib/shared/isThenable.ts +9 -0
  90. package/src/lib/shared/jsonSchemaForType.ts +258 -0
  91. package/src/lib/shared/lenientDecode.ts +15 -0
  92. package/src/lib/shared/loadProjectTsConfig.ts +28 -0
  93. package/src/lib/shared/markFrameworkSourcesIgnored.ts +1 -1
  94. package/src/lib/shared/matchRoute.ts +4 -14
  95. package/src/lib/shared/peek.ts +14 -0
  96. package/src/lib/shared/pending.ts +9 -6
  97. package/src/lib/shared/pendingAsyncCellsSlot.ts +13 -0
  98. package/src/lib/shared/prepareRpcModule.ts +91 -93
  99. package/src/lib/shared/prepareSocketModule.ts +3 -2
  100. package/src/lib/shared/probeRegistries.ts +5 -18
  101. package/src/lib/shared/reachable.ts +7 -10
  102. package/src/lib/shared/refresh.ts +12 -2
  103. package/src/lib/shared/refreshing.ts +8 -2
  104. package/src/lib/shared/resolvedCellsSlot.ts +13 -0
  105. package/src/lib/shared/reviveWireField.ts +49 -0
  106. package/src/lib/shared/reviveWireOutput.ts +36 -0
  107. package/src/lib/shared/rpcServerForRoot.ts +25 -0
  108. package/src/lib/shared/scanPages.ts +25 -0
  109. package/src/lib/shared/snapshotShippable.ts +8 -7
  110. package/src/lib/shared/streamedCellsSlot.ts +14 -0
  111. package/src/lib/shared/subscribableFromResponse.ts +4 -4
  112. package/src/lib/shared/subscribableProbes.ts +1 -1
  113. package/src/lib/shared/tailProbeSlot.ts +1 -1
  114. package/src/lib/shared/types/AsyncComputed.ts +20 -0
  115. package/src/lib/shared/types/AsyncState.ts +13 -0
  116. package/src/lib/shared/types/CacheEntry.ts +7 -7
  117. package/src/lib/shared/types/CacheOptions.ts +23 -37
  118. package/src/lib/shared/types/CachePolicy.ts +25 -0
  119. package/src/lib/shared/types/CacheSnapshotEntry.ts +6 -5
  120. package/src/lib/shared/types/ErrorJsonSchemas.ts +8 -0
  121. package/src/lib/shared/types/HttpMethod.ts +3 -1
  122. package/src/lib/shared/types/InputCoercion.ts +17 -0
  123. package/src/lib/shared/types/{Subscribable.ts → NamedAsyncIterable.ts} +1 -1
  124. package/src/lib/shared/types/OutputWirePlan.ts +17 -0
  125. package/src/lib/shared/types/PagesScan.ts +7 -0
  126. package/src/lib/shared/types/PendingAsyncCells.ts +10 -0
  127. package/src/lib/shared/types/RawRemoteFunction.ts +6 -0
  128. package/src/lib/shared/types/RemoteCallable.ts +8 -7
  129. package/src/lib/shared/types/RemoteFunction.ts +16 -15
  130. package/src/lib/shared/types/ResolvedCells.ts +11 -0
  131. package/src/lib/shared/types/ReturnBody.ts +15 -0
  132. package/src/lib/shared/types/RpcBuildStamps.ts +23 -0
  133. package/src/lib/shared/types/RpcErrorGuard.ts +3 -8
  134. package/src/lib/shared/types/Socket.ts +4 -4
  135. package/src/lib/shared/types/SsrPayload.ts +5 -1
  136. package/src/lib/shared/types/StreamPolicy.ts +11 -0
  137. package/src/lib/shared/types/StreamedCells.ts +19 -0
  138. package/src/lib/shared/types/StreamedResolution.ts +24 -6
  139. package/src/lib/shared/types/TailHooks.ts +1 -1
  140. package/src/lib/shared/types/WireKind.ts +11 -0
  141. package/src/lib/shared/validationHttpError.ts +33 -0
  142. package/src/lib/shared/warmSeedKey.ts +16 -0
  143. package/src/lib/shared/wireJsonReplacer.ts +30 -0
  144. package/src/lib/shared/writeRpcDts.ts +11 -1
  145. package/src/lib/shared/writeTestSocketsDts.ts +1 -1
  146. package/src/lib/test/createTestApp.ts +19 -1
  147. package/src/lib/ui/README.md +1 -1
  148. package/src/lib/ui/activePendingCells.ts +14 -0
  149. package/src/lib/ui/compile/BLOCK_KEYWORDS.ts +21 -0
  150. package/src/lib/ui/compile/SSR_ESCAPE.ts +4 -1
  151. package/src/lib/ui/compile/UI_RUNTIME_IMPORTS.ts +35 -8
  152. package/src/lib/ui/compile/abideUiPlugin.ts +19 -2
  153. package/src/lib/ui/compile/analyzeComponent.ts +89 -6
  154. package/src/lib/ui/compile/assignmentTargetNames.ts +54 -0
  155. package/src/lib/ui/compile/asyncInterpolationFields.ts +152 -0
  156. package/src/lib/ui/compile/asyncValuePositionError.ts +21 -0
  157. package/src/lib/ui/compile/asyncValuePositionInterpolations.ts +64 -0
  158. package/src/lib/ui/compile/attrLiftPosition.ts +18 -0
  159. package/src/lib/ui/compile/cachedSourceFile.ts +40 -0
  160. package/src/lib/ui/compile/catchBinding.ts +7 -5
  161. package/src/lib/ui/compile/classifyInterpolationType.ts +46 -0
  162. package/src/lib/ui/compile/collectAbideDiagnostics.ts +96 -0
  163. package/src/lib/ui/compile/compileComponent.ts +19 -3
  164. package/src/lib/ui/compile/compileModule.ts +27 -58
  165. package/src/lib/ui/compile/compileSSR.ts +51 -12
  166. package/src/lib/ui/compile/compileShadow.ts +145 -18
  167. package/src/lib/ui/compile/composeProps.ts +12 -2
  168. package/src/lib/ui/compile/createShadowLanguageService.ts +116 -68
  169. package/src/lib/ui/compile/createShadowProgram.ts +35 -6
  170. package/src/lib/ui/compile/declaredNames.ts +36 -0
  171. package/src/lib/ui/compile/desugarSignals.ts +418 -36
  172. package/src/lib/ui/compile/expressionIsPrefixEvaluable.ts +19 -0
  173. package/src/lib/ui/compile/generateBuild.ts +52 -9
  174. package/src/lib/ui/compile/generateSSR.ts +242 -43
  175. package/src/lib/ui/compile/hoistableAwaits.ts +193 -0
  176. package/src/lib/ui/compile/hoistableChildRenders.ts +112 -0
  177. package/src/lib/ui/compile/interpolatedTemplateLiteral.ts +3 -1
  178. package/src/lib/ui/compile/interpolationClassifierForRoot.ts +37 -0
  179. package/src/lib/ui/compile/isSpuriousAsyncReadDiagnostic.ts +174 -0
  180. package/src/lib/ui/compile/liftAsyncSubExpressions.ts +166 -0
  181. package/src/lib/ui/compile/lowerAsyncInterpolations.ts +92 -0
  182. package/src/lib/ui/compile/lowerContext.ts +10 -0
  183. package/src/lib/ui/compile/lowerDocAccess.ts +26 -23
  184. package/src/lib/ui/compile/lowerScript.ts +40 -4
  185. package/src/lib/ui/compile/nodeAtShadowOffset.ts +28 -0
  186. package/src/lib/ui/compile/parseTemplate.ts +18 -1092
  187. package/src/lib/ui/compile/parseTemplateRecovering.ts +1385 -0
  188. package/src/lib/ui/compile/referencedIdentifiers.ts +35 -0
  189. package/src/lib/ui/compile/renameSignalRefs.ts +21 -22
  190. package/src/lib/ui/compile/seedTypeClassifierForRoot.ts +65 -0
  191. package/src/lib/ui/compile/shadowInterpolationClassifier.ts +47 -0
  192. package/src/lib/ui/compile/sourceFileOptionsSignature.ts +20 -0
  193. package/src/lib/ui/compile/structuralHeadTokens.ts +107 -0
  194. package/src/lib/ui/compile/templateSemanticTokens.ts +21 -0
  195. package/src/lib/ui/compile/templateStartOffset.ts +15 -0
  196. package/src/lib/ui/compile/tryPlan.ts +4 -3
  197. package/src/lib/ui/compile/types/AnalyzedComponent.ts +4 -0
  198. package/src/lib/ui/compile/types/AsyncInterpolationField.ts +19 -0
  199. package/src/lib/ui/compile/types/InterpolationClassifier.ts +12 -0
  200. package/src/lib/ui/compile/types/InterpolationKind.ts +7 -0
  201. package/src/lib/ui/compile/types/ParseDiagnostic.ts +8 -0
  202. package/src/lib/ui/compile/types/SeedTypeClassifier.ts +15 -0
  203. package/src/lib/ui/compile/types/TemplateNode.ts +36 -4
  204. package/src/lib/ui/compile/types/ValuePositionInterpolation.ts +13 -0
  205. package/src/lib/ui/compile/writtenTemplateNames.ts +84 -0
  206. package/src/lib/ui/computed.ts +28 -1
  207. package/src/lib/ui/createScope.ts +35 -68
  208. package/src/lib/ui/dom/anchoredBranch.ts +142 -0
  209. package/src/lib/ui/dom/appendText.ts +8 -3
  210. package/src/lib/ui/dom/awaitBlock.ts +43 -94
  211. package/src/lib/ui/dom/bindProp.ts +22 -0
  212. package/src/lib/ui/dom/bindableProp.ts +47 -0
  213. package/src/lib/ui/dom/cellPending.ts +24 -0
  214. package/src/lib/ui/dom/disposeRange.ts +2 -1
  215. package/src/lib/ui/dom/each.ts +26 -4
  216. package/src/lib/ui/dom/eachAsync.ts +39 -3
  217. package/src/lib/ui/dom/fillBoundary.ts +2 -3
  218. package/src/lib/ui/dom/fillRange.ts +4 -4
  219. package/src/lib/ui/dom/hydrate.ts +6 -12
  220. package/src/lib/ui/dom/isComment.ts +1 -1
  221. package/src/lib/ui/dom/matchingRangeClose.ts +29 -0
  222. package/src/lib/ui/dom/mount.ts +1 -2
  223. package/src/lib/ui/dom/mountChild.ts +9 -40
  224. package/src/lib/ui/dom/mountRange.ts +2 -3
  225. package/src/lib/ui/dom/mountStreamedChild.ts +84 -0
  226. package/src/lib/ui/dom/mountSwappableRange.ts +46 -4
  227. package/src/lib/ui/dom/mutateDocContainer.ts +65 -0
  228. package/src/lib/ui/dom/on.ts +2 -2
  229. package/src/lib/ui/dom/readCell.ts +40 -0
  230. package/src/lib/ui/dom/switchBlock.ts +30 -7
  231. package/src/lib/ui/dom/tryBlock.ts +230 -66
  232. package/src/lib/ui/dom/types/SwitchCase.ts +5 -1
  233. package/src/lib/ui/dom/when.ts +12 -2
  234. package/src/lib/ui/dom/withScope.ts +2 -2
  235. package/src/lib/ui/finalizeStreamedChildren.ts +89 -0
  236. package/src/lib/ui/flight.ts +56 -0
  237. package/src/lib/ui/html.ts +12 -1
  238. package/src/lib/ui/isolateCellBarrier.ts +17 -0
  239. package/src/lib/ui/linked.ts +48 -2
  240. package/src/lib/ui/remoteProxy.ts +84 -159
  241. package/src/lib/ui/renderChain.ts +49 -13
  242. package/src/lib/ui/renderToStream.ts +22 -18
  243. package/src/lib/ui/resumeSeedScript.ts +1 -1
  244. package/src/lib/ui/router.ts +63 -36
  245. package/src/lib/ui/runtime/AsyncCellError.ts +20 -0
  246. package/src/lib/ui/runtime/CELL_SEED.ts +14 -0
  247. package/src/lib/ui/runtime/CURRENT_BOUNDARY.ts +11 -0
  248. package/src/lib/ui/runtime/CURRENT_PATH.ts +29 -0
  249. package/src/lib/ui/runtime/RENDER.ts +10 -7
  250. package/src/lib/ui/runtime/RESUME.ts +4 -4
  251. package/src/lib/ui/runtime/STREAMED_CELLS.ts +57 -0
  252. package/src/lib/ui/runtime/ambientPathBacking.ts +32 -0
  253. package/src/lib/ui/runtime/applyPatchToTree.ts +1 -1
  254. package/src/lib/ui/runtime/blockId.ts +31 -0
  255. package/src/lib/ui/runtime/boundaryFor.ts +11 -0
  256. package/src/lib/ui/runtime/cellBarrierBacking.ts +30 -0
  257. package/src/lib/ui/runtime/createAsyncCell.ts +300 -0
  258. package/src/lib/ui/runtime/createDoc.ts +3 -42
  259. package/src/lib/ui/runtime/createEffectNode.ts +9 -0
  260. package/src/lib/ui/runtime/enterRenderPass.ts +5 -4
  261. package/src/lib/ui/runtime/flushEffects.ts +16 -6
  262. package/src/lib/ui/runtime/isAsyncFunction.ts +14 -0
  263. package/src/lib/ui/runtime/nextBlockId.ts +8 -7
  264. package/src/lib/ui/runtime/renderPath.ts +16 -0
  265. package/src/lib/ui/runtime/types/Boundary.ts +9 -0
  266. package/src/lib/ui/runtime/types/RenderContext.ts +9 -7
  267. package/src/lib/ui/runtime/types/SsrRender.ts +9 -2
  268. package/src/lib/ui/runtime/types/UiComponent.ts +1 -5
  269. package/src/lib/ui/runtime/withOptionalPath.ts +8 -0
  270. package/src/lib/ui/runtime/withPath.ts +16 -0
  271. package/src/lib/ui/runtime/withPathFrom.ts +20 -0
  272. package/src/lib/ui/seedStreamedResolution.ts +31 -6
  273. package/src/lib/ui/settleAsyncCells.ts +24 -0
  274. package/src/lib/ui/socketProxy.ts +1 -1
  275. package/src/lib/ui/startClient.ts +16 -31
  276. package/src/lib/ui/trackedComputed.ts +68 -0
  277. package/src/lib/ui/types/Scope.ts +8 -24
  278. package/src/lib/ui/watch.ts +3 -3
  279. package/src/serverEntry.ts +14 -0
  280. package/template/src/server/rpc/getHello.ts +15 -13
  281. package/template/test/app.test.ts +1 -1
  282. package/src/lib/server/runtime/devHotModuleResponse.ts +0 -41
  283. package/src/lib/shared/DEV_HOT_PREFIX.ts +0 -7
  284. package/src/lib/shared/UNREACHABLE_STATUSES.ts +0 -13
  285. package/src/lib/shared/hasReplayableRequest.ts +0 -17
  286. package/src/lib/shared/hydratingSlot.ts +0 -12
  287. package/src/lib/shared/outboxProbeSlot.ts +0 -20
  288. package/src/lib/shared/types/Outbox.ts +0 -9
  289. package/src/lib/shared/types/OutboxEntry.ts +0 -27
  290. package/src/lib/shared/types/SmartReadOptions.ts +0 -36
  291. package/src/lib/shared/wakeHydrationPeeks.ts +0 -20
  292. package/src/lib/ui/COMPONENT_WRAPPER_PREFIX.ts +0 -5
  293. package/src/lib/ui/compile/REACTIVE_CALLEES.ts +0 -11
  294. package/src/lib/ui/compile/assertRuntimeHelpersBound.ts +0 -80
  295. package/src/lib/ui/compile/markupTokens.ts +0 -313
  296. package/src/lib/ui/compile/structuralBlockTokens.ts +0 -100
  297. package/src/lib/ui/dom/applyResolved.ts +0 -87
  298. package/src/lib/ui/dom/mutateDocArray.ts +0 -38
  299. package/src/lib/ui/dom/scopeLabel.ts +0 -21
  300. package/src/lib/ui/dom/text.ts +0 -20
  301. package/src/lib/ui/history.ts +0 -108
  302. package/src/lib/ui/installHotBridge.ts +0 -95
  303. package/src/lib/ui/installInspectorBridge.ts +0 -140
  304. package/src/lib/ui/outbox.ts +0 -35
  305. package/src/lib/ui/persist.ts +0 -115
  306. package/src/lib/ui/rpcOutbox/createOutboxQueue.ts +0 -281
  307. package/src/lib/ui/rpcOutbox/outboxRegistry.ts +0 -69
  308. package/src/lib/ui/runtime/PATCH_BUS.ts +0 -28
  309. package/src/lib/ui/runtime/captureModelDoc.ts +0 -43
  310. package/src/lib/ui/runtime/hotInstances.ts +0 -10
  311. package/src/lib/ui/runtime/hotReloadEnabled.ts +0 -8
  312. package/src/lib/ui/runtime/hotReplace.ts +0 -38
  313. package/src/lib/ui/runtime/liveScopes.ts +0 -15
  314. package/src/lib/ui/runtime/localStoragePersistence.ts +0 -47
  315. package/src/lib/ui/runtime/registerHotInstance.ts +0 -23
  316. package/src/lib/ui/runtime/seedModelDoc.ts +0 -26
  317. package/src/lib/ui/runtime/types/HotInstance.ts +0 -22
  318. package/src/lib/ui/runtime/types/PatchEvent.ts +0 -16
  319. package/src/lib/ui/seedResolved.ts +0 -28
  320. package/src/lib/ui/sync.ts +0 -48
  321. package/src/lib/ui/types/History.ts +0 -14
  322. package/src/lib/ui/types/PersistHandle.ts +0 -11
  323. package/src/lib/ui/types/PersistenceStore.ts +0 -12
  324. package/src/lib/ui/types/ResolvedFrame.ts +0 -15
  325. package/src/lib/ui/types/SyncTransport.ts +0 -13
@@ -0,0 +1,166 @@
1
+ import ts from 'typescript'
2
+ import { AbideCompileError } from './AbideCompileError.ts'
3
+ import type { InjectedCell } from './lowerAsyncInterpolations.ts'
4
+ import type { InterpolationClassifier } from './types/InterpolationClassifier.ts'
5
+ import type { InterpolationKind } from './types/InterpolationKind.ts'
6
+
7
+ /* The value/content position a lifted (sub)expression sits in — only `each` (a plain `{#for}`
8
+ source) forbids an `AsyncIterable` (D4a). Every other position renders a stream's latest frame. */
9
+ export type LiftPosition = 'content' | 'attribute' | 'if' | 'switch' | 'each'
10
+
11
+ /* A lifted (sub)expression's span within the interpolation `code` and how it lifts: `start`/`end`
12
+ index `code`, `kind` picks the peek flavour, `blocking` marks a leading `await`. The runtime uses
13
+ these to rewrite the span to a cell ref; the type-check shadow uses the non-blocking ones to wrap
14
+ the seed in a peek helper so its resolved type composes (ADR-0032). */
15
+ export type LiftSpan = {
16
+ start: number
17
+ end: number
18
+ kind: 'promise' | 'asyncIterable'
19
+ blocking: boolean
20
+ }
21
+
22
+ /* The binary operators whose result reacts to a pending-`undefined` operand: the walk does NOT
23
+ lift the composition, it descends into the operands so the operator composes the peek(s)
24
+ (ADR-0032 D1). `?.` needs no entry — a member/call on a promise is sync-typed, so the default
25
+ sync-recurse already reaches and lifts the async base. */
26
+ const PENDING_TOLERANT = new Set<ts.SyntaxKind>([
27
+ ts.SyntaxKind.QuestionQuestionToken,
28
+ ts.SyntaxKind.BarBarToken,
29
+ ts.SyntaxKind.AmpersandAmpersandToken,
30
+ ])
31
+
32
+ /*
33
+ ADR-0032 — the sub-expression walk. Given an interpolation's verbatim `code`, its absolute source
34
+ offset `loc`, and the warm classifier, lifts every promise/`AsyncIterable`-typed (sub)expression
35
+ into an injected peek-cell and rewrites it in place to a bare `__vN` reference, returning the
36
+ rewritten code and the cells to inject. Pending is `undefined` at the value level, so `??` / `?.` /
37
+ member access compose around the peek (`getFoo() ?? 'Loading…'` shows the fallback while pending).
38
+
39
+ Three rules, top-down (ADR D1 "The walk"):
40
+ 1. `await X` → lift `X` BLOCKING (SSR-inline); syntactic, needs no classifier.
41
+ 2. `a ?? b` / `||` / `&&`→ NOT lifted; recurse into the operands (pending stays visible to the op).
42
+ 3. any other promise/`AsyncIterable` node → lift as a unit, STREAMING; a sync node recurses
43
+ (a sync parent can wrap an async child: `String(getFoo())`, `` `${getFoo()}` ``).
44
+
45
+ `classify` absent (no warm program) ⇒ only rule 1 fires — fail-open: `await` still works, a bare
46
+ async position degrades to today's stringify. A promise seed is emitted `await X` so the cell
47
+ UNWRAPS to the resolved value; an `AsyncIterable` seed stays bare (its latest frame). D4: an
48
+ `AsyncIterable` in a plain `{#for}` source, or an `await` on an `AsyncIterable`, throw.
49
+ */
50
+ export function liftAsyncSubExpressions(
51
+ code: string,
52
+ loc: number,
53
+ classify: InterpolationClassifier | undefined,
54
+ mint: () => string,
55
+ position: LiftPosition = 'content',
56
+ ): { code: string; lifts: InjectedCell[]; spans: LiftSpan[] } {
57
+ const source = ts.createSourceFile('__lift.ts', code, ts.ScriptTarget.Latest, true)
58
+ const first = source.statements[0]
59
+ /* Only a single bare expression is walkable; anything else (a statement, empty) passes through. */
60
+ if (first === undefined || source.statements.length !== 1 || !ts.isExpressionStatement(first)) {
61
+ return { code, lifts: [], spans: [] }
62
+ }
63
+ const lifts: InjectedCell[] = []
64
+ /* Each lifted span, in walk (source) order; spliced right-to-left so earlier offsets stay valid.
65
+ `start`/`end` index `code`; `name` replaces that span. */
66
+ const edits: { start: number; end: number; name: string }[] = []
67
+ /* The same lifted spans with their kind/tier — the shadow's peek-wrap consumes these directly. */
68
+ const spans: LiftSpan[] = []
69
+
70
+ const text = (node: ts.Node): string => code.slice(node.getStart(source), node.getEnd())
71
+
72
+ /* The checker kind of a sub-node, keyed by its absolute source offset through the linear
73
+ interpolation→shadow mapping. `undefined` when no classifier (only `await` lifts then). */
74
+ const kindOf = (node: ts.Node): InterpolationKind | undefined => {
75
+ if (classify === undefined) {
76
+ return undefined
77
+ }
78
+ const start = node.getStart(source)
79
+ return classify(loc + start, code.slice(start, node.getEnd()))
80
+ }
81
+
82
+ /* Record a lift of `node`'s span. `seed` is the RAW (sub)expression text — `analyzeComponent`
83
+ wraps a `promise` seed as `computed(async () => await (<seed>))` (so `await` is a keyword in
84
+ an async context — a bare-text `await (X)` reparsed at module scope becomes a call to a
85
+ function `await`) and an `asyncIterable` seed as `computed(<seed>)` (a bare stream thunk). */
86
+ const lift = (
87
+ node: ts.Node,
88
+ seed: string,
89
+ kind: 'promise' | 'asyncIterable',
90
+ blocking: boolean,
91
+ ): void => {
92
+ const name = mint()
93
+ const start = node.getStart(source)
94
+ const end = node.getEnd()
95
+ lifts.push({ name, code: seed, kind, blocking })
96
+ edits.push({ start, end, name })
97
+ spans.push({ start, end, kind, blocking })
98
+ }
99
+
100
+ const visit = (node: ts.Node): void => {
101
+ /* A nested function is its own evaluation scope — an async (sub)expression inside a callback
102
+ (`items.map(x => fetchName(x))`, `items.map(async x => await load(x))`) must NOT be hoisted
103
+ to a top-level cell: its parameters/closure vars would become free identifiers, and it
104
+ would run once instead of per row. Stop at the boundary, mirroring
105
+ `desugarSignals.hasTopLevelAwait`. */
106
+ if (
107
+ ts.isArrowFunction(node) ||
108
+ ts.isFunctionExpression(node) ||
109
+ ts.isFunctionDeclaration(node)
110
+ ) {
111
+ return
112
+ }
113
+ if (ts.isAwaitExpression(node)) {
114
+ /* `await X` → a BLOCKING promise cell (its operand). D4b: `await` on a stream is
115
+ meaningless. The operand text is already a bounded unary — no parens needed. */
116
+ if (kindOf(node.expression) === 'asyncIterable') {
117
+ throw new AbideCompileError(
118
+ '[abide] `await` unwraps a promise, but this is an `AsyncIterable` — drop the `await`; a stream auto-tracks (render its frames, or `{#for await}`).',
119
+ loc + node.getStart(source),
120
+ )
121
+ }
122
+ lift(node, text(node.expression), 'promise', true)
123
+ return
124
+ }
125
+ if (ts.isBinaryExpression(node) && PENDING_TOLERANT.has(node.operatorToken.kind)) {
126
+ /* Pending-tolerant: don't lift the composition — descend so the operator sees the
127
+ operands' `undefined` (`getFoo() ?? 'Loading…'`, `x || getFoo()`). */
128
+ visit(node.left)
129
+ visit(node.right)
130
+ return
131
+ }
132
+ const kind = kindOf(node)
133
+ if (kind === 'promise') {
134
+ /* Lift as a unit, streaming; the cell unwraps the seed to its resolved value. */
135
+ lift(node, text(node), 'promise', false)
136
+ return
137
+ }
138
+ if (kind === 'asyncIterable') {
139
+ if (position === 'each') {
140
+ throw new AbideCompileError(
141
+ "[abide] an `AsyncIterable` can't drive a plain `{#for}` — iterate its frames with `{#for await}`.",
142
+ loc + node.getStart(source),
143
+ )
144
+ }
145
+ /* A stream cell: its latest frame. */
146
+ lift(node, text(node), 'asyncIterable', false)
147
+ return
148
+ }
149
+ /* Sync (or classifier-absent): a sync parent can still wrap an async child. */
150
+ ts.forEachChild(node, visit)
151
+ }
152
+
153
+ visit(first.expression)
154
+
155
+ if (edits.length === 0) {
156
+ return { code, lifts: [], spans: [] }
157
+ }
158
+ edits.sort((a, b) => b.start - a.start)
159
+ let rewritten = code
160
+ for (const edit of edits) {
161
+ rewritten = rewritten.slice(0, edit.start) + edit.name + rewritten.slice(edit.end)
162
+ }
163
+ /* Spans in ascending source order — the shadow's peek-wrap walks them left-to-right. */
164
+ spans.sort((a, b) => a.start - b.start)
165
+ return { code: rewritten, lifts, spans }
166
+ }
@@ -0,0 +1,92 @@
1
+ import { asyncInterpolationFields } from './asyncInterpolationFields.ts'
2
+ import { liftAsyncSubExpressions } from './liftAsyncSubExpressions.ts'
3
+ import type { InterpolationClassifier } from './types/InterpolationClassifier.ts'
4
+ import type { TemplateNode } from './types/TemplateNode.ts'
5
+
6
+ /* A synthetic peek-cell an async (sub)expression lowers to: a unique local name, the RAW seed
7
+ expression, its `kind` (a `promise` cell UNWRAPS to the resolved value; an `asyncIterable` cell
8
+ tracks its latest frame), and whether the author wrote `await` (BLOCKING: joins the SSR barrier,
9
+ resolved inline; else STREAMING: ships pending-`undefined`, resolves on the client). By kind,
10
+ `analyzeComponent` prepends `const <name> = computed(async () => await (<code>))` (promise) or
11
+ `const <name> = computed(<code>)` (stream); `desugarSignals` routes it (by injected name) to a
12
+ `trackedComputed` cell read via `$$readCell(<name>)`. */
13
+ export type InjectedCell = {
14
+ name: string
15
+ code: string
16
+ kind: 'promise' | 'asyncIterable'
17
+ blocking: boolean
18
+ }
19
+
20
+ /* The result of the async-interpolation lowering: the (mutated-in-place) template plus the cells
21
+ the lifted (sub)expressions were rewritten into (empty when none). */
22
+ export type LoweredInterpolations = { nodes: TemplateNode[]; cells: InjectedCell[] }
23
+
24
+ /*
25
+ ADR-0032 type-directed lowering of async (sub)expressions, in EVERY position — content
26
+ interpolations and value positions (attribute, `{#if}`/`{#switch}` subject, plain `{#for}` source)
27
+ alike. Each interpolation is walked (`liftAsyncSubExpressions`): a promise/`AsyncIterable`-typed
28
+ (sub)expression is lifted to an injected peek-cell and rewritten IN PLACE to a bare `__vN`
29
+ reference, so pending reads as `undefined` and composes with `??`/`?.`. A leading `await` marks the
30
+ SSR BLOCKING tier; no `await` is STREAMING. Runs even without a classifier — then only syntactic
31
+ `await`s lift (fail-open), matching the pre-classifier path for everything else.
32
+ */
33
+ export function lowerAsyncInterpolations(
34
+ nodes: TemplateNode[],
35
+ classify: InterpolationClassifier | undefined,
36
+ ): LoweredInterpolations {
37
+ const counter = { n: 0 }
38
+ const mint = (): string => `__v${counter.n++}`
39
+ const cells: InjectedCell[] = []
40
+ lowerList(nodes, classify, mint, cells)
41
+ return { nodes, cells }
42
+ }
43
+
44
+ /* Lifts every async (sub)expression in a sibling list, then recurses into children. Each node's
45
+ async-liftable fields come from the shared `asyncInterpolationFields` plan (the single reading the
46
+ shadow front-end also drives from), so the two can't disagree on which interpolations are async;
47
+ this side owns the RENDERING — lift the field to a peek-cell, write the `__vN` reference back, and
48
+ for a control-flow subject that collapsed to one bare cell mark `asyncSubject`. */
49
+ function lowerList(
50
+ nodes: TemplateNode[],
51
+ classify: InterpolationClassifier | undefined,
52
+ mint: () => string,
53
+ cells: InjectedCell[],
54
+ ): void {
55
+ for (const node of nodes) {
56
+ for (const field of asyncInterpolationFields(node)) {
57
+ const lifted = cells.length
58
+ const result = liftAsyncSubExpressions(
59
+ field.code,
60
+ field.loc,
61
+ classify,
62
+ mint,
63
+ field.position,
64
+ )
65
+ for (const lift of result.lifts) {
66
+ cells.push(lift)
67
+ }
68
+ field.write(result.code)
69
+ if (field.subject) {
70
+ field.setAsyncSubject(isBareLiftedCell(result.code, cells, lifted))
71
+ }
72
+ }
73
+ if ('children' in node) {
74
+ lowerList(node.children, classify, mint, cells)
75
+ }
76
+ }
77
+ }
78
+
79
+ /* True when a control-flow subject's WHOLE expression was lifted to one peek-cell — the
80
+ rewritten code is exactly a single injected name (`{#if getX()}` → `__v3`), not a compound
81
+ the peek composes into (`{#if getX()?.ok}` → `__v3?.ok`, still the falsy-else peek). `cells`
82
+ past index `lifted` are the ones this subject minted, so an equal name means the subject IS
83
+ that bare cell. Only then do the back-ends read its pending facet to hold the block. */
84
+ function isBareLiftedCell(code: string, cells: InjectedCell[], lifted: number): boolean {
85
+ const name = code.trim()
86
+ for (let index = lifted; index < cells.length; index++) {
87
+ if (cells[index]?.name === name) {
88
+ return true
89
+ }
90
+ }
91
+ return false
92
+ }
@@ -25,6 +25,8 @@ export function lowerContext(
25
25
  stateNames: ReadonlySet<string>,
26
26
  derivedNames: ReadonlySet<string>,
27
27
  computedNames: ReadonlySet<string> = new Set(),
28
+ /* `linked` / async `computed` names, read through `$$readCell(name)`. */
29
+ cellReadNames: ReadonlySet<string> = new Set(),
28
30
  ) {
29
31
  /* The typed branch-local shadow stack: one auto-popping value owning both kinds.
30
32
  `derived` names deref to `.value` like a `computed` (block value params the client
@@ -48,6 +50,7 @@ export function lowerContext(
48
50
  computedNames,
49
51
  new Set(scope.names('derived')),
50
52
  new Set(scope.names('plain')),
53
+ cellReadNames,
51
54
  ),
52
55
  docAccessTransformer('$$model'),
53
56
  ])
@@ -101,6 +104,13 @@ export function lowerContext(
101
104
  return `(${expression(code)}).set(${valueExpr});`
102
105
  }
103
106
  guardWritableBind(code)
107
+ /* A `linked` cell (a writable `State`) is read through `$$readCell`, but that call is
108
+ not an lvalue — the WRITE goes straight to the cell's `.value` setter (`NAME.value =
109
+ …`). The name is a real local holding the cell, so it needs no reference lowering. */
110
+ const name = code.trim()
111
+ if (cellReadNames.has(name)) {
112
+ return `${name}.value = ${valueExpr};`
113
+ }
104
114
  return statement(`${code} = ${valueExpr}`)
105
115
  }
106
116
 
@@ -1,6 +1,5 @@
1
1
  import ts from 'typescript'
2
2
  import { escapeKey } from '../runtime/escapeKey.ts'
3
- import { TS_PRINTER } from './TS_PRINTER.ts'
4
3
 
5
4
  /*
6
5
  The linchpin compiler pass. Rewrites idiomatic data access on a reactive document
@@ -23,15 +22,10 @@ many — literal keys at compile time, dynamic ones wrapped in a runtime
23
22
  `escapeKey(...)`. Reads are lowered to `read(path)`; a later pass hoists
24
23
  static-path reads to a `cell` bound once at component init (the string-free hot
25
24
  path the bench measured). Index expressions are themselves visited, so a read
26
- used as an index lowers too.
25
+ used as an index lowers too. Exposed as `docAccessTransformer` (a
26
+ `ts.TransformerFactory`) so the script pipeline chains it after
27
+ `signalRefsTransformer` over a single parsed tree (see `lowerScript`).
27
28
  */
28
- export function lowerDocAccess(code: string, docName: string): string {
29
- const source = ts.createSourceFile('component.ts', code, ts.ScriptTarget.Latest, true)
30
- const result = ts.transform(source, [docAccessTransformer(docName)])
31
- const output = TS_PRINTER.printFile(result.transformed[0] as ts.SourceFile)
32
- result.dispose()
33
- return output
34
- }
35
29
 
36
30
  /* A path segment is either a literal key or a runtime expression (a dynamic index). */
37
31
  type Segment = { kind: 'literal'; value: string } | { kind: 'expression'; node: ts.Expression }
@@ -39,12 +33,15 @@ type Segment = { kind: 'literal'; value: string } | { kind: 'expression'; node:
39
33
  /* Maps a compound-assignment operator to its plain binary counterpart. Logical
40
34
  assignments (`||=`/`&&=`/`??=`) lower to an unconditional replace of the
41
35
  combined value — consistent with how `+=` lowers (the patch always writes). */
42
- /* Array methods that mutate the receiver in place. Called on a doc-rooted array
43
- these can't lower to a bare `readCall` (which would mutate the live tree by
44
- reference and never emit a patch — no re-render, no undo/persistence/sync);
45
- they route through `$$mutateDocArray`, which clones-applies-replaces so a real
46
- patch fires. `push` is handled separately above (fine-grained `add` patches). */
47
- const MUTATING_ARRAY_METHODS = new Set([
36
+ /* Methods that mutate a doc-rooted container in place Array, Map and Set (the three
37
+ mutable containers the doc codec serializes). Called on a doc value these can't lower to
38
+ a bare `readCall` (which would mutate the live tree by reference and never emit a patch —
39
+ so nothing re-renders); they route through `$$mutateDocContainer`, which
40
+ clones-applies-replaces so a real patch fires. The names are disjoint across the three
41
+ container kinds, so one set routes them all and the runtime helper decides the kind by the
42
+ value's type. `push` is handled separately above (fine-grained `add` patches). */
43
+ const MUTATING_CONTAINER_METHODS = new Set([
44
+ // Array
48
45
  'pop',
49
46
  'shift',
50
47
  'unshift',
@@ -53,6 +50,11 @@ const MUTATING_ARRAY_METHODS = new Set([
53
50
  'reverse',
54
51
  'fill',
55
52
  'copyWithin',
53
+ // Map / Set (`delete` + `clear` shared; `set` is Map, `add` is Set)
54
+ 'set',
55
+ 'add',
56
+ 'delete',
57
+ 'clear',
56
58
  ])
57
59
 
58
60
  const COMPOUND_OPERATORS = new Map<ts.SyntaxKind, ts.BinaryOperator>([
@@ -167,6 +169,7 @@ export function docAccessTransformer(docName: string): ts.TransformerFactory<ts.
167
169
  )
168
170
  /* `node.arguments.length > 0` above guarantees `adds` is non-empty, so the
169
171
  single-element branch's `adds[0]` is defined. */
172
+ // biome-ignore lint/style/noNonNullAssertion: length === 1 guarantees adds[0] is defined
170
173
  return adds.length === 1 ? adds[0]! : ts.factory.createCommaListExpression(adds)
171
174
  }
172
175
  }
@@ -181,19 +184,19 @@ export function docAccessTransformer(docName: string): ts.TransformerFactory<ts.
181
184
  const args = node.arguments.map(
182
185
  (arg) => ts.visitNode(arg, visit) as ts.Expression,
183
186
  )
184
- /* An in-place-mutating array method on a doc path → route through
185
- `$$mutateDocArray(doc, path, member, [args])` so the mutation lands as a
186
- patch instead of silently mutating the live tree by reference. Optional
187
- chaining (`model.items?.splice(…)`) keeps the bare-call semantics below —
188
- skip-if-absent is the author's explicit choice, and a nullish array has
189
- nothing to mutate. */
187
+ /* An in-place-mutating container method (Array/Map/Set) on a doc path →
188
+ route through `$$mutateDocContainer(doc, path, member, [args])` so the
189
+ mutation lands as a patch instead of silently mutating the live tree by
190
+ reference. Optional chaining (`model.items?.splice(…)`) keeps the bare-call
191
+ semantics below — skip-if-absent is the author's explicit choice, and a
192
+ nullish container has nothing to mutate. */
190
193
  if (
191
- MUTATING_ARRAY_METHODS.has(access.name.text) &&
194
+ MUTATING_CONTAINER_METHODS.has(access.name.text) &&
192
195
  !access.questionDotToken &&
193
196
  !node.questionDotToken
194
197
  ) {
195
198
  return ts.factory.createCallExpression(
196
- ts.factory.createIdentifier('$$mutateDocArray'),
199
+ ts.factory.createIdentifier('$$mutateDocContainer'),
197
200
  undefined,
198
201
  [
199
202
  ts.factory.createIdentifier(docName),
@@ -7,12 +7,13 @@ import { docAccessTransformer } from './lowerDocAccess.ts'
7
7
  import { signalRefsTransformer } from './renameSignalRefs.ts'
8
8
  import { stripEffectsTransformer } from './stripEffects.ts'
9
9
  import { TS_PRINTER } from './TS_PRINTER.ts'
10
+ import type { SeedTypeClassifier } from './types/SeedTypeClassifier.ts'
10
11
 
11
12
  /* The `abide/ui/*` modules the reactive surface is imported from. An author's import of
12
13
  one is compiler-recognised and lowered, so its binding is often fully consumed — a plain
13
14
  `state(...)` becomes `$$model`/`$$scope` with no `state` reference left. Such a dead
14
- import is dropped from the emitted module (see `deadReactiveImport`) so the output has no
15
- spurious static `@abide/ui` dependency — load-bearing for hot modules, which forbid one. */
15
+ import is dropped from the emitted module (see `deadReactiveImport`) so the output carries
16
+ no spurious static `@abide/ui` dependency — a dead import a bundler would still resolve. */
16
17
  const REACTIVE_IMPORT_SPECIFIERS = new Set([
17
18
  `${ABIDE_PACKAGE_NAME}/ui/state`,
18
19
  `${ABIDE_PACKAGE_NAME}/ui/effect`,
@@ -61,6 +62,25 @@ export function lowerScript(
61
62
  desugared away. Folded into the dead-import usage check so a live import isn't dropped
62
63
  (→ `ReferenceError: state is not defined` in the branch). Empty for a nested script. */
63
64
  externalUsage = '',
65
+ /* Names of synthetic `const __cN = computed(...)` cells `analyzeComponent` prepended for
66
+ asyncIterable interpolations (ADR-0019 Stage D). `computed` here is an unimported callee,
67
+ so `desugarSignals` recognizes these declarations by name — routing each to an eager
68
+ `trackedComputed` stream cell — rather than by import resolution. Empty on every other path. */
69
+ injectedCellNames: ReadonlySet<string> = new Set(),
70
+ /* The subset of `injectedCellNames` whose author wrote `await` (ADR-0032): a BLOCKING peek-cell
71
+ that joins the SSR barrier (resolved inline), vs a streaming one that ships pending. Threaded
72
+ to `desugarSignals` so `injectedComputedStatements` passes the right `trackedComputed`
73
+ streaming flag. Empty on every other path. */
74
+ blockingCellNames: ReadonlySet<string> = new Set(),
75
+ /* Names the template writes/forwards (`writtenTemplateNames`), so a `props()` binding used
76
+ two-way is desugared to a writable cell rather than a read-only derive. Empty for a nested
77
+ script (which declares no props). */
78
+ templateWrittenNames: ReadonlySet<string> = new Set(),
79
+ /* Type-directed seed classifier + this script's absolute source base (ADR-0023), threaded
80
+ to `desugarSignals` so a no-marker `computed(seed)` routes on the seed's checker type.
81
+ Absent ⇒ fail-open to the `isBareCallComputed` syntax heuristic (today's behavior). */
82
+ seedClassify?: SeedTypeClassifier,
83
+ scriptBase = 0,
64
84
  ): {
65
85
  body: string
66
86
  imports: string
@@ -68,13 +88,28 @@ export function lowerScript(
68
88
  stateNames: Set<string>
69
89
  derivedNames: Set<string>
70
90
  computedNames: Set<string>
91
+ cellReadNames: Set<string>
71
92
  droppedReactiveImports: Set<string>
72
93
  } {
73
94
  const source = ts.createSourceFile('component.ts', scriptBody, ts.ScriptTarget.Latest, true)
74
- const { transformer, stateNames, derivedNames, computedNames } = desugarSignals(source)
95
+ const { transformer, stateNames, derivedNames, computedNames, cellReadNames } = desugarSignals(
96
+ source,
97
+ injectedCellNames,
98
+ blockingCellNames,
99
+ templateWrittenNames,
100
+ seedClassify,
101
+ scriptBase,
102
+ )
75
103
  const result = ts.transform(source, [
76
104
  transformer,
77
- signalRefsTransformer(stateNames, derivedNames, computedNames),
105
+ signalRefsTransformer(
106
+ stateNames,
107
+ derivedNames,
108
+ computedNames,
109
+ new Set(),
110
+ new Set(),
111
+ cellReadNames,
112
+ ),
78
113
  docAccessTransformer('$$model'),
79
114
  ])
80
115
  const transformed = result.transformed[0] as ts.SourceFile
@@ -126,6 +161,7 @@ export function lowerScript(
126
161
  stateNames,
127
162
  derivedNames,
128
163
  computedNames,
164
+ cellReadNames,
129
165
  droppedReactiveImports,
130
166
  }
131
167
  }
@@ -0,0 +1,28 @@
1
+ import type ts from 'typescript'
2
+
3
+ /*
4
+ Finds the shadow AST node for a template interpolation given its span in shadow
5
+ coordinates. An interpolation is emitted as a parenthesised expression, so the
6
+ node we want starts and ends exactly on the span; we descend only into nodes that
7
+ still cover the whole span and keep the deepest exact match (the innermost node
8
+ whose `getStart()/getEnd()` equal the span bounds).
9
+ */
10
+ export function nodeAtShadowOffset(
11
+ sourceFile: ts.SourceFile,
12
+ offset: number,
13
+ length: number,
14
+ ): ts.Node | undefined {
15
+ let best: ts.Node | undefined
16
+ const visit = (node: ts.Node): void => {
17
+ const start = node.getStart(sourceFile)
18
+ const end = node.getEnd()
19
+ if (start <= offset && end >= offset + length) {
20
+ if (start === offset && end === offset + length) {
21
+ best = node
22
+ }
23
+ node.forEachChild(visit)
24
+ }
25
+ }
26
+ visit(sourceFile)
27
+ return best
28
+ }