@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.
- package/AGENTS.md +564 -437
- package/CHANGELOG.md +183 -0
- package/README.md +165 -202
- package/package.json +14 -5
- package/src/abideLsp.ts +5 -6
- package/src/abideResolverPlugin.ts +197 -169
- package/src/build.ts +79 -56
- package/src/buildDisconnected.ts +0 -2
- package/src/checkAbide.ts +12 -2
- package/src/devEntry.ts +113 -28
- package/src/discoveryEntry.ts +3 -2
- package/src/lib/bundle/disconnected.abide +69 -73
- package/src/lib/bundle/infoPlist.ts +13 -7
- package/src/lib/bundle/spawnEmbeddedServer.ts +13 -4
- package/src/lib/bundle/waitForServer.ts +6 -1
- package/src/lib/mcp/mcpTools.ts +15 -6
- package/src/lib/server/error.ts +6 -6
- package/src/lib/server/json.ts +17 -3
- package/src/lib/server/rpc/defineRpc.ts +58 -24
- package/src/lib/server/rpc/parseArgs.ts +98 -13
- package/src/lib/server/rpc/resolveRpcJsonSchema.ts +28 -0
- package/src/lib/server/rpc/types/RpcHelper.ts +75 -105
- package/src/lib/server/rpc/types/RpcRegistryEntry.ts +24 -9
- package/src/lib/server/rpc/validationError.ts +1 -1
- package/src/lib/server/runtime/DEV_RELOAD_CLIENT_SCRIPT.ts +0 -15
- package/src/lib/server/runtime/buildCacheSnapshot.ts +10 -9
- package/src/lib/server/runtime/buildInspectorSurface.ts +6 -4
- package/src/lib/server/runtime/buildOpenApiSpec.ts +55 -11
- package/src/lib/server/runtime/buildPreloadManifest.ts +12 -10
- package/src/lib/server/runtime/createAppAssetServer.ts +18 -15
- package/src/lib/server/runtime/createAppRouteResolver.ts +145 -0
- package/src/lib/server/runtime/createPlumbingRouter.ts +212 -0
- package/src/lib/server/runtime/createRouteDispatcher.ts +4 -10
- package/src/lib/server/runtime/createServer.ts +120 -324
- package/src/lib/server/runtime/createUiPageRenderer.ts +137 -26
- package/src/lib/server/runtime/devClientFingerprint.ts +19 -34
- package/src/lib/server/runtime/installAmbientScopeStore.ts +30 -9
- package/src/lib/server/runtime/logExposedSurfaces.ts +8 -7
- package/src/lib/server/runtime/pathStore.ts +15 -0
- package/src/lib/server/runtime/registryManifests.ts +1 -1
- package/src/lib/server/runtime/renderCellBarrierStore.ts +15 -0
- package/src/lib/server/runtime/runWithRequestScope.ts +3 -0
- package/src/lib/server/runtime/serializeCacheSnapshot.ts +6 -4
- package/src/lib/server/runtime/snapshotEntryFromCache.ts +14 -14
- package/src/lib/server/runtime/streamCacheResolutions.ts +14 -7
- package/src/lib/server/runtime/streamFromIterator.ts +30 -2
- package/src/lib/server/runtime/textResponse.ts +23 -0
- package/src/lib/server/runtime/types/DevReloadStamp.ts +6 -10
- package/src/lib/server/runtime/types/InspectorCacheEntry.ts +1 -1
- package/src/lib/server/runtime/types/RequestStore.ts +27 -0
- package/src/lib/server/runtime/warnUnguardedMcp.ts +15 -5
- package/src/lib/server/sockets/createSocketDispatcher.ts +9 -11
- package/src/lib/server/sockets/defineSocket.ts +5 -4
- package/src/lib/server/sse.ts +5 -1
- package/src/lib/shared/ASYNC_CELL.ts +5 -0
- package/src/lib/shared/DEV_REBUILD_PATH.ts +6 -0
- package/src/lib/shared/HTTP_METHODS.ts +6 -0
- package/src/lib/shared/HttpError.ts +1 -5
- package/src/lib/shared/MCP_PATH.ts +6 -0
- package/src/lib/shared/PROXIED_SERVER_SUBDIRS.ts +15 -0
- package/src/lib/shared/REF_JSON_TAGS.ts +13 -1
- package/src/lib/shared/RPC_SHIM_GLOBALS.ts +9 -0
- package/src/lib/shared/activeCacheStore.ts +1 -0
- package/src/lib/shared/activePage.ts +1 -0
- package/src/lib/shared/buildArtifact.ts +4 -1
- package/src/lib/shared/buildSocketOverChannel.ts +4 -3
- package/src/lib/shared/bundleGraphFromMetafile.ts +68 -0
- package/src/lib/shared/cache.ts +156 -131
- package/src/lib/shared/changeAffectsClient.ts +12 -7
- package/src/lib/shared/createCacheStore.ts +31 -8
- package/src/lib/shared/createLifecycleChannel.ts +7 -1
- package/src/lib/shared/createReachable.ts +47 -78
- package/src/lib/shared/createRemoteFunction.ts +52 -30
- package/src/lib/shared/createRpcServerProgram.ts +820 -0
- package/src/lib/shared/decodeRefJson.ts +4 -4
- package/src/lib/shared/decodeResponse.ts +2 -2
- package/src/lib/shared/decodeWireBody.ts +35 -0
- package/src/lib/shared/detectRpcMethod.ts +8 -1
- package/src/lib/shared/done.ts +8 -2
- package/src/lib/shared/encodeRefJson.ts +4 -4
- package/src/lib/shared/encodeWireBody.ts +18 -0
- package/src/lib/{server/rpc → shared}/fieldErrorsFromIssues.ts +5 -1
- package/src/lib/shared/generateDeclarations.ts +72 -0
- package/src/lib/shared/hasSeedableRequest.ts +25 -0
- package/src/lib/shared/hydrationWindow.ts +53 -0
- package/src/lib/shared/isAsyncCell.ts +11 -0
- package/src/lib/shared/isAsyncIterable.ts +8 -0
- package/src/lib/shared/isSubscribable.ts +3 -3
- package/src/lib/shared/isThenable.ts +9 -0
- package/src/lib/shared/jsonSchemaForType.ts +258 -0
- package/src/lib/shared/lenientDecode.ts +15 -0
- package/src/lib/shared/loadProjectTsConfig.ts +28 -0
- package/src/lib/shared/markFrameworkSourcesIgnored.ts +1 -1
- package/src/lib/shared/matchRoute.ts +4 -14
- package/src/lib/shared/peek.ts +14 -0
- package/src/lib/shared/pending.ts +9 -6
- package/src/lib/shared/pendingAsyncCellsSlot.ts +13 -0
- package/src/lib/shared/prepareRpcModule.ts +91 -93
- package/src/lib/shared/prepareSocketModule.ts +3 -2
- package/src/lib/shared/probeRegistries.ts +5 -18
- package/src/lib/shared/reachable.ts +7 -10
- package/src/lib/shared/refresh.ts +12 -2
- package/src/lib/shared/refreshing.ts +8 -2
- package/src/lib/shared/resolvedCellsSlot.ts +13 -0
- package/src/lib/shared/reviveWireField.ts +49 -0
- package/src/lib/shared/reviveWireOutput.ts +36 -0
- package/src/lib/shared/rpcServerForRoot.ts +25 -0
- package/src/lib/shared/scanPages.ts +25 -0
- package/src/lib/shared/snapshotShippable.ts +8 -7
- package/src/lib/shared/streamedCellsSlot.ts +14 -0
- package/src/lib/shared/subscribableFromResponse.ts +4 -4
- package/src/lib/shared/subscribableProbes.ts +1 -1
- package/src/lib/shared/tailProbeSlot.ts +1 -1
- package/src/lib/shared/types/AsyncComputed.ts +20 -0
- package/src/lib/shared/types/AsyncState.ts +13 -0
- package/src/lib/shared/types/CacheEntry.ts +7 -7
- package/src/lib/shared/types/CacheOptions.ts +23 -37
- package/src/lib/shared/types/CachePolicy.ts +25 -0
- package/src/lib/shared/types/CacheSnapshotEntry.ts +6 -5
- package/src/lib/shared/types/ErrorJsonSchemas.ts +8 -0
- package/src/lib/shared/types/HttpMethod.ts +3 -1
- package/src/lib/shared/types/InputCoercion.ts +17 -0
- package/src/lib/shared/types/{Subscribable.ts → NamedAsyncIterable.ts} +1 -1
- package/src/lib/shared/types/OutputWirePlan.ts +17 -0
- package/src/lib/shared/types/PagesScan.ts +7 -0
- package/src/lib/shared/types/PendingAsyncCells.ts +10 -0
- package/src/lib/shared/types/RawRemoteFunction.ts +6 -0
- package/src/lib/shared/types/RemoteCallable.ts +8 -7
- package/src/lib/shared/types/RemoteFunction.ts +16 -15
- package/src/lib/shared/types/ResolvedCells.ts +11 -0
- package/src/lib/shared/types/ReturnBody.ts +15 -0
- package/src/lib/shared/types/RpcBuildStamps.ts +23 -0
- package/src/lib/shared/types/RpcErrorGuard.ts +3 -8
- package/src/lib/shared/types/Socket.ts +4 -4
- package/src/lib/shared/types/SsrPayload.ts +5 -1
- package/src/lib/shared/types/StreamPolicy.ts +11 -0
- package/src/lib/shared/types/StreamedCells.ts +19 -0
- package/src/lib/shared/types/StreamedResolution.ts +24 -6
- package/src/lib/shared/types/TailHooks.ts +1 -1
- package/src/lib/shared/types/WireKind.ts +11 -0
- package/src/lib/shared/validationHttpError.ts +33 -0
- package/src/lib/shared/warmSeedKey.ts +16 -0
- package/src/lib/shared/wireJsonReplacer.ts +30 -0
- package/src/lib/shared/writeRpcDts.ts +11 -1
- package/src/lib/shared/writeTestSocketsDts.ts +1 -1
- package/src/lib/test/createTestApp.ts +19 -1
- package/src/lib/ui/README.md +1 -1
- package/src/lib/ui/activePendingCells.ts +14 -0
- package/src/lib/ui/compile/BLOCK_KEYWORDS.ts +21 -0
- package/src/lib/ui/compile/SSR_ESCAPE.ts +4 -1
- package/src/lib/ui/compile/UI_RUNTIME_IMPORTS.ts +35 -8
- package/src/lib/ui/compile/abideUiPlugin.ts +19 -2
- package/src/lib/ui/compile/analyzeComponent.ts +89 -6
- package/src/lib/ui/compile/assignmentTargetNames.ts +54 -0
- package/src/lib/ui/compile/asyncInterpolationFields.ts +152 -0
- package/src/lib/ui/compile/asyncValuePositionError.ts +21 -0
- package/src/lib/ui/compile/asyncValuePositionInterpolations.ts +64 -0
- package/src/lib/ui/compile/attrLiftPosition.ts +18 -0
- package/src/lib/ui/compile/cachedSourceFile.ts +40 -0
- package/src/lib/ui/compile/catchBinding.ts +7 -5
- package/src/lib/ui/compile/classifyInterpolationType.ts +46 -0
- package/src/lib/ui/compile/collectAbideDiagnostics.ts +96 -0
- package/src/lib/ui/compile/compileComponent.ts +19 -3
- package/src/lib/ui/compile/compileModule.ts +27 -58
- package/src/lib/ui/compile/compileSSR.ts +51 -12
- package/src/lib/ui/compile/compileShadow.ts +145 -18
- package/src/lib/ui/compile/composeProps.ts +12 -2
- package/src/lib/ui/compile/createShadowLanguageService.ts +116 -68
- package/src/lib/ui/compile/createShadowProgram.ts +35 -6
- package/src/lib/ui/compile/declaredNames.ts +36 -0
- package/src/lib/ui/compile/desugarSignals.ts +418 -36
- package/src/lib/ui/compile/expressionIsPrefixEvaluable.ts +19 -0
- package/src/lib/ui/compile/generateBuild.ts +52 -9
- package/src/lib/ui/compile/generateSSR.ts +242 -43
- package/src/lib/ui/compile/hoistableAwaits.ts +193 -0
- package/src/lib/ui/compile/hoistableChildRenders.ts +112 -0
- package/src/lib/ui/compile/interpolatedTemplateLiteral.ts +3 -1
- package/src/lib/ui/compile/interpolationClassifierForRoot.ts +37 -0
- package/src/lib/ui/compile/isSpuriousAsyncReadDiagnostic.ts +174 -0
- package/src/lib/ui/compile/liftAsyncSubExpressions.ts +166 -0
- package/src/lib/ui/compile/lowerAsyncInterpolations.ts +92 -0
- package/src/lib/ui/compile/lowerContext.ts +10 -0
- package/src/lib/ui/compile/lowerDocAccess.ts +26 -23
- package/src/lib/ui/compile/lowerScript.ts +40 -4
- package/src/lib/ui/compile/nodeAtShadowOffset.ts +28 -0
- package/src/lib/ui/compile/parseTemplate.ts +18 -1092
- package/src/lib/ui/compile/parseTemplateRecovering.ts +1385 -0
- package/src/lib/ui/compile/referencedIdentifiers.ts +35 -0
- package/src/lib/ui/compile/renameSignalRefs.ts +21 -22
- package/src/lib/ui/compile/seedTypeClassifierForRoot.ts +65 -0
- package/src/lib/ui/compile/shadowInterpolationClassifier.ts +47 -0
- package/src/lib/ui/compile/sourceFileOptionsSignature.ts +20 -0
- package/src/lib/ui/compile/structuralHeadTokens.ts +107 -0
- package/src/lib/ui/compile/templateSemanticTokens.ts +21 -0
- package/src/lib/ui/compile/templateStartOffset.ts +15 -0
- package/src/lib/ui/compile/tryPlan.ts +4 -3
- package/src/lib/ui/compile/types/AnalyzedComponent.ts +4 -0
- package/src/lib/ui/compile/types/AsyncInterpolationField.ts +19 -0
- package/src/lib/ui/compile/types/InterpolationClassifier.ts +12 -0
- package/src/lib/ui/compile/types/InterpolationKind.ts +7 -0
- package/src/lib/ui/compile/types/ParseDiagnostic.ts +8 -0
- package/src/lib/ui/compile/types/SeedTypeClassifier.ts +15 -0
- package/src/lib/ui/compile/types/TemplateNode.ts +36 -4
- package/src/lib/ui/compile/types/ValuePositionInterpolation.ts +13 -0
- package/src/lib/ui/compile/writtenTemplateNames.ts +84 -0
- package/src/lib/ui/computed.ts +28 -1
- package/src/lib/ui/createScope.ts +35 -68
- package/src/lib/ui/dom/anchoredBranch.ts +142 -0
- package/src/lib/ui/dom/appendText.ts +8 -3
- package/src/lib/ui/dom/awaitBlock.ts +43 -94
- package/src/lib/ui/dom/bindProp.ts +22 -0
- package/src/lib/ui/dom/bindableProp.ts +47 -0
- package/src/lib/ui/dom/cellPending.ts +24 -0
- package/src/lib/ui/dom/disposeRange.ts +2 -1
- package/src/lib/ui/dom/each.ts +26 -4
- package/src/lib/ui/dom/eachAsync.ts +39 -3
- package/src/lib/ui/dom/fillBoundary.ts +2 -3
- package/src/lib/ui/dom/fillRange.ts +4 -4
- package/src/lib/ui/dom/hydrate.ts +6 -12
- package/src/lib/ui/dom/isComment.ts +1 -1
- package/src/lib/ui/dom/matchingRangeClose.ts +29 -0
- package/src/lib/ui/dom/mount.ts +1 -2
- package/src/lib/ui/dom/mountChild.ts +9 -40
- package/src/lib/ui/dom/mountRange.ts +2 -3
- package/src/lib/ui/dom/mountStreamedChild.ts +84 -0
- package/src/lib/ui/dom/mountSwappableRange.ts +46 -4
- package/src/lib/ui/dom/mutateDocContainer.ts +65 -0
- package/src/lib/ui/dom/on.ts +2 -2
- package/src/lib/ui/dom/readCell.ts +40 -0
- package/src/lib/ui/dom/switchBlock.ts +30 -7
- package/src/lib/ui/dom/tryBlock.ts +230 -66
- package/src/lib/ui/dom/types/SwitchCase.ts +5 -1
- package/src/lib/ui/dom/when.ts +12 -2
- package/src/lib/ui/dom/withScope.ts +2 -2
- package/src/lib/ui/finalizeStreamedChildren.ts +89 -0
- package/src/lib/ui/flight.ts +56 -0
- package/src/lib/ui/html.ts +12 -1
- package/src/lib/ui/isolateCellBarrier.ts +17 -0
- package/src/lib/ui/linked.ts +48 -2
- package/src/lib/ui/remoteProxy.ts +84 -159
- package/src/lib/ui/renderChain.ts +49 -13
- package/src/lib/ui/renderToStream.ts +22 -18
- package/src/lib/ui/resumeSeedScript.ts +1 -1
- package/src/lib/ui/router.ts +63 -36
- package/src/lib/ui/runtime/AsyncCellError.ts +20 -0
- package/src/lib/ui/runtime/CELL_SEED.ts +14 -0
- package/src/lib/ui/runtime/CURRENT_BOUNDARY.ts +11 -0
- package/src/lib/ui/runtime/CURRENT_PATH.ts +29 -0
- package/src/lib/ui/runtime/RENDER.ts +10 -7
- package/src/lib/ui/runtime/RESUME.ts +4 -4
- package/src/lib/ui/runtime/STREAMED_CELLS.ts +57 -0
- package/src/lib/ui/runtime/ambientPathBacking.ts +32 -0
- package/src/lib/ui/runtime/applyPatchToTree.ts +1 -1
- package/src/lib/ui/runtime/blockId.ts +31 -0
- package/src/lib/ui/runtime/boundaryFor.ts +11 -0
- package/src/lib/ui/runtime/cellBarrierBacking.ts +30 -0
- package/src/lib/ui/runtime/createAsyncCell.ts +300 -0
- package/src/lib/ui/runtime/createDoc.ts +3 -42
- package/src/lib/ui/runtime/createEffectNode.ts +9 -0
- package/src/lib/ui/runtime/enterRenderPass.ts +5 -4
- package/src/lib/ui/runtime/flushEffects.ts +16 -6
- package/src/lib/ui/runtime/isAsyncFunction.ts +14 -0
- package/src/lib/ui/runtime/nextBlockId.ts +8 -7
- package/src/lib/ui/runtime/renderPath.ts +16 -0
- package/src/lib/ui/runtime/types/Boundary.ts +9 -0
- package/src/lib/ui/runtime/types/RenderContext.ts +9 -7
- package/src/lib/ui/runtime/types/SsrRender.ts +9 -2
- package/src/lib/ui/runtime/types/UiComponent.ts +1 -5
- package/src/lib/ui/runtime/withOptionalPath.ts +8 -0
- package/src/lib/ui/runtime/withPath.ts +16 -0
- package/src/lib/ui/runtime/withPathFrom.ts +20 -0
- package/src/lib/ui/seedStreamedResolution.ts +31 -6
- package/src/lib/ui/settleAsyncCells.ts +24 -0
- package/src/lib/ui/socketProxy.ts +1 -1
- package/src/lib/ui/startClient.ts +16 -31
- package/src/lib/ui/trackedComputed.ts +68 -0
- package/src/lib/ui/types/Scope.ts +8 -24
- package/src/lib/ui/watch.ts +3 -3
- package/src/serverEntry.ts +14 -0
- package/template/src/server/rpc/getHello.ts +15 -13
- package/template/test/app.test.ts +1 -1
- package/src/lib/server/runtime/devHotModuleResponse.ts +0 -41
- package/src/lib/shared/DEV_HOT_PREFIX.ts +0 -7
- package/src/lib/shared/UNREACHABLE_STATUSES.ts +0 -13
- package/src/lib/shared/hasReplayableRequest.ts +0 -17
- package/src/lib/shared/hydratingSlot.ts +0 -12
- package/src/lib/shared/outboxProbeSlot.ts +0 -20
- package/src/lib/shared/types/Outbox.ts +0 -9
- package/src/lib/shared/types/OutboxEntry.ts +0 -27
- package/src/lib/shared/types/SmartReadOptions.ts +0 -36
- package/src/lib/shared/wakeHydrationPeeks.ts +0 -20
- package/src/lib/ui/COMPONENT_WRAPPER_PREFIX.ts +0 -5
- package/src/lib/ui/compile/REACTIVE_CALLEES.ts +0 -11
- package/src/lib/ui/compile/assertRuntimeHelpersBound.ts +0 -80
- package/src/lib/ui/compile/markupTokens.ts +0 -313
- package/src/lib/ui/compile/structuralBlockTokens.ts +0 -100
- package/src/lib/ui/dom/applyResolved.ts +0 -87
- package/src/lib/ui/dom/mutateDocArray.ts +0 -38
- package/src/lib/ui/dom/scopeLabel.ts +0 -21
- package/src/lib/ui/dom/text.ts +0 -20
- package/src/lib/ui/history.ts +0 -108
- package/src/lib/ui/installHotBridge.ts +0 -95
- package/src/lib/ui/installInspectorBridge.ts +0 -140
- package/src/lib/ui/outbox.ts +0 -35
- package/src/lib/ui/persist.ts +0 -115
- package/src/lib/ui/rpcOutbox/createOutboxQueue.ts +0 -281
- package/src/lib/ui/rpcOutbox/outboxRegistry.ts +0 -69
- package/src/lib/ui/runtime/PATCH_BUS.ts +0 -28
- package/src/lib/ui/runtime/captureModelDoc.ts +0 -43
- package/src/lib/ui/runtime/hotInstances.ts +0 -10
- package/src/lib/ui/runtime/hotReloadEnabled.ts +0 -8
- package/src/lib/ui/runtime/hotReplace.ts +0 -38
- package/src/lib/ui/runtime/liveScopes.ts +0 -15
- package/src/lib/ui/runtime/localStoragePersistence.ts +0 -47
- package/src/lib/ui/runtime/registerHotInstance.ts +0 -23
- package/src/lib/ui/runtime/seedModelDoc.ts +0 -26
- package/src/lib/ui/runtime/types/HotInstance.ts +0 -22
- package/src/lib/ui/runtime/types/PatchEvent.ts +0 -16
- package/src/lib/ui/seedResolved.ts +0 -28
- package/src/lib/ui/sync.ts +0 -48
- package/src/lib/ui/types/History.ts +0 -14
- package/src/lib/ui/types/PersistHandle.ts +0 -11
- package/src/lib/ui/types/PersistenceStore.ts +0 -12
- package/src/lib/ui/types/ResolvedFrame.ts +0 -15
- package/src/lib/ui/types/SyncTransport.ts +0 -13
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import ts from 'typescript'
|
|
2
|
+
import { declaredNames } from './declaredNames.ts'
|
|
3
|
+
import { expressionIsPrefixEvaluable } from './expressionIsPrefixEvaluable.ts'
|
|
4
|
+
import type { TemplateNode } from './types/TemplateNode.ts'
|
|
5
|
+
|
|
6
|
+
/* One hoistable await: the node whose promise-start moves to the SSR prefix, and the
|
|
7
|
+
`$flightN` const name that holds the in-flight promise. */
|
|
8
|
+
export type HoistedFlight = {
|
|
9
|
+
node: Extract<TemplateNode, { kind: 'await' }>
|
|
10
|
+
name: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
ADR-0034 — the SSR-only pass that picks which `await` blocks may start their promise in the
|
|
15
|
+
synchronous render prefix so independent flights overlap instead of serializing. An await is
|
|
16
|
+
HOISTABLE iff its promise is evaluable at prefix time and its content is unconditionally rendered:
|
|
17
|
+
|
|
18
|
+
1. Its promise's referenced identifiers avoid every enclosing TEMPLATE-LOCAL binder (a `{#for}`
|
|
19
|
+
item/index, a `{:then}`/`{:catch}`/`then v` value, a snippet param, a nested `<script>`
|
|
20
|
+
local) — those don't exist in the prefix — AND every ASYNC-CELL name (`cellReadNames`, still
|
|
21
|
+
pending at prefix time).
|
|
22
|
+
2. It is not inside a CONDITIONALLY-rendered branch (`{#if}`/`{#switch}`/`{#try}`, a snippet/slot
|
|
23
|
+
body, or another await's pending/then/catch branch) — starting its flight eagerly would fetch
|
|
24
|
+
for a branch that may never render, or surface an error prematurely.
|
|
25
|
+
3. It is STATICALLY-SINGLE: on the top-level spine, or inside a single-element-literal
|
|
26
|
+
`{#for k of [expr] by k}` whose body renders exactly once (so one hoisted flight == one row).
|
|
27
|
+
|
|
28
|
+
Fail-closed: identifiers are over-collected and any doubt blocks the hoist, so a mis-classified
|
|
29
|
+
row-local flight can never collapse a multi-row `{#for}` to one shared value. Server-only — the
|
|
30
|
+
returned nodes are rewired only in `generateSSR`; the client build and the wire are untouched.
|
|
31
|
+
*/
|
|
32
|
+
export function hoistableAwaits(
|
|
33
|
+
nodes: TemplateNode[],
|
|
34
|
+
cellReadNames: ReadonlySet<string>,
|
|
35
|
+
): HoistedFlight[] {
|
|
36
|
+
const flights: HoistedFlight[] = []
|
|
37
|
+
const counter = { next: 0 }
|
|
38
|
+
walkList(nodes, new Set<string>(), false, false, cellReadNames, flights, counter)
|
|
39
|
+
return flights
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Walk a sibling list left-to-right so a nested `<script>`'s declared locals extend the binder
|
|
43
|
+
set for the siblings that follow it (they are in that script's lexical scope). */
|
|
44
|
+
function walkList(
|
|
45
|
+
list: TemplateNode[],
|
|
46
|
+
binders: ReadonlySet<string>,
|
|
47
|
+
conditional: boolean,
|
|
48
|
+
multiRow: boolean,
|
|
49
|
+
cellReadNames: ReadonlySet<string>,
|
|
50
|
+
flights: HoistedFlight[],
|
|
51
|
+
counter: { next: number },
|
|
52
|
+
): void {
|
|
53
|
+
/* A running binder set for this list: a `<script>` sibling adds its top-level declarations,
|
|
54
|
+
so a later sibling's await that reads one is (conservatively) not hoistable. */
|
|
55
|
+
let listBinders = binders
|
|
56
|
+
for (const node of list) {
|
|
57
|
+
if (node.kind === 'script') {
|
|
58
|
+
listBinders = union(listBinders, declaredNames(node.code))
|
|
59
|
+
continue
|
|
60
|
+
}
|
|
61
|
+
walkNode(node, listBinders, conditional, multiRow, cellReadNames, flights, counter)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function walkNode(
|
|
66
|
+
node: TemplateNode,
|
|
67
|
+
binders: ReadonlySet<string>,
|
|
68
|
+
conditional: boolean,
|
|
69
|
+
multiRow: boolean,
|
|
70
|
+
cellReadNames: ReadonlySet<string>,
|
|
71
|
+
flights: HoistedFlight[],
|
|
72
|
+
counter: { next: number },
|
|
73
|
+
): void {
|
|
74
|
+
if (node.kind === 'await') {
|
|
75
|
+
/* Hoistability is judged in the INCOMING context — the block's promise starts when the
|
|
76
|
+
block mounts (its pending branch always renders), so it is not gated by its own
|
|
77
|
+
branches, only by an enclosing conditional/multi-row. */
|
|
78
|
+
if (
|
|
79
|
+
!conditional &&
|
|
80
|
+
!multiRow &&
|
|
81
|
+
promiseIsPrefixEvaluable(node.promise, binders, cellReadNames)
|
|
82
|
+
) {
|
|
83
|
+
flights.push({ node, name: `$flight${counter.next++}` })
|
|
84
|
+
}
|
|
85
|
+
/* The resolved/catch/pending branches are conditionally shown and bind the resolved value
|
|
86
|
+
(`node.as` for `{#await p then v}`; a `{:then v}`/`{:catch e}` branch binds via its own
|
|
87
|
+
`as`, added when that branch node is walked). */
|
|
88
|
+
const inner = node.as === undefined ? binders : union(binders, new Set([node.as]))
|
|
89
|
+
walkList(node.children, inner, true, multiRow, cellReadNames, flights, counter)
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
if (node.kind === 'each') {
|
|
93
|
+
/* Row/index bindings are template-local; the body is multi-row unless the source is a
|
|
94
|
+
single-element array literal (then it renders exactly once). `{#for await}` drains a
|
|
95
|
+
stream — its body is still per-frame, so treat it as multi-row. */
|
|
96
|
+
const rowBinders = new Set<string>()
|
|
97
|
+
rowBinders.add(node.as)
|
|
98
|
+
if (node.index !== undefined) {
|
|
99
|
+
rowBinders.add(node.index)
|
|
100
|
+
}
|
|
101
|
+
const rowMulti = multiRow || node.async === true || !isSingleElementLiteral(node.items)
|
|
102
|
+
walkList(
|
|
103
|
+
node.children,
|
|
104
|
+
union(binders, rowBinders),
|
|
105
|
+
conditional,
|
|
106
|
+
rowMulti,
|
|
107
|
+
cellReadNames,
|
|
108
|
+
flights,
|
|
109
|
+
counter,
|
|
110
|
+
)
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
if (
|
|
114
|
+
node.kind === 'if' ||
|
|
115
|
+
node.kind === 'switch' ||
|
|
116
|
+
node.kind === 'case' ||
|
|
117
|
+
node.kind === 'try'
|
|
118
|
+
) {
|
|
119
|
+
/* Conditionally-rendered subtrees — never hoist a flight out of them. */
|
|
120
|
+
walkList(node.children, binders, true, multiRow, cellReadNames, flights, counter)
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
if (node.kind === 'branch') {
|
|
124
|
+
const inner = node.as === undefined ? binders : union(binders, new Set([node.as]))
|
|
125
|
+
walkList(node.children, inner, true, multiRow, cellReadNames, flights, counter)
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
if (node.kind === 'snippet') {
|
|
129
|
+
/* A snippet is a builder called conditionally; its params are locals. */
|
|
130
|
+
const params =
|
|
131
|
+
node.params === undefined
|
|
132
|
+
? new Set<string>()
|
|
133
|
+
: declaredNames(`const [${node.params}] = []`)
|
|
134
|
+
walkList(
|
|
135
|
+
node.children,
|
|
136
|
+
union(binders, params),
|
|
137
|
+
true,
|
|
138
|
+
multiRow,
|
|
139
|
+
cellReadNames,
|
|
140
|
+
flights,
|
|
141
|
+
counter,
|
|
142
|
+
)
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
if (node.kind === 'component') {
|
|
146
|
+
/* Slot content is a fresh, lazily-built scope rendered at the child's `<slot>`. */
|
|
147
|
+
walkList(node.children, binders, true, multiRow, cellReadNames, flights, counter)
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
if (node.kind === 'element' && 'children' in node) {
|
|
151
|
+
walkList(node.children, binders, conditional, multiRow, cellReadNames, flights, counter)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/* True when EVERY referenced identifier of `promise` is prefix-evaluable: none is a template-local
|
|
156
|
+
binder and none is an async-cell name. Delegates to the shared `expressionIsPrefixEvaluable`. */
|
|
157
|
+
function promiseIsPrefixEvaluable(
|
|
158
|
+
promise: string,
|
|
159
|
+
binders: ReadonlySet<string>,
|
|
160
|
+
cellReadNames: ReadonlySet<string>,
|
|
161
|
+
): boolean {
|
|
162
|
+
return expressionIsPrefixEvaluable(promise, binders, cellReadNames)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/* A single-element array literal like `[remountKey]` — the only `{#for}` source that renders its
|
|
166
|
+
body exactly once, so a hoisted flight inside it maps to one row (no shared-flight hazard). */
|
|
167
|
+
function isSingleElementLiteral(items: string): boolean {
|
|
168
|
+
let source: ts.SourceFile
|
|
169
|
+
try {
|
|
170
|
+
source = ts.createSourceFile('items.ts', items, ts.ScriptTarget.Latest, true)
|
|
171
|
+
} catch {
|
|
172
|
+
return false
|
|
173
|
+
}
|
|
174
|
+
const statement = source.statements[0]
|
|
175
|
+
if (
|
|
176
|
+
statement === undefined ||
|
|
177
|
+
source.statements.length !== 1 ||
|
|
178
|
+
!ts.isExpressionStatement(statement) ||
|
|
179
|
+
!ts.isArrayLiteralExpression(statement.expression)
|
|
180
|
+
) {
|
|
181
|
+
return false
|
|
182
|
+
}
|
|
183
|
+
const elements = statement.expression.elements
|
|
184
|
+
return elements.length === 1 && !ts.isSpreadElement(elements[0] as ts.Expression)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function union(a: ReadonlySet<string>, b: ReadonlySet<string>): Set<string> {
|
|
188
|
+
const result = new Set(a)
|
|
189
|
+
for (const value of b) {
|
|
190
|
+
result.add(value)
|
|
191
|
+
}
|
|
192
|
+
return result
|
|
193
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { declaredNames } from './declaredNames.ts'
|
|
2
|
+
import { expressionIsPrefixEvaluable } from './expressionIsPrefixEvaluable.ts'
|
|
3
|
+
import type { TemplateNode } from './types/TemplateNode.ts'
|
|
4
|
+
|
|
5
|
+
type ComponentNode = Extract<TemplateNode, { kind: 'component' }>
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
ADR-0037 Phase 2 — which `<Child/>` renders may START in the synchronous SSR prefix (as a `$$flight`
|
|
9
|
+
awaited at its structural position) so sibling child renders overlap instead of serializing behind
|
|
10
|
+
each other's `await Child.render(...)`. A child render is HOISTABLE iff it is on the TOP-LEVEL SPINE
|
|
11
|
+
— reachable from the template root through only plain elements, so no control-flow / await-branch /
|
|
12
|
+
snippet / slot binder is in scope — AND:
|
|
13
|
+
|
|
14
|
+
1. its component reference and every prop value (including a `{...spread}`) are prefix-evaluable:
|
|
15
|
+
no async-cell name (still pending at the prefix, before the barrier) and no nested-`<script>`
|
|
16
|
+
local declared earlier on the spine (those lower at their body position, not the prefix);
|
|
17
|
+
2. it carries NO slot content (`children` empty) — lazily-built slot content would need its own
|
|
18
|
+
prefix-evaluability proof; a childless card is the common parallel case;
|
|
19
|
+
3. no `bind:` prop — a two-way write-back channel that must wire at its structural position.
|
|
20
|
+
|
|
21
|
+
Off-spine components (inside `{#if}`/`{#for}`/`{#await}`, a snippet, or another component's slot) stay
|
|
22
|
+
sequential: conservatively excluded rather than proven row/branch-safe. Fail-closed throughout — any
|
|
23
|
+
doubt leaves the render sequential, never wrong. Server-only; the client build is untouched.
|
|
24
|
+
*/
|
|
25
|
+
export function hoistableChildRenders(
|
|
26
|
+
nodes: TemplateNode[],
|
|
27
|
+
cellReadNames: ReadonlySet<string>,
|
|
28
|
+
): Set<ComponentNode> {
|
|
29
|
+
const hoistable = new Set<ComponentNode>()
|
|
30
|
+
/* Every `{#snippet name}` in the component is a hoisted builder that may itself render an
|
|
31
|
+
async cell / await; a prop passing one to a child (`<List item={row}/>`) must therefore NOT
|
|
32
|
+
hoist — calling `row()` early, in the prefix, would render it out of order. Seed the binder set
|
|
33
|
+
with all snippet names (collected tree-wide, since a snippet is in scope for the whole
|
|
34
|
+
component) so any prop referencing one fails the prefix-evaluability check. */
|
|
35
|
+
const binders = new Set<string>()
|
|
36
|
+
collectSnippetNames(nodes, binders)
|
|
37
|
+
walkSpine(nodes, binders, cellReadNames, hoistable)
|
|
38
|
+
return hoistable
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* Recursively collect every `{#snippet name}` name in the subtree into `into`. */
|
|
42
|
+
function collectSnippetNames(list: TemplateNode[], into: Set<string>): void {
|
|
43
|
+
for (const node of list) {
|
|
44
|
+
if (node.kind === 'snippet') {
|
|
45
|
+
into.add(node.name)
|
|
46
|
+
}
|
|
47
|
+
if ('children' in node && Array.isArray(node.children)) {
|
|
48
|
+
collectSnippetNames(node.children, into)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* Walk a spine sibling list left-to-right: a `<script>` sibling's locals bind the siblings that
|
|
54
|
+
follow it; a component is judged in the running binder context; a plain element descends (still on
|
|
55
|
+
the spine). Every other node kind leaves the spine and is not descended — its components stay
|
|
56
|
+
sequential. */
|
|
57
|
+
function walkSpine(
|
|
58
|
+
list: TemplateNode[],
|
|
59
|
+
binders: ReadonlySet<string>,
|
|
60
|
+
cellReadNames: ReadonlySet<string>,
|
|
61
|
+
hoistable: Set<ComponentNode>,
|
|
62
|
+
): void {
|
|
63
|
+
let running = binders
|
|
64
|
+
for (const node of list) {
|
|
65
|
+
if (node.kind === 'script') {
|
|
66
|
+
running = union(running, declaredNames(node.code))
|
|
67
|
+
continue
|
|
68
|
+
}
|
|
69
|
+
if (node.kind === 'component') {
|
|
70
|
+
if (componentIsHoistable(node, running, cellReadNames)) {
|
|
71
|
+
hoistable.add(node)
|
|
72
|
+
}
|
|
73
|
+
/* Slot children are off-spine (built lazily inside the child) — not descended. */
|
|
74
|
+
continue
|
|
75
|
+
}
|
|
76
|
+
if (node.kind === 'element' && 'children' in node) {
|
|
77
|
+
walkSpine(node.children, running, cellReadNames, hoistable)
|
|
78
|
+
}
|
|
79
|
+
/* if / switch / each / await / try / snippet / branch: off the spine, skip. */
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* Childless, bind-free, with a prefix-evaluable tag and every prop value prefix-evaluable. */
|
|
84
|
+
function componentIsHoistable(
|
|
85
|
+
node: ComponentNode,
|
|
86
|
+
binders: ReadonlySet<string>,
|
|
87
|
+
cellReadNames: ReadonlySet<string>,
|
|
88
|
+
): boolean {
|
|
89
|
+
if (node.children.length > 0) {
|
|
90
|
+
return false
|
|
91
|
+
}
|
|
92
|
+
if (!expressionIsPrefixEvaluable(node.name, binders, cellReadNames)) {
|
|
93
|
+
return false
|
|
94
|
+
}
|
|
95
|
+
for (const prop of node.props) {
|
|
96
|
+
if (prop.bind === true) {
|
|
97
|
+
return false
|
|
98
|
+
}
|
|
99
|
+
if (!expressionIsPrefixEvaluable(prop.code, binders, cellReadNames)) {
|
|
100
|
+
return false
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return true
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function union(a: ReadonlySet<string>, b: ReadonlySet<string>): Set<string> {
|
|
107
|
+
const result = new Set(a)
|
|
108
|
+
for (const value of b) {
|
|
109
|
+
result.add(value)
|
|
110
|
+
}
|
|
111
|
+
return result
|
|
112
|
+
}
|
|
@@ -16,9 +16,11 @@ prop value can't diverge on the concatenation. Always yields a string, so an
|
|
|
16
16
|
interpolated attribute is always present.
|
|
17
17
|
*/
|
|
18
18
|
export function interpolatedTemplateLiteral(parts: TextPart[]): string {
|
|
19
|
+
/* A dynamic slot coerces nullish to empty so `attr="a {v} b"` with a pending/undefined
|
|
20
|
+
`v` yields `"a b"`, never `"a undefined b"` (ADR-0032 D3). */
|
|
19
21
|
const body = parts
|
|
20
22
|
.map((part) =>
|
|
21
|
-
part.kind === 'static' ? escapeTemplateChunk(part.value) : `\${${part.code}}`,
|
|
23
|
+
part.kind === 'static' ? escapeTemplateChunk(part.value) : `\${(${part.code}) ?? ''}`,
|
|
22
24
|
)
|
|
23
25
|
.join('')
|
|
24
26
|
return `\`${body}\``
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { createShadowProgram, type ShadowProgram } from './createShadowProgram.ts'
|
|
2
|
+
import { shadowInterpolationClassifier } from './shadowInterpolationClassifier.ts'
|
|
3
|
+
import type { InterpolationClassifier } from './types/InterpolationClassifier.ts'
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
Builds the type-directed interpolation classifier for one `.abide` file, backed by
|
|
7
|
+
a WARM shadow program kept once per project root (ADR-0019, Stage B). The shadow
|
|
8
|
+
program (checker + every component's source→shadow `mappings`) is expensive to
|
|
9
|
+
build, so it is created lazily on first request for a root and reused for every
|
|
10
|
+
component in that root — the coupling that makes type-directed lowering affordable.
|
|
11
|
+
The incremental `createShadowLanguageService` does not expose the checker, shadow
|
|
12
|
+
`ts.SourceFile`, or per-file mappings publicly, so v1 uses the one-shot
|
|
13
|
+
`createShadowProgram` (reused per root) rather than the LS overlay path.
|
|
14
|
+
|
|
15
|
+
FAIL-OPEN throughout: if the program can't be built, or the file has no shadow, the
|
|
16
|
+
classifier is absent (→ today's plain-value binding); and the returned closure wraps
|
|
17
|
+
its whole body so ANY throw (resolution failure, missing node, checker hiccup) returns
|
|
18
|
+
`'sync'`. A type-resolution problem degrades to today's behavior, never breaks the build.
|
|
19
|
+
*/
|
|
20
|
+
export function interpolationClassifierForRoot(
|
|
21
|
+
cache: Map<string, ShadowProgram | undefined>,
|
|
22
|
+
root: string,
|
|
23
|
+
abidePath: string,
|
|
24
|
+
): InterpolationClassifier | undefined {
|
|
25
|
+
if (!cache.has(root)) {
|
|
26
|
+
try {
|
|
27
|
+
cache.set(root, createShadowProgram(root))
|
|
28
|
+
} catch {
|
|
29
|
+
cache.set(root, undefined)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const shadowProgram = cache.get(root)
|
|
33
|
+
if (shadowProgram === undefined) {
|
|
34
|
+
return undefined
|
|
35
|
+
}
|
|
36
|
+
return shadowInterpolationClassifier(shadowProgram.program, shadowProgram.shadows, abidePath)
|
|
37
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import ts from 'typescript'
|
|
2
|
+
import { classifyInterpolationType } from './classifyInterpolationType.ts'
|
|
3
|
+
|
|
4
|
+
/* The two TypeScript diagnostics a bare async read (ADR-0032) raises spuriously in a template.
|
|
5
|
+
2339 — a property "missing" on the un-awaited `Promise`/`AsyncIterable`; 2801 — a condition the
|
|
6
|
+
raw promise makes "always defined". Both are correct against the raw type but wrong against the
|
|
7
|
+
runtime PEEK (the resolved value, `undefined` while pending), which the shadow can't see. */
|
|
8
|
+
const PROPERTY_DOES_NOT_EXIST = 2339
|
|
9
|
+
const CONDITION_ALWAYS_DEFINED = 2801
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
Whether a shadow diagnostic is one the ADR-0032 bare-async-read peek makes spurious. The runtime
|
|
13
|
+
lifts a promise/iterable sub-expression to a cell that reads the RESOLVED value (`undefined` while
|
|
14
|
+
pending), so `getFoo()?.name` composes on the resolved object and `{#if getFoo()}` holds while
|
|
15
|
+
pending — but the type-check shadow checks the RAW expression, where the property sits on the
|
|
16
|
+
un-awaited `Promise` and the promise reads as always-truthy. This recognises those two cases so the
|
|
17
|
+
caller can drop them:
|
|
18
|
+
|
|
19
|
+
- 2339 (missing property): an OPTIONAL access (`?.`) on a promise/iterable-typed base whose
|
|
20
|
+
property EXISTS on the awaited (promise) or frame (iterable) type — the peek's real member.
|
|
21
|
+
- 2801 (always-defined condition): the flagged subject itself classifies as a promise/iterable —
|
|
22
|
+
the peek is genuinely nullable (pending → `undefined`), so it is not always-defined.
|
|
23
|
+
|
|
24
|
+
Deliberately narrow so it never hides a real mistake: only these two codes; 2339 only via `?.`
|
|
25
|
+
(a bare `.name` on a promise stays an error, nudging toward the `?.` the peek needs) and only when
|
|
26
|
+
the property resolves on the awaited/frame type (a typo `?.namex` stays); 2801 only when the
|
|
27
|
+
subject is actually async (a bare `{#if someFn}` — a forgotten call — stays). Callers additionally
|
|
28
|
+
gate on the diagnostic mapping into the template region, so `<script>` code (where a forgotten
|
|
29
|
+
`await` must surface) is never touched.
|
|
30
|
+
*/
|
|
31
|
+
export function isSpuriousAsyncReadDiagnostic(
|
|
32
|
+
shadowFile: ts.SourceFile,
|
|
33
|
+
checker: ts.TypeChecker,
|
|
34
|
+
code: number,
|
|
35
|
+
start: number,
|
|
36
|
+
length: number,
|
|
37
|
+
): boolean {
|
|
38
|
+
if (code === PROPERTY_DOES_NOT_EXIST) {
|
|
39
|
+
return isResolvedOptionalAsyncAccess(shadowFile, checker, start, length)
|
|
40
|
+
}
|
|
41
|
+
if (code === CONDITION_ALWAYS_DEFINED) {
|
|
42
|
+
return isAsyncSubject(shadowFile, checker, start, length)
|
|
43
|
+
}
|
|
44
|
+
return false
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* A 2339 on the member of an optional (`?.`) access whose base is a promise/iterable and whose
|
|
48
|
+
accessed property exists on the resolved value — the ADR-0032 peek, not an error. */
|
|
49
|
+
function isResolvedOptionalAsyncAccess(
|
|
50
|
+
shadowFile: ts.SourceFile,
|
|
51
|
+
checker: ts.TypeChecker,
|
|
52
|
+
start: number,
|
|
53
|
+
length: number,
|
|
54
|
+
): boolean {
|
|
55
|
+
const access = enclosingOptionalAccess(deepestNodeAt(shadowFile, start, length))
|
|
56
|
+
if (access === undefined) {
|
|
57
|
+
return false
|
|
58
|
+
}
|
|
59
|
+
const resolved = resolvedAsyncType(access.expression, checker)
|
|
60
|
+
if (resolved === undefined) {
|
|
61
|
+
return false
|
|
62
|
+
}
|
|
63
|
+
const property = accessedPropertyName(access)
|
|
64
|
+
return property !== undefined && checker.getPropertyOfType(resolved, property) !== undefined
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* A 2801 whose flagged subject classifies as a promise/iterable — the peek reads `undefined` while
|
|
68
|
+
pending, so it is genuinely nullable, not always-defined. */
|
|
69
|
+
function isAsyncSubject(
|
|
70
|
+
shadowFile: ts.SourceFile,
|
|
71
|
+
checker: ts.TypeChecker,
|
|
72
|
+
start: number,
|
|
73
|
+
length: number,
|
|
74
|
+
): boolean {
|
|
75
|
+
const node = deepestNodeAt(shadowFile, start, length)
|
|
76
|
+
const kind = classifyInterpolationType(checker.getTypeAtLocation(node), node, checker)
|
|
77
|
+
return kind === 'promise' || kind === 'asyncIterable'
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* The resolved value an async base peeks: a promise's awaited type, an iterable's frame type,
|
|
81
|
+
`undefined` when the base isn't actually async. */
|
|
82
|
+
function resolvedAsyncType(base: ts.Node, checker: ts.TypeChecker): ts.Type | undefined {
|
|
83
|
+
const baseType = checker.getTypeAtLocation(base)
|
|
84
|
+
const kind = classifyInterpolationType(baseType, base, checker)
|
|
85
|
+
if (kind === 'promise') {
|
|
86
|
+
return checker.getAwaitedType(baseType)
|
|
87
|
+
}
|
|
88
|
+
if (kind === 'asyncIterable') {
|
|
89
|
+
return asyncFrameType(baseType, base, checker)
|
|
90
|
+
}
|
|
91
|
+
return undefined
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* The deepest AST node whose range contains the diagnostic span. */
|
|
95
|
+
function deepestNodeAt(sourceFile: ts.SourceFile, start: number, length: number): ts.Node {
|
|
96
|
+
let best: ts.Node = sourceFile
|
|
97
|
+
const end = start + length
|
|
98
|
+
const visit = (node: ts.Node): void => {
|
|
99
|
+
if (node.getStart(sourceFile) <= start && node.getEnd() >= end) {
|
|
100
|
+
best = node
|
|
101
|
+
node.forEachChild(visit)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
visit(sourceFile)
|
|
105
|
+
return best
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* The optional (`?.`) property/element access that OWNS `node` as its accessed member — walking
|
|
109
|
+
up from the name identifier. `undefined` when `node` isn't the member of an optional access
|
|
110
|
+
(a bare `.name`, or the base rather than the member). */
|
|
111
|
+
function enclosingOptionalAccess(
|
|
112
|
+
node: ts.Node,
|
|
113
|
+
): ts.PropertyAccessExpression | ts.ElementAccessExpression | undefined {
|
|
114
|
+
const parent = node.parent
|
|
115
|
+
if (parent === undefined) {
|
|
116
|
+
return undefined
|
|
117
|
+
}
|
|
118
|
+
if (ts.isPropertyAccessExpression(parent) && parent.name === node) {
|
|
119
|
+
return parent.questionDotToken === undefined ? undefined : parent
|
|
120
|
+
}
|
|
121
|
+
if (ts.isElementAccessExpression(parent) && parent.argumentExpression === node) {
|
|
122
|
+
return parent.questionDotToken === undefined ? undefined : parent
|
|
123
|
+
}
|
|
124
|
+
return undefined
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* The property name an access reads — the identifier for `a?.b`, the string literal for
|
|
128
|
+
`a?.['b']`. A computed non-literal element access has no static name. */
|
|
129
|
+
function accessedPropertyName(
|
|
130
|
+
access: ts.PropertyAccessExpression | ts.ElementAccessExpression,
|
|
131
|
+
): string | undefined {
|
|
132
|
+
if (ts.isPropertyAccessExpression(access)) {
|
|
133
|
+
return access.name.text
|
|
134
|
+
}
|
|
135
|
+
return ts.isStringLiteralLike(access.argumentExpression)
|
|
136
|
+
? access.argumentExpression.text
|
|
137
|
+
: undefined
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* The frame type of an `AsyncIterable<T>` — `T`, which is what the runtime peek reads (the latest
|
|
141
|
+
frame). The named async-iterable types (`AsyncIterable`/`AsyncGenerator`/`AsyncIterableIterator`)
|
|
142
|
+
all carry `T` as their first type argument, so read it directly; a structurally-typed iterable
|
|
143
|
+
with no type arguments falls back to resolving `[Symbol.asyncIterator]().next()`'s yielded value.
|
|
144
|
+
`location` is any node in the program, needed to resolve the iterator symbol's type. `undefined`
|
|
145
|
+
when neither yields a frame, so the caller keeps the diagnostic. */
|
|
146
|
+
function asyncFrameType(
|
|
147
|
+
type: ts.Type,
|
|
148
|
+
location: ts.Node,
|
|
149
|
+
checker: ts.TypeChecker,
|
|
150
|
+
): ts.Type | undefined {
|
|
151
|
+
const directArgument = checker.getTypeArguments(type as ts.TypeReference)[0]
|
|
152
|
+
if (directArgument !== undefined) {
|
|
153
|
+
return directArgument
|
|
154
|
+
}
|
|
155
|
+
/* The `Symbol.asyncIterator` member mangles to `__@asyncIterator@<id>`, so match by prefix. */
|
|
156
|
+
const iterator = checker
|
|
157
|
+
.getPropertiesOfType(type)
|
|
158
|
+
.find((property) => (property.escapedName as string).startsWith('__@asyncIterator'))
|
|
159
|
+
if (iterator === undefined) {
|
|
160
|
+
return undefined
|
|
161
|
+
}
|
|
162
|
+
const factory = checker.getTypeOfSymbolAtLocation(iterator, location).getCallSignatures()[0]
|
|
163
|
+
const next = factory && checker.getPropertyOfType(factory.getReturnType(), 'next')
|
|
164
|
+
const nextSignature =
|
|
165
|
+
next && checker.getTypeOfSymbolAtLocation(next, location).getCallSignatures()[0]
|
|
166
|
+
if (!nextSignature) {
|
|
167
|
+
return undefined
|
|
168
|
+
}
|
|
169
|
+
/* `next()` resolves to `IteratorResult<T, TReturn>`, whose first type argument is the yielded
|
|
170
|
+
`T` (its `value` PROPERTY widens to `T | TReturn` across the done/not-done union, so read the
|
|
171
|
+
argument, not the property). */
|
|
172
|
+
const result = checker.getAwaitedType(nextSignature.getReturnType())
|
|
173
|
+
return result && checker.getTypeArguments(result as ts.TypeReference)[0]
|
|
174
|
+
}
|