@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
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import ts from 'typescript'
|
|
2
2
|
import { ABIDE_PACKAGE_NAME } from '../../shared/ABIDE_PACKAGE_NAME.ts'
|
|
3
|
+
import { attrLiftPosition } from './attrLiftPosition.ts'
|
|
4
|
+
import { isWhitespaceText } from './isWhitespaceText.ts'
|
|
5
|
+
import { type LiftPosition, liftAsyncSubExpressions } from './liftAsyncSubExpressions.ts'
|
|
3
6
|
import { parseTemplate } from './parseTemplate.ts'
|
|
4
7
|
import {
|
|
5
8
|
NESTED_REACTIVE_BINDINGS,
|
|
@@ -8,7 +11,9 @@ import {
|
|
|
8
11
|
resolveReactiveExport,
|
|
9
12
|
} from './resolveReactiveExport.ts'
|
|
10
13
|
import { signalCallee } from './signalCallee.ts'
|
|
14
|
+
import { LEADING_SCRIPT } from './templateStartOffset.ts'
|
|
11
15
|
import type { CompiledShadow, ShadowDiagnostic, ShadowMapping } from './types/CompiledShadow.ts'
|
|
16
|
+
import type { InterpolationClassifier } from './types/InterpolationClassifier.ts'
|
|
12
17
|
import type { TemplateNode } from './types/TemplateNode.ts'
|
|
13
18
|
|
|
14
19
|
/*
|
|
@@ -69,9 +74,13 @@ The script's signal surface is rewritten to value types:
|
|
|
69
74
|
Everything else (functions, plain consts, imports) is emitted verbatim, so
|
|
70
75
|
expressions inside it (e.g. a computed's compute body) are checked and mapped too.
|
|
71
76
|
*/
|
|
72
|
-
export function compileShadow(
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
export function compileShadow(
|
|
78
|
+
source: string,
|
|
79
|
+
propsType = 'Record<string, any>',
|
|
80
|
+
classify?: InterpolationClassifier,
|
|
81
|
+
): CompiledShadow {
|
|
82
|
+
const builder = createBuilder(classify)
|
|
83
|
+
const leadingScript = source.match(LEADING_SCRIPT)
|
|
75
84
|
const scriptBody = leadingScript?.[1] ?? ''
|
|
76
85
|
/* Body starts just past the opening `<script …>`; template just past `</script>`. */
|
|
77
86
|
const scriptStart = leadingScript ? source.indexOf('>', leadingScript.index) + 1 : 0
|
|
@@ -80,6 +89,18 @@ export function compileShadow(source: string, propsType = 'Record<string, any>')
|
|
|
80
89
|
const { imports, types, scope, propsShapes, diagnostics, importedReactives, propsLocalName } =
|
|
81
90
|
analyzeScript(scriptBody, scriptStart)
|
|
82
91
|
builder.raw(shadowPreamble(importedReactives))
|
|
92
|
+
/* The peek helpers the async-interpolation wrap targets (ADR-0032): `$$peek` unwraps a promise
|
|
93
|
+
sub-expression to its resolved value (`undefined` while pending) and `$$peekStream` reads an
|
|
94
|
+
async iterable's latest frame, so `getFoo()?.name`/`{#if getFoo()}` type-check against the
|
|
95
|
+
RESOLVED value the runtime peeks — not the raw `Promise`. Emitted only when a classifier is
|
|
96
|
+
present (the wrap is type-directed); their names carry the reserved `$$` prefix an author
|
|
97
|
+
binding can never take, so they cannot collide with user code. */
|
|
98
|
+
if (classify !== undefined) {
|
|
99
|
+
builder.raw('declare function $$peek<T>(v: T): Awaited<T> | undefined\n')
|
|
100
|
+
builder.raw(
|
|
101
|
+
'declare function $$peekStream<S>(v: S): (S extends AsyncIterable<infer F> ? F : unknown) | undefined\n',
|
|
102
|
+
)
|
|
103
|
+
}
|
|
83
104
|
/* `props` is a required import (`abide/ui/props`). The shadow owns its type so the
|
|
84
105
|
return is file-contextual — the route param shape (page/layout) or `Record<string,
|
|
85
106
|
any>` (component), intersected with the author's annotation `T` so declared props
|
|
@@ -171,6 +192,12 @@ type Builder = {
|
|
|
171
192
|
mapped: (text: string, sourceLoc: number | undefined) => void
|
|
172
193
|
expr: (code: string, sourceLoc: number | undefined) => void
|
|
173
194
|
stmt: (code: string, sourceLoc: number | undefined) => void
|
|
195
|
+
/* Like `expr`/`stmt` but for a template interpolation: async (sub)expressions are wrapped in a
|
|
196
|
+
peek helper (`$$peek`/`$$peekStream`) so their RESOLVED type composes (ADR-0032). Every source
|
|
197
|
+
span still maps 1:1 — the inserted wrapper chars are unmapped — so diagnostics land precisely.
|
|
198
|
+
With no classifier (or no async sub-expression) these are byte-identical to `expr`/`stmt`. */
|
|
199
|
+
asyncExpr: (code: string, sourceLoc: number | undefined, position: LiftPosition) => void
|
|
200
|
+
asyncStmt: (code: string, sourceLoc: number | undefined, position: LiftPosition) => void
|
|
174
201
|
flush: (line: ScopeLine) => void
|
|
175
202
|
/* A fresh shadow-local binding name (`__<base>_<n>`) — for synthesised bindings
|
|
176
203
|
like an await's resolved value, kept distinct so nested blocks never collide. */
|
|
@@ -182,10 +209,55 @@ type Builder = {
|
|
|
182
209
|
emitted verbatim from the original script), offset-relative to the line start. */
|
|
183
210
|
type ScopeLine = { text: string; segments: ShadowMapping[] }
|
|
184
211
|
|
|
185
|
-
function createBuilder(): Builder {
|
|
212
|
+
function createBuilder(classify?: InterpolationClassifier): Builder {
|
|
186
213
|
let code = ''
|
|
187
214
|
let uniqueCounter = 0
|
|
188
215
|
const mappings: ShadowMapping[] = []
|
|
216
|
+
/* The non-blocking async (sub)expression spans in one interpolation — the peek-wrap targets.
|
|
217
|
+
A leading `await` (blocking) stays verbatim: `await X` already resolves in the async shadow
|
|
218
|
+
render fn. Fail-open: no classifier, an unparseable field, or the D4a `AsyncIterable`-in-`each`
|
|
219
|
+
throw all yield no spans, so the emit is verbatim (`collectValuePositionDiagnostics` still
|
|
220
|
+
reports the D4a error separately). */
|
|
221
|
+
const peekSpans = (exprCode: string, sourceLoc: number, position: LiftPosition) => {
|
|
222
|
+
if (classify === undefined) {
|
|
223
|
+
return []
|
|
224
|
+
}
|
|
225
|
+
try {
|
|
226
|
+
let counter = 0
|
|
227
|
+
const result = liftAsyncSubExpressions(
|
|
228
|
+
exprCode,
|
|
229
|
+
sourceLoc,
|
|
230
|
+
classify,
|
|
231
|
+
() => `__v${counter++}`,
|
|
232
|
+
position,
|
|
233
|
+
)
|
|
234
|
+
return result.spans.filter((span) => !span.blocking)
|
|
235
|
+
} catch {
|
|
236
|
+
return []
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/* Appends `exprCode` mapped 1:1 to source, but wraps each async span in its peek helper — the
|
|
240
|
+
wrapper chars are unmapped, every original char keeps its true source offset. */
|
|
241
|
+
const emitPeeked = (exprCode: string, sourceLoc: number, position: LiftPosition): void => {
|
|
242
|
+
const spans = peekSpans(exprCode, sourceLoc, position)
|
|
243
|
+
if (spans.length === 0) {
|
|
244
|
+
builder.mapped(exprCode, sourceLoc)
|
|
245
|
+
return
|
|
246
|
+
}
|
|
247
|
+
let cursor = 0
|
|
248
|
+
for (const span of spans) {
|
|
249
|
+
if (span.start > cursor) {
|
|
250
|
+
builder.mapped(exprCode.slice(cursor, span.start), sourceLoc + cursor)
|
|
251
|
+
}
|
|
252
|
+
builder.raw(span.kind === 'asyncIterable' ? '$$peekStream(' : '$$peek(')
|
|
253
|
+
builder.mapped(exprCode.slice(span.start, span.end), sourceLoc + span.start)
|
|
254
|
+
builder.raw(')')
|
|
255
|
+
cursor = span.end
|
|
256
|
+
}
|
|
257
|
+
if (cursor < exprCode.length) {
|
|
258
|
+
builder.mapped(exprCode.slice(cursor), sourceLoc + cursor)
|
|
259
|
+
}
|
|
260
|
+
}
|
|
189
261
|
const builder: Builder = {
|
|
190
262
|
unique(base) {
|
|
191
263
|
return `__${base}_${uniqueCounter++}`
|
|
@@ -219,6 +291,21 @@ function createBuilder(): Builder {
|
|
|
219
291
|
builder.expr(exprCode, sourceLoc)
|
|
220
292
|
code += ';\n'
|
|
221
293
|
},
|
|
294
|
+
asyncExpr(exprCode, sourceLoc, position) {
|
|
295
|
+
/* No source offset ⇒ can't map or classify — emit verbatim, like `expr`. */
|
|
296
|
+
if (sourceLoc === undefined) {
|
|
297
|
+
code += `(${exprCode})`
|
|
298
|
+
return
|
|
299
|
+
}
|
|
300
|
+
code += '('
|
|
301
|
+
emitPeeked(exprCode, sourceLoc, position)
|
|
302
|
+
code += ')'
|
|
303
|
+
},
|
|
304
|
+
asyncStmt(exprCode, sourceLoc, position) {
|
|
305
|
+
code += ';'
|
|
306
|
+
builder.asyncExpr(exprCode, sourceLoc, position)
|
|
307
|
+
code += ';\n'
|
|
308
|
+
},
|
|
222
309
|
flush(line) {
|
|
223
310
|
const base = code.length
|
|
224
311
|
for (const segment of line.segments) {
|
|
@@ -481,12 +568,20 @@ function scopeLineFor(
|
|
|
481
568
|
`verbatim(call.expression)` below keeps the AUTHOR's text for the trailing hover ref. */
|
|
482
569
|
const callee = resolveReactiveExport(call.expression, bindings)
|
|
483
570
|
if (callee === 'props') {
|
|
484
|
-
/*
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
571
|
+
/* The parent-facing prop shape. Like `state`/`computed` below, it comes from the
|
|
572
|
+
generic (`const {…} = props<Shape>()`) OR the destructure's binding annotation
|
|
573
|
+
(`const {…}: Shape = props()`) — either TS-idiomatic form resolves through the
|
|
574
|
+
shadow's own imports, so an imported/aliased type just works, no inline gotcha.
|
|
575
|
+
Default `Record<string, any>`; the destructure then projects verbatim so each
|
|
576
|
+
binding inherits its value type. */
|
|
577
|
+
const shape = call.typeArguments?.[0] ?? declaration.type
|
|
488
578
|
propsShapes.push(shape === undefined ? 'Record<string, any>' : verbatim(shape))
|
|
489
|
-
|
|
579
|
+
/* `let`, not `const`: a prop the component writes back through a two-way `bind:prop`
|
|
580
|
+
(`value += 1`, or forwarding it to another `bind:`) is a legal reassignment, so the
|
|
581
|
+
destructure must not read as a `const`. Bindability is dynamic (decided by what the
|
|
582
|
+
parent passed), so the shadow can't single out which props are written — it relaxes
|
|
583
|
+
the whole destructure, matching the runtime's usage-inferred upgrade. */
|
|
584
|
+
return { text: `let ${verbatim(declaration)};`, segments: [span(declaration, 4)] }
|
|
490
585
|
}
|
|
491
586
|
/* The rewrite drops the callee from the line, so hovering `state`/`computed`/
|
|
492
587
|
`linked` at its call site has nothing to resolve. Append the callee as a
|
|
@@ -596,7 +691,7 @@ function emitNode(node: TemplateNode, builder: Builder): void {
|
|
|
596
691
|
case 'text':
|
|
597
692
|
for (const part of node.parts) {
|
|
598
693
|
if (part.kind === 'expression') {
|
|
599
|
-
builder.
|
|
694
|
+
builder.asyncStmt(part.code, part.loc, 'content')
|
|
600
695
|
}
|
|
601
696
|
}
|
|
602
697
|
return
|
|
@@ -617,12 +712,22 @@ function emitNode(node: TemplateNode, builder: Builder): void {
|
|
|
617
712
|
/* An interpolated value checks each `{expr}` part on its own offset. */
|
|
618
713
|
for (const part of attr.parts) {
|
|
619
714
|
if (part.kind === 'expression') {
|
|
620
|
-
builder.
|
|
715
|
+
builder.asyncStmt(part.code, part.loc, 'attribute')
|
|
621
716
|
}
|
|
622
717
|
}
|
|
623
718
|
} else if (attr.kind !== 'static') {
|
|
624
|
-
/* Every other dynamic attribute checks its single `code`.
|
|
625
|
-
|
|
719
|
+
/* Every other dynamic attribute checks its single `code`. `attrLiftPosition` —
|
|
720
|
+
the shared gate the runtime front-end lifts through — decides whether the
|
|
721
|
+
value is async-liftable: only `name={code}` peek-wraps (its promise/stream
|
|
722
|
+
sub-expressions project to their resolved type), while a directive
|
|
723
|
+
(`on*`/`bind:`/`class:`/`style:`/`{...}`) checks its code verbatim, matching
|
|
724
|
+
that the runtime never injects a cell for it. */
|
|
725
|
+
const position = attrLiftPosition(attr)
|
|
726
|
+
if (position !== undefined) {
|
|
727
|
+
builder.asyncStmt(attr.code, attr.loc, position)
|
|
728
|
+
} else {
|
|
729
|
+
builder.stmt(attr.code, attr.loc)
|
|
730
|
+
}
|
|
626
731
|
}
|
|
627
732
|
}
|
|
628
733
|
emitNodes(node.children, builder)
|
|
@@ -644,6 +749,12 @@ function emitNode(node: TemplateNode, builder: Builder): void {
|
|
|
644
749
|
prop.name.startsWith('style:') ||
|
|
645
750
|
prop.name === 'attach'
|
|
646
751
|
const hasSpread = node.props.some((prop) => prop.spread)
|
|
752
|
+
/* Default slot content (`<Card>…</Card>`) lowers to a `children` Snippet prop at
|
|
753
|
+
runtime (see generateBuild.slotPart), so the completeness check must treat
|
|
754
|
+
`children` as supplied when it's present (handled below). Mirror the runtime's
|
|
755
|
+
predicate: any child that isn't whitespace-only text is renderable slot content
|
|
756
|
+
— an empty/whitespace slot sets no `children` layer, so it doesn't count. */
|
|
757
|
+
const hasSlotContent = node.children.some((child) => !isWhitespaceText(child))
|
|
647
758
|
for (const prop of node.props.filter(handled)) {
|
|
648
759
|
/* Lead with a defensive `;`: an IIFE/object-literal arg starts with `(` or
|
|
649
760
|
`{`, so without it a preceding scope statement left unterminated (a script
|
|
@@ -679,14 +790,30 @@ function emitNode(node: TemplateNode, builder: Builder): void {
|
|
|
679
790
|
builder.raw(');\n')
|
|
680
791
|
}
|
|
681
792
|
} else {
|
|
682
|
-
|
|
793
|
+
/* With slot content the child's `children` prop is supplied by the slot, so
|
|
794
|
+
check completeness against the shape with `children` made OPTIONAL rather
|
|
795
|
+
than removed — dropping it via a bare `Omit` collapses a children-only
|
|
796
|
+
shape to `{}`, a weak type that then lets excess props slip by. `Omit` +
|
|
797
|
+
`Partial<Pick<…>>` keeps every other prop required and `children` as a
|
|
798
|
+
known-optional key (a no-op `Partial<{}>` when the child declares none), so
|
|
799
|
+
completeness and excess-prop checks both stay intact. The explicit
|
|
800
|
+
`children={…}` attribute is dropped from the literal — the slot's key wins
|
|
801
|
+
(`composeProps`), and leaving it in would read as excess. */
|
|
802
|
+
const propsShape = `Parameters<typeof ${node.name}>[0]`
|
|
803
|
+
const completenessType = hasSlotContent
|
|
804
|
+
? `Omit<${propsShape}, 'children'> & Partial<Pick<${propsShape}, Extract<keyof ${propsShape}, 'children'>>>`
|
|
805
|
+
: propsShape
|
|
806
|
+
const literalProps = hasSlotContent
|
|
807
|
+
? dataProps.filter((prop) => prop.name !== 'children')
|
|
808
|
+
: dataProps
|
|
809
|
+
builder.raw(`;((__c: ${completenessType}): void => { void __c })({`)
|
|
683
810
|
/* A zero-length anchor right after `{`, pointing at the tag: a missing-
|
|
684
811
|
required-prop error spans the literal from `{`, so it overlaps this and
|
|
685
812
|
maps to the tag (an empty span trivially satisfies the source-text ==
|
|
686
813
|
shadow-text invariant). */
|
|
687
814
|
builder.mapped('', node.loc)
|
|
688
815
|
builder.raw('\n')
|
|
689
|
-
for (const prop of
|
|
816
|
+
for (const prop of literalProps) {
|
|
690
817
|
/* The key mapped to its source name (excess-prop errors land on the key);
|
|
691
818
|
the value verbatim-mapped (wrong-type errors land on the value).
|
|
692
819
|
Hyphenated names (`aria-label`, `data-*`) aren't valid identifiers, so
|
|
@@ -726,7 +853,7 @@ function emitNode(node: TemplateNode, builder: Builder): void {
|
|
|
726
853
|
)
|
|
727
854
|
const thenChildren = node.children.filter((child) => child.kind !== 'case')
|
|
728
855
|
builder.raw('if ')
|
|
729
|
-
builder.
|
|
856
|
+
builder.asyncExpr(node.condition, node.loc, 'if')
|
|
730
857
|
builder.raw(' {\n')
|
|
731
858
|
emitNodes(thenChildren, builder)
|
|
732
859
|
builder.raw('}')
|
|
@@ -735,7 +862,7 @@ function emitNode(node: TemplateNode, builder: Builder): void {
|
|
|
735
862
|
for (const branch of branches) {
|
|
736
863
|
if (branch.condition !== undefined) {
|
|
737
864
|
builder.raw(' else if ')
|
|
738
|
-
builder.
|
|
865
|
+
builder.asyncExpr(branch.condition, branch.loc, 'if')
|
|
739
866
|
builder.raw(' {\n')
|
|
740
867
|
} else {
|
|
741
868
|
builder.raw(' else {\n')
|
|
@@ -817,7 +944,7 @@ function emitNode(node: TemplateNode, builder: Builder): void {
|
|
|
817
944
|
non-case children (whitespace between cases) carry nothing and are
|
|
818
945
|
skipped. `break` keeps cases independent under `noFallthroughCasesInSwitch`. */
|
|
819
946
|
builder.raw('switch (')
|
|
820
|
-
builder.
|
|
947
|
+
builder.asyncExpr(node.subject, node.loc, 'switch')
|
|
821
948
|
builder.raw(') {\n')
|
|
822
949
|
for (const child of node.children) {
|
|
823
950
|
if (child.kind !== 'case') {
|
|
@@ -20,9 +20,19 @@ export function composeProps(
|
|
|
20
20
|
props: ComponentProps,
|
|
21
21
|
lowerExpression: (code: string) => string,
|
|
22
22
|
slotPart: string | undefined,
|
|
23
|
+
/* The two-way bind accessors (`lowerContext`): `bindRead` lowers a target to its
|
|
24
|
+
current value, `bindWrite` to the statement that writes a value back. Shared with
|
|
25
|
+
element `bind:`, so a component bind accepts the same lvalue / `{ get, set }` targets. */
|
|
26
|
+
bindRead: (code: string) => string,
|
|
27
|
+
bindWrite: (code: string, valueExpr: string) => string,
|
|
23
28
|
): string {
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
/* A `bind:<name>={target}` prop: the normal `() => value` entry, annotated with a
|
|
30
|
+
`set` write-back channel (`bindProp`) so the child can push changes upstream. A
|
|
31
|
+
read-only consumer still just calls the thunk; the setter rides along unseen. */
|
|
32
|
+
const propThunk = (prop: { name: string; code: string; bind?: true }): string =>
|
|
33
|
+
prop.bind === true
|
|
34
|
+
? `${JSON.stringify(prop.name)}: $$bindProp(() => (${bindRead(prop.code)}), ($value) => { ${bindWrite(prop.code, '$value')} })`
|
|
35
|
+
: `${JSON.stringify(prop.name)}: () => (${lowerExpression(prop.code)})`
|
|
26
36
|
if (!props.some((prop) => prop.spread)) {
|
|
27
37
|
const parts = props.map(propThunk)
|
|
28
38
|
if (slotPart !== undefined) {
|
|
@@ -4,14 +4,18 @@ import { messageFromError } from '../../shared/messageFromError.ts'
|
|
|
4
4
|
import { mapSyntacticClassification, mapTsClassification } from './ABIDE_SEMANTIC_TOKENS_LEGEND.ts'
|
|
5
5
|
import { assetModulesFile } from './assetModulesFile.ts'
|
|
6
6
|
import { compileShadow } from './compileShadow.ts'
|
|
7
|
+
import { isSpuriousAsyncReadDiagnostic } from './isSpuriousAsyncReadDiagnostic.ts'
|
|
7
8
|
import { loadShadowTsConfig } from './loadShadowTsConfig.ts'
|
|
8
9
|
import { pagePropsType } from './pagePropsType.ts'
|
|
9
10
|
import { remapShadowDiagnostic } from './remapShadowDiagnostic.ts'
|
|
10
11
|
import { resolveAbideImports } from './resolveAbideImports.ts'
|
|
12
|
+
import { shadowInterpolationClassifier } from './shadowInterpolationClassifier.ts'
|
|
11
13
|
import { shadowNaming } from './shadowNaming.ts'
|
|
12
14
|
import { sourceToShadowOffset } from './sourceToShadowOffset.ts'
|
|
15
|
+
import { templateStartOffset } from './templateStartOffset.ts'
|
|
13
16
|
import type { AbideDiagnostic } from './types/AbideDiagnostic.ts'
|
|
14
17
|
import type { CompiledShadow } from './types/CompiledShadow.ts'
|
|
18
|
+
import type { InterpolationClassifier } from './types/InterpolationClassifier.ts'
|
|
15
19
|
import type { SemanticToken } from './types/SemanticToken.ts'
|
|
16
20
|
|
|
17
21
|
const { suffixed, isShadow, sourceOf } = shadowNaming
|
|
@@ -45,12 +49,6 @@ export function createShadowLanguageService(cwd: string): ShadowLanguageService
|
|
|
45
49
|
const { options, fileNames } = loadShadowTsConfig(cwd)
|
|
46
50
|
const overlays = new Map<string, string>()
|
|
47
51
|
const versions = new Map<string, number>()
|
|
48
|
-
const shadows = new Map<string, CompiledShadow>()
|
|
49
|
-
const parseErrors = new Map<string, string>()
|
|
50
|
-
/* Memo of `shadowText` output keyed by source path, tagged with the shadow
|
|
51
|
-
version it was compiled at; a stale tag forces recompilation. `update`/`close`
|
|
52
|
-
bump that version, so the next read recompiles. */
|
|
53
|
-
const compiledAt = new Map<string, { version: number; code: string }>()
|
|
54
52
|
/* Memo of `pagePropsType` (route re-parse) per source path; the path → props
|
|
55
53
|
type mapping is immutable, so no version tag is needed. */
|
|
56
54
|
const propsTypes = new Map<string, string | undefined>()
|
|
@@ -69,33 +67,6 @@ export function createShadowLanguageService(cwd: string): ShadowLanguageService
|
|
|
69
67
|
const exists = (abidePath: string): boolean =>
|
|
70
68
|
overlays.has(abidePath) || ts.sys.fileExists(abidePath)
|
|
71
69
|
|
|
72
|
-
/* Compiles (and caches) a component's shadow from its overlay or disk text; a
|
|
73
|
-
template parse error yields a minimal valid module + a recorded message.
|
|
74
|
-
Memoised against the shadow's version (bumped by `update`/`close`), so
|
|
75
|
-
repeated reads at the same version skip the ~700-line compile and the
|
|
76
|
-
`shadows`/`parseErrors` caches stay current. */
|
|
77
|
-
const shadowText = (abidePath: string): string => {
|
|
78
|
-
const version = versions.get(suffixed(abidePath)) ?? 0
|
|
79
|
-
const memo = compiledAt.get(abidePath)
|
|
80
|
-
if (memo !== undefined && memo.version === version) {
|
|
81
|
-
return memo.code
|
|
82
|
-
}
|
|
83
|
-
const source = overlays.get(abidePath) ?? ts.sys.readFile(abidePath) ?? ''
|
|
84
|
-
try {
|
|
85
|
-
const compiled = compileShadow(source, propsTypeOf(abidePath))
|
|
86
|
-
shadows.set(abidePath, compiled)
|
|
87
|
-
parseErrors.delete(abidePath)
|
|
88
|
-
compiledAt.set(abidePath, { version, code: compiled.code })
|
|
89
|
-
return compiled.code
|
|
90
|
-
} catch (error) {
|
|
91
|
-
shadows.set(abidePath, { code: '', mappings: [] })
|
|
92
|
-
parseErrors.set(abidePath, messageFromError(error))
|
|
93
|
-
const code = 'export default function (): void {}\n'
|
|
94
|
-
compiledAt.set(abidePath, { version, code })
|
|
95
|
-
return code
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
70
|
/* All component shadows: those on disk plus any opened (possibly unsaved) ones. */
|
|
100
71
|
const shadowNames = (): string[] => {
|
|
101
72
|
const disk = [...new Bun.Glob('**/*.abide').scanSync({ cwd, onlyFiles: true })]
|
|
@@ -104,44 +75,99 @@ export function createShadowLanguageService(cwd: string): ShadowLanguageService
|
|
|
104
75
|
return [...new Set([...disk, ...overlays.keys()])].map(suffixed)
|
|
105
76
|
}
|
|
106
77
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
return ts.ScriptSnapshot.fromString(assets.content)
|
|
78
|
+
/* One incremental language service over the shadow world, its shadows built by `compile`. Two
|
|
79
|
+
are made: a VERBATIM one (the classifier source — interpolations un-wrapped, so `getFoo()`
|
|
80
|
+
still types as `Promise`), and the WRAPPED main one (async interpolations peek-wrapped, ADR-
|
|
81
|
+
0032, so hover/completions/diagnostics see the RESOLVED value). They share `overlays`/`versions`
|
|
82
|
+
so both reflect unsaved edits, but hold SEPARATE shadow caches + document registries (the same
|
|
83
|
+
shadow file name carries different text in each). */
|
|
84
|
+
const buildService = (compile: (source: string, abidePath: string) => CompiledShadow) => {
|
|
85
|
+
const shadows = new Map<string, CompiledShadow>()
|
|
86
|
+
const parseErrors = new Map<string, string>()
|
|
87
|
+
/* Memo of `shadowText` output keyed by source path, tagged with the shadow version it was
|
|
88
|
+
compiled at; a stale tag forces recompilation. `update`/`close` bump that version. */
|
|
89
|
+
const compiledAt = new Map<string, { version: number; code: string }>()
|
|
90
|
+
const shadowText = (abidePath: string): string => {
|
|
91
|
+
const version = versions.get(suffixed(abidePath)) ?? 0
|
|
92
|
+
const memo = compiledAt.get(abidePath)
|
|
93
|
+
if (memo !== undefined && memo.version === version) {
|
|
94
|
+
return memo.code
|
|
125
95
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
96
|
+
const source = overlays.get(abidePath) ?? ts.sys.readFile(abidePath) ?? ''
|
|
97
|
+
try {
|
|
98
|
+
const compiled = compile(source, abidePath)
|
|
99
|
+
shadows.set(abidePath, compiled)
|
|
100
|
+
parseErrors.delete(abidePath)
|
|
101
|
+
compiledAt.set(abidePath, { version, code: compiled.code })
|
|
102
|
+
return compiled.code
|
|
103
|
+
} catch (error) {
|
|
104
|
+
shadows.set(abidePath, { code: '', mappings: [] })
|
|
105
|
+
parseErrors.set(abidePath, messageFromError(error))
|
|
106
|
+
const code = 'export default function (): void {}\n'
|
|
107
|
+
compiledAt.set(abidePath, { version, code })
|
|
108
|
+
return code
|
|
130
109
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
110
|
+
}
|
|
111
|
+
const moduleResolutionHost: ts.ModuleResolutionHost = {
|
|
112
|
+
fileExists: (fileName) =>
|
|
113
|
+
fileName === assets.path ||
|
|
114
|
+
(isShadow(fileName) ? exists(sourceOf(fileName)) : ts.sys.fileExists(fileName)),
|
|
115
|
+
readFile: (fileName) =>
|
|
116
|
+
fileName === assets.path
|
|
117
|
+
? assets.content
|
|
118
|
+
: isShadow(fileName)
|
|
119
|
+
? shadowText(sourceOf(fileName))
|
|
120
|
+
: ts.sys.readFile(fileName),
|
|
121
|
+
}
|
|
122
|
+
const host: ts.LanguageServiceHost = {
|
|
123
|
+
getScriptFileNames: () => [assets.path, ...fileNames, ...shadowNames()],
|
|
124
|
+
getScriptVersion: (fileName) => String(versions.get(fileName) ?? 0),
|
|
125
|
+
getScriptSnapshot: (fileName) => {
|
|
126
|
+
if (fileName === assets.path) {
|
|
127
|
+
return ts.ScriptSnapshot.fromString(assets.content)
|
|
128
|
+
}
|
|
129
|
+
if (isShadow(fileName)) {
|
|
130
|
+
return exists(sourceOf(fileName))
|
|
131
|
+
? ts.ScriptSnapshot.fromString(shadowText(sourceOf(fileName)))
|
|
132
|
+
: undefined
|
|
133
|
+
}
|
|
134
|
+
const text = ts.sys.readFile(fileName)
|
|
135
|
+
return text === undefined ? undefined : ts.ScriptSnapshot.fromString(text)
|
|
136
|
+
},
|
|
137
|
+
getCurrentDirectory: () => cwd,
|
|
138
|
+
getCompilationSettings: () => options,
|
|
139
|
+
getDefaultLibFileName: (compilerOptions) => ts.getDefaultLibFilePath(compilerOptions),
|
|
140
|
+
fileExists: moduleResolutionHost.fileExists,
|
|
141
|
+
readFile: moduleResolutionHost.readFile,
|
|
142
|
+
readDirectory: ts.sys.readDirectory,
|
|
143
|
+
directoryExists: ts.sys.directoryExists,
|
|
144
|
+
getDirectories: ts.sys.getDirectories,
|
|
145
|
+
resolveModuleNames: resolveAbideImports(options, moduleResolutionHost),
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
service: ts.createLanguageService(host, ts.createDocumentRegistry()),
|
|
149
|
+
shadows,
|
|
150
|
+
parseErrors,
|
|
151
|
+
shadowText,
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const verbatim = buildService((source, abidePath) =>
|
|
156
|
+
compileShadow(source, propsTypeOf(abidePath)),
|
|
157
|
+
)
|
|
158
|
+
/* The wrapped shadow's peek-wrap is type-directed: it asks the verbatim program which
|
|
159
|
+
sub-expressions are async. `getProgram()` reflects the shared overlays/versions, so the
|
|
160
|
+
classifier tracks unsaved edits; fail-open to `undefined` (verbatim shadow) if unavailable. */
|
|
161
|
+
const classifierFor = (abidePath: string): InterpolationClassifier | undefined => {
|
|
162
|
+
const program = verbatim.service.getProgram()
|
|
163
|
+
return program === undefined
|
|
164
|
+
? undefined
|
|
165
|
+
: shadowInterpolationClassifier(program, verbatim.shadows, abidePath)
|
|
143
166
|
}
|
|
144
|
-
const
|
|
167
|
+
const main = buildService((source, abidePath) =>
|
|
168
|
+
compileShadow(source, propsTypeOf(abidePath), classifierFor(abidePath)),
|
|
169
|
+
)
|
|
170
|
+
const { service, shadows, parseErrors, shadowText } = main
|
|
145
171
|
|
|
146
172
|
const bump = (abidePath: string): void => {
|
|
147
173
|
const fileName = suffixed(abidePath)
|
|
@@ -178,6 +204,12 @@ export function createShadowLanguageService(cwd: string): ShadowLanguageService
|
|
|
178
204
|
]
|
|
179
205
|
}
|
|
180
206
|
const mappings = shadows.get(abidePath)?.mappings ?? []
|
|
207
|
+
/* The shadow file + checker + template boundary, for the ADR-0032 bare-async-read
|
|
208
|
+
suppression (mirrors `collectAbideDiagnostics` so the editor and CLI agree). */
|
|
209
|
+
const shadowFile = service.getProgram()?.getSourceFile(fileName)
|
|
210
|
+
const checker = service.getProgram()?.getTypeChecker()
|
|
211
|
+
const source = overlays.get(abidePath) ?? ts.sys.readFile(abidePath) ?? ''
|
|
212
|
+
const templateStart = templateStartOffset(source)
|
|
181
213
|
return raw.flatMap((diagnostic) => {
|
|
182
214
|
if (diagnostic.start === undefined) {
|
|
183
215
|
return []
|
|
@@ -190,6 +222,22 @@ export function createShadowLanguageService(cwd: string): ShadowLanguageService
|
|
|
190
222
|
if (located === undefined) {
|
|
191
223
|
return []
|
|
192
224
|
}
|
|
225
|
+
/* Drop the spurious "property missing on Promise" / "condition always defined" a
|
|
226
|
+
bare async read provokes — the runtime peeks the resolved value (ADR-0032). */
|
|
227
|
+
if (
|
|
228
|
+
shadowFile !== undefined &&
|
|
229
|
+
checker !== undefined &&
|
|
230
|
+
located.start >= templateStart &&
|
|
231
|
+
isSpuriousAsyncReadDiagnostic(
|
|
232
|
+
shadowFile,
|
|
233
|
+
checker,
|
|
234
|
+
diagnostic.code,
|
|
235
|
+
diagnostic.start,
|
|
236
|
+
diagnostic.length ?? 0,
|
|
237
|
+
)
|
|
238
|
+
) {
|
|
239
|
+
return []
|
|
240
|
+
}
|
|
193
241
|
return [
|
|
194
242
|
{
|
|
195
243
|
file: abidePath,
|
|
@@ -2,12 +2,15 @@ import { resolve } from 'node:path'
|
|
|
2
2
|
import ts from 'typescript'
|
|
3
3
|
import { messageFromError } from '../../shared/messageFromError.ts'
|
|
4
4
|
import { assetModulesFile } from './assetModulesFile.ts'
|
|
5
|
+
import { cachedSourceFile } from './cachedSourceFile.ts'
|
|
5
6
|
import { compileShadow } from './compileShadow.ts'
|
|
6
7
|
import { loadShadowTsConfig } from './loadShadowTsConfig.ts'
|
|
7
8
|
import { pagePropsType } from './pagePropsType.ts'
|
|
8
9
|
import { resolveAbideImports } from './resolveAbideImports.ts'
|
|
9
10
|
import { shadowNaming } from './shadowNaming.ts'
|
|
11
|
+
import { sourceFileOptionsSignature } from './sourceFileOptionsSignature.ts'
|
|
10
12
|
import type { CompiledShadow } from './types/CompiledShadow.ts'
|
|
13
|
+
import type { InterpolationClassifier } from './types/InterpolationClassifier.ts'
|
|
11
14
|
|
|
12
15
|
const { suffixed, isShadow, sourceOf } = shadowNaming
|
|
13
16
|
|
|
@@ -29,7 +32,16 @@ the project's tsconfig (lib/paths/baseUrl) so the shadows type-check against the
|
|
|
29
32
|
same world the app does; `noUnusedLocals`/`noUnusedParameters` are forced off
|
|
30
33
|
because the shadow legitimately declares scope bindings a template may not read.
|
|
31
34
|
*/
|
|
32
|
-
export function createShadowProgram(
|
|
35
|
+
export function createShadowProgram(
|
|
36
|
+
cwd: string,
|
|
37
|
+
abidePaths?: string[],
|
|
38
|
+
/* Per-file interpolation classifier (ADR-0032). When provided, each shadow peek-wraps its async
|
|
39
|
+
sub-expressions so they type-check against the RESOLVED value; it must be backed by a SEPARATE
|
|
40
|
+
verbatim program (see `checkAbide`), since a classifier reading these same wrapped shadows
|
|
41
|
+
would be circular. Absent ⇒ verbatim shadows (the classifier-source pass, and any caller that
|
|
42
|
+
doesn't need the peek types). */
|
|
43
|
+
classifierFor?: (abidePath: string) => InterpolationClassifier | undefined,
|
|
44
|
+
): ShadowProgram {
|
|
33
45
|
const { options, fileNames } = loadShadowTsConfig(cwd)
|
|
34
46
|
const shadows = new Map<string, CompiledShadow>()
|
|
35
47
|
const parseErrors = new Map<string, string>()
|
|
@@ -47,7 +59,11 @@ export function createShadowProgram(cwd: string, abidePaths?: string[]): ShadowP
|
|
|
47
59
|
const shadowText = (abidePath: string): string => {
|
|
48
60
|
const source = ts.sys.readFile(abidePath) ?? ''
|
|
49
61
|
try {
|
|
50
|
-
const compiled = compileShadow(
|
|
62
|
+
const compiled = compileShadow(
|
|
63
|
+
source,
|
|
64
|
+
pagePropsType(abidePath),
|
|
65
|
+
classifierFor?.(abidePath),
|
|
66
|
+
)
|
|
51
67
|
shadows.set(abidePath, compiled)
|
|
52
68
|
parseErrors.delete(abidePath)
|
|
53
69
|
return compiled.code
|
|
@@ -61,22 +77,35 @@ export function createShadowProgram(cwd: string, abidePaths?: string[]): ShadowP
|
|
|
61
77
|
/* Ambient declarations for bundler-handled asset imports (`*.css`, …). */
|
|
62
78
|
const assets = assetModulesFile(cwd)
|
|
63
79
|
|
|
80
|
+
/* The parse/bind-affecting options are identical for every source in a program, so compute
|
|
81
|
+
the cache signature once and reuse it for every file (see `cachedSourceFile`). */
|
|
82
|
+
const signature = sourceFileOptionsSignature(options)
|
|
83
|
+
|
|
64
84
|
const host = ts.createCompilerHost(options, true)
|
|
65
85
|
const originalGetSourceFile = host.getSourceFile.bind(host)
|
|
66
86
|
host.getSourceFile = (fileName, languageVersionOrOptions, onError, shouldCreate) => {
|
|
67
87
|
if (fileName === assets.path) {
|
|
68
|
-
return
|
|
88
|
+
return cachedSourceFile(fileName, assets.content, languageVersionOrOptions, signature)
|
|
69
89
|
}
|
|
70
90
|
if (isShadow(fileName)) {
|
|
71
|
-
|
|
91
|
+
/* `shadowText` populates the per-program `shadows`/`parseErrors` maps as a side effect,
|
|
92
|
+
so it must run on every call — only its expensive TS parse is memoised by `cachedSourceFile`. */
|
|
93
|
+
return cachedSourceFile(
|
|
72
94
|
fileName,
|
|
73
95
|
shadowText(sourceOf(fileName)),
|
|
74
96
|
languageVersionOrOptions,
|
|
75
|
-
|
|
97
|
+
signature,
|
|
76
98
|
ts.ScriptKind.TS,
|
|
77
99
|
)
|
|
78
100
|
}
|
|
79
|
-
|
|
101
|
+
/* Real `.ts`/`.d.ts` inputs (the ~3MB of default libs plus resolved dependencies) — read the
|
|
102
|
+
text and reuse the parsed file across programs when it is byte-identical. Fall back to the
|
|
103
|
+
default host on a read failure so its missing-file / `onError` handling is preserved. */
|
|
104
|
+
const text = ts.sys.readFile(fileName)
|
|
105
|
+
if (text === undefined) {
|
|
106
|
+
return originalGetSourceFile(fileName, languageVersionOrOptions, onError, shouldCreate)
|
|
107
|
+
}
|
|
108
|
+
return cachedSourceFile(fileName, text, languageVersionOrOptions, signature)
|
|
80
109
|
}
|
|
81
110
|
host.fileExists = (fileName) =>
|
|
82
111
|
fileName === assets.path ||
|