@abide/abide 0.123.2 → 0.124.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ABIDE.md +8 -5
- package/CHANGELOG.md +58 -0
- package/bin/abide.ts +36 -65
- package/docs/REGISTRY.md +45 -12
- package/docs/RULEBOOK.md +24 -14
- package/docs/abide.md +999 -459
- package/package.json +1 -1
- package/src/compiler/ARRAY_MUTATORS.ts +20 -0
- package/src/compiler/Diagnostics.ts +198 -0
- package/src/compiler/KeptChecker.ts +97 -0
- package/src/compiler/LoweredFile.ts +609 -0
- package/src/compiler/Lowering.ts +18 -1
- package/src/compiler/PositionedRefusal.ts +1 -1
- package/src/compiler/SHARED_NAMES.ts +2 -0
- package/src/compiler/ShadowHost.ts +670 -0
- package/src/compiler/ShadowMap.ts +1 -1
- package/src/compiler/TypeSession.ts +76 -1743
- package/src/compiler/appLayout.ts +28 -20
- package/src/compiler/appLints.ts +109 -0
- package/src/compiler/argsDefaultsIn.ts +1 -1
- package/src/compiler/checkApp.ts +64 -68
- package/src/compiler/checkedShadow.ts +2 -2
- package/src/compiler/classifyRead.ts +16 -1
- package/src/compiler/compileApp.ts +102 -71
- package/src/compiler/declaredExports.ts +46 -21
- package/src/compiler/diagnoseSources.ts +232 -0
- package/src/compiler/emitClient.ts +1 -1
- package/src/compiler/formatAbide.ts +7 -8
- package/src/compiler/handlersUnder.ts +32 -63
- package/src/compiler/lineOf.ts +0 -19
- package/src/compiler/matchersOf.ts +3 -3
- package/src/compiler/mountsOf.ts +3 -3
- package/src/compiler/reactiveReads.ts +235 -0
- package/src/compiler/reactiveTypes.ts +13 -0
- package/src/compiler/readSources.ts +16 -18
- package/src/compiler/readsIn.ts +181 -82
- package/src/compiler/routesDeclaration.ts +46 -0
- package/src/compiler/scaffold.ts +7 -2
- package/src/compiler/serveLanguage.ts +46 -74
- package/src/compiler/sharedModuleWrites.ts +1 -1
- package/src/compiler/splitImports.ts +2 -2
- package/src/compiler/templateWarnings.ts +1 -2
- package/src/compiler/trackingWarnings.ts +1 -1
- package/src/server/CLIENT_BUILD.ts +4 -32
- package/src/server/DesktopSession.ts +213 -0
- package/src/server/Followers.ts +399 -0
- package/src/server/MODULE_QUERIES.ts +6 -2
- package/src/server/PageRender.ts +201 -0
- package/src/server/RESUME_MILLISECONDS.ts +2 -0
- package/src/server/abideModules.ts +7 -7
- package/src/server/answerFailure.ts +3 -2
- package/src/server/answerRequest.ts +16 -369
- package/src/server/answerRpc.ts +48 -302
- package/src/server/answerSocket.ts +41 -490
- package/src/server/appBuild.ts +377 -0
- package/src/server/appKit.ts +1 -1
- package/src/server/appPlugins.ts +15 -17
- package/src/server/appTree.ts +6 -5
- package/src/server/argsContract.ts +209 -0
- package/src/server/bridgeRelay.ts +153 -65
- package/src/server/claudeCode.ts +30 -127
- package/src/server/config.ts +6 -7
- package/src/server/createApp.ts +38 -66
- package/src/server/csp.ts +1 -1
- package/src/server/deferredBuilds.ts +3 -16
- package/src/server/devWorker.ts +11 -15
- package/src/server/faceOf.ts +1 -1
- package/src/server/generatedSurfaces.ts +2 -1
- package/src/server/handlerFileMount.ts +40 -0
- package/src/server/index.ts +3 -3
- package/src/server/installServer.ts +5 -9
- package/src/server/invokeHandler.ts +230 -0
- package/src/server/mountHandlers.ts +184 -0
- package/src/server/openDesktopWindow.ts +308 -0
- package/src/server/packageDirectory.ts +14 -0
- package/src/server/preload.ts +21 -2
- package/src/server/principal.ts +5 -4
- package/src/server/remote.ts +2 -2
- package/src/server/render.ts +31 -59
- package/src/server/requestScope.ts +5 -36
- package/src/server/runCompiled.ts +1 -1
- package/src/server/runDesktop.ts +20 -448
- package/src/server/seedBuffer.ts +12 -12
- package/src/server/serveDev.ts +24 -58
- package/src/server/servedApp.ts +390 -0
- package/src/server/shell.ts +1 -1
- package/src/server/signInChanged.ts +20 -0
- package/src/server/surfaceOf.ts +3 -2
- package/src/server/versionOf.ts +7 -16
- package/src/shared/RPC_SERVER.ts +27 -31
- package/src/shared/Schema.ts +1 -2
- package/src/shared/applyPatch.ts +1 -1
- package/src/shared/index.ts +1 -1
- package/src/shared/{note.ts → issueReport.ts} +22 -0
- package/src/shared/loadCall.ts +66 -0
- package/src/shared/muxFrames.ts +1 -1
- package/src/shared/reactive/Keyed.ts +24 -16
- package/src/shared/reactive/Reactive.ts +37 -991
- package/src/shared/reactive/argsContract.ts +131 -4
- package/src/shared/reactive/byOf.ts +2 -5
- package/src/shared/reactive/callLoad.ts +8 -14
- package/src/shared/reactive/cellShape.ts +734 -0
- package/src/shared/reactive/index.ts +12 -23
- package/src/shared/reactive/pairReactive.ts +5 -3
- package/src/shared/reactive/patchLog.ts +79 -0
- package/src/shared/reactive/patchMarks.ts +34 -0
- package/src/shared/reactive/producer.ts +121 -0
- package/src/shared/reactive/recordWrites.ts +11 -14
- package/src/shared/reactive/scoped.ts +3 -2
- package/src/shared/reactive/selections.ts +209 -0
- package/src/shared/reactive/share.ts +4 -3
- package/src/shared/reactive/watch.ts +3 -4
- package/src/shared/rpc.ts +20 -24
- package/src/shared/validateJson.ts +1 -2
- package/src/shared/writePath.ts +29 -0
- package/src/ui/ArmRegion.ts +10 -4
- package/src/ui/Level.ts +11 -20
- package/src/ui/LevelChain.ts +272 -0
- package/src/ui/Router.ts +106 -313
- package/src/ui/Scope.ts +17 -3
- package/src/ui/TryRegion.ts +22 -23
- package/src/ui/bindSpread.ts +2 -4
- package/src/ui/listenForRoutes.ts +3 -10
- package/src/ui/outboxQueue.ts +23 -11
- package/src/ui/outcomeOf.ts +120 -0
- package/src/ui/reconcile.ts +4 -6
- package/src/ui/routing.ts +2 -1
- package/src/ui/rows.ts +2 -2
- package/src/ui/setAttribute.ts +12 -3
- package/src/ui/socketWire.ts +28 -13
- package/src/ui/wire.ts +9 -10
- package/src/compiler/Finding.ts +0 -2
- package/src/compiler/Refusals.ts +0 -65
- package/src/compiler/formatDiagnosis.ts +0 -20
- package/src/server/appDirectories.ts +0 -9
- package/src/server/argsFor.ts +0 -39
- package/src/server/buildDirectory.ts +0 -10
- package/src/server/coerceArgs.ts +0 -109
- package/src/server/derivedArgs.ts +0 -26
- package/src/server/runBuild.ts +0 -24
- package/src/server/serveBuild.ts +0 -144
- package/src/server/testBuild.ts +0 -220
- package/src/shared/LOAD_OFFLINE.ts +0 -15
- package/src/shared/LOAD_SIGNAL.ts +0 -10
- package/src/shared/issuesOf.ts +0 -18
- package/src/shared/reactive/ENTRY_LIVES.ts +0 -7
- package/src/shared/reactive/FOLLOWED.ts +0 -8
- package/src/shared/reactive/PATCHES.ts +0 -11
- package/src/shared/reactive/PATCH_SINKS.ts +0 -26
- package/src/shared/reactive/canonicalKey.ts +0 -95
package/src/compiler/readsIn.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
isBinaryExpression,
|
|
8
8
|
isCallExpression,
|
|
9
9
|
isClassDeclaration,
|
|
10
|
+
isElementAccessExpression,
|
|
10
11
|
isEnumDeclaration,
|
|
11
12
|
isExpressionStatement,
|
|
12
13
|
isForOfStatement,
|
|
@@ -32,9 +33,11 @@ import {
|
|
|
32
33
|
SymbolFlags,
|
|
33
34
|
type Type,
|
|
34
35
|
TypeFlags,
|
|
36
|
+
type TypeReference,
|
|
35
37
|
} from 'typescript/unstable/async'
|
|
36
38
|
|
|
37
39
|
import { aliasedSymbol } from './aliasedSymbol.ts'
|
|
40
|
+
import { ARRAY_MUTATORS } from './ARRAY_MUTATORS.ts'
|
|
38
41
|
import { CheckerAnswer } from './CheckerAnswer.ts'
|
|
39
42
|
import {
|
|
40
43
|
classifyPropRead,
|
|
@@ -45,7 +48,7 @@ import {
|
|
|
45
48
|
type Read,
|
|
46
49
|
TRANSPARENT,
|
|
47
50
|
} from './classifyRead.ts'
|
|
48
|
-
import type { Finding } from './
|
|
51
|
+
import type { Finding } from './Diagnostics.ts'
|
|
49
52
|
import type { AbideFile } from './parseAbide.ts'
|
|
50
53
|
import {
|
|
51
54
|
holdsReactive,
|
|
@@ -53,12 +56,13 @@ import {
|
|
|
53
56
|
type PositionKind,
|
|
54
57
|
type PositionTable,
|
|
55
58
|
} from './PositionTable.ts'
|
|
56
|
-
import { INLINE_PROPS, type Span } from './projectShadow.ts'
|
|
59
|
+
import { INLINE_PROPS, type Projection, type Span } from './projectShadow.ts'
|
|
57
60
|
import { REACTIVE_MEMBERS } from './REACTIVE_MEMBERS.ts'
|
|
58
61
|
import { optionNamed, reactiveOptions } from './reactiveOptions.ts'
|
|
59
62
|
import {
|
|
60
63
|
type Abide,
|
|
61
64
|
boundInitializer,
|
|
65
|
+
isReactiveSymbol,
|
|
62
66
|
isReactiveType,
|
|
63
67
|
unionMembers,
|
|
64
68
|
valueTypeOf,
|
|
@@ -66,19 +70,6 @@ import {
|
|
|
66
70
|
import { sharedModuleWrites } from './sharedModuleWrites.ts'
|
|
67
71
|
import { trackingWarnings } from './trackingWarnings.ts'
|
|
68
72
|
|
|
69
|
-
/** Where in a `.abide`'s shadow each kind of question is asked (`projectShadow`). */
|
|
70
|
-
export type ShadowSpans = {
|
|
71
|
-
/** Where the shadow's scripts end and its template begins. */
|
|
72
|
-
scriptsEnd: number
|
|
73
|
-
/** The `<script module>`, `<script server>` and `<script>` bodies, in the shadow. */
|
|
74
|
-
modules: Span
|
|
75
|
-
servers: Span
|
|
76
|
-
scripts: Span
|
|
77
|
-
positions: PositionTable
|
|
78
|
-
/** 32.67 — where each `{#for}` row's item binding starts. */
|
|
79
|
-
items: Set<number>
|
|
80
|
-
}
|
|
81
|
-
|
|
82
73
|
const SETUP: ReadonlySet<PositionKind> = new Set(['setup'])
|
|
83
74
|
const HEAD: ReadonlySet<PositionKind> = new Set(['head'])
|
|
84
75
|
/**
|
|
@@ -121,19 +112,22 @@ const RAW_LOWERED: ReadonlySet<PositionKind> = new Set([...PROVABLE, 'prop'])
|
|
|
121
112
|
* absent. What each reference DOES — a read, a write, an update, a shorthand — is read off its
|
|
122
113
|
* parents by `classifyRead`, so no token walk re-derives the grammar.
|
|
123
114
|
*
|
|
124
|
-
* Asked of one snapshot (`
|
|
125
|
-
*
|
|
115
|
+
* Asked of one snapshot (`reactiveReads`), and every offset — a read's and a warning's — is the
|
|
116
|
+
* shadow's; `reactiveReads` places them in the `.abide`. `suspects` are where each binding starts
|
|
117
|
+
* whose type the projection cannot vouch for (`bindingsOfReads`), for the next round to ask.
|
|
126
118
|
*/
|
|
127
119
|
export async function readsIn(
|
|
128
120
|
checker: Checker,
|
|
129
121
|
abide: Abide,
|
|
130
122
|
sourceFile: SourceFile,
|
|
131
123
|
shadowPath: string,
|
|
132
|
-
|
|
124
|
+
projection: Projection,
|
|
133
125
|
file: AbideFile,
|
|
134
126
|
/** Where each name the lowered program proved a `Reactive` starts (`bindingsOfReads`). */
|
|
135
127
|
held: ReadonlySet<number>,
|
|
136
128
|
): Promise<{ answer: CheckerAnswer; warnings: Finding[]; suspects: number[] }> {
|
|
129
|
+
const scriptsEnd = projection.scripts.end
|
|
130
|
+
const positions = projection.positions
|
|
137
131
|
// A destructuring pattern binds names this does not see, and that is 31.12's case.
|
|
138
132
|
const names: Node[] = []
|
|
139
133
|
const spelled = new Set<string>()
|
|
@@ -143,7 +137,7 @@ export async function readsIn(
|
|
|
143
137
|
names.push(name)
|
|
144
138
|
}
|
|
145
139
|
for (const statement of sourceFile.statements) {
|
|
146
|
-
if (statement.getStart() >=
|
|
140
|
+
if (statement.getStart() >= scriptsEnd) break
|
|
147
141
|
if (isVariableStatement(statement)) {
|
|
148
142
|
for (const declaration of statement.declarationList.declarations)
|
|
149
143
|
bind(declaration.name)
|
|
@@ -172,12 +166,11 @@ export async function readsIn(
|
|
|
172
166
|
const topLevel = names.length
|
|
173
167
|
// The script's own statements, as the top-level walk takes the page's: a declaration inside
|
|
174
168
|
// one of its functions is not a name the body reads.
|
|
175
|
-
for (const { node } of
|
|
169
|
+
for (const { node } of positions.nodesIn(sourceFile, SETUP))
|
|
176
170
|
if (isVariableStatement(node))
|
|
177
171
|
for (const declaration of node.declarationList.declarations)
|
|
178
172
|
if (isIdentifier(declaration.name)) names.push(declaration.name)
|
|
179
|
-
refuseDeclared(sourceFile.statements,
|
|
180
|
-
const reads: Read[] = []
|
|
173
|
+
refuseDeclared(sourceFile.statements, scriptsEnd, file)
|
|
181
174
|
// 33's pass runs whatever 31.3's found: a `props` pattern binds no name the walk
|
|
182
175
|
// above collects — `const { amount } = props()` declares a PATTERN — so a file
|
|
183
176
|
// whose only declaration is one would have returned with its props unread.
|
|
@@ -186,22 +179,21 @@ export async function readsIn(
|
|
|
186
179
|
abide,
|
|
187
180
|
sourceFile,
|
|
188
181
|
shadowPath,
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
reads,
|
|
182
|
+
scriptsEnd,
|
|
183
|
+
positions,
|
|
192
184
|
)
|
|
193
185
|
// Awaited after the round trips below, so a 33.14 refusal would reject with nothing
|
|
194
186
|
// listening and be reported unhandled; `await props` still throws it.
|
|
195
187
|
props.catch(() => {})
|
|
196
|
-
const params = routeReads(checker, abide, sourceFile
|
|
197
|
-
const rows = rowReads(
|
|
198
|
-
const configs = configReads(
|
|
188
|
+
const params = routeReads(checker, abide, sourceFile)
|
|
189
|
+
const rows = rowReads(
|
|
199
190
|
checker,
|
|
200
|
-
abide,
|
|
201
191
|
sourceFile,
|
|
202
|
-
|
|
203
|
-
|
|
192
|
+
shadowPath,
|
|
193
|
+
positions,
|
|
194
|
+
new Set(projection.items.values()),
|
|
204
195
|
)
|
|
196
|
+
const configs = configReads(checker, abide, sourceFile, projection.servers)
|
|
205
197
|
// No name is no round trip to ask about none.
|
|
206
198
|
const symbols =
|
|
207
199
|
names.length === 0 ? [] : await checker.getSymbolAtLocation(names)
|
|
@@ -249,30 +241,34 @@ export async function readsIn(
|
|
|
249
241
|
const owned = await ownMembers(checker, nodes.flat(), (node) =>
|
|
250
242
|
valueTypeOf(checker, node),
|
|
251
243
|
)
|
|
244
|
+
const lifted = await liftedMutators(checker, nodes.flat(), (node) =>
|
|
245
|
+
valueTypeOf(checker, node),
|
|
246
|
+
)
|
|
252
247
|
const holding: Promise<void>[] = []
|
|
253
248
|
const located: { node: Node; read: Read }[] = []
|
|
249
|
+
const named: Read[] = []
|
|
254
250
|
for (let index = 0; index < nodes.length; index += 1)
|
|
255
251
|
for (const node of nodes[index] as (Node | undefined)[]) {
|
|
256
252
|
if (node === undefined) continue
|
|
257
|
-
const read = classifyRead(node, owned.has(node))
|
|
253
|
+
const read = classifyRead(node, owned.has(node), lifted.has(node))
|
|
258
254
|
if (read === undefined) continue
|
|
259
255
|
read.byValue = byValue[index] as boolean
|
|
260
256
|
read.local = index >= topLevel
|
|
261
|
-
|
|
257
|
+
named.push(read)
|
|
262
258
|
located.push({ node, read })
|
|
263
259
|
if (read.shape === 'value')
|
|
264
260
|
holding.push(holdIn(checker, abide, node, read))
|
|
265
261
|
}
|
|
266
262
|
await Promise.all(holding)
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
263
|
+
const reads = [
|
|
264
|
+
...(await props),
|
|
265
|
+
...(await params),
|
|
266
|
+
...(await rows),
|
|
267
|
+
...(await configs),
|
|
268
|
+
...named,
|
|
269
|
+
]
|
|
271
270
|
const statics = new Set<number>()
|
|
272
|
-
const expressions =
|
|
273
|
-
sourceFile,
|
|
274
|
-
TEMPLATE_EXPRESSIONS,
|
|
275
|
-
)
|
|
271
|
+
const expressions = positions.nodesIn(sourceFile, TEMPLATE_EXPRESSIONS)
|
|
276
272
|
const rawCallees: Node[] = []
|
|
277
273
|
for (const { position, node } of expressions)
|
|
278
274
|
expressionFacts(
|
|
@@ -281,15 +277,15 @@ export async function readsIn(
|
|
|
281
277
|
)
|
|
282
278
|
// A `Reactive` the page named `raw` is a read, and the read's rewrite is the answer.
|
|
283
279
|
if (rawCallees.length > 0) {
|
|
284
|
-
const
|
|
285
|
-
for (const one of reads)
|
|
280
|
+
const starts = new Set<number>()
|
|
281
|
+
for (const one of reads) starts.add(one.start)
|
|
286
282
|
for (const callee of rawCallees)
|
|
287
|
-
if (!
|
|
283
|
+
if (!starts.has(callee.getStart()))
|
|
288
284
|
reads.push(newRead(callee.getStart(), callee.end, 'raw'))
|
|
289
285
|
}
|
|
290
286
|
await proveSlots(checker, expressions, reads, statics)
|
|
291
287
|
reads.sort(byPosition)
|
|
292
|
-
const warnings = await warningsFor(checker, located,
|
|
288
|
+
const warnings = await warningsFor(checker, located, positions)
|
|
293
289
|
// 34.18.
|
|
294
290
|
for (const warning of await sharedModuleWrites(
|
|
295
291
|
checker,
|
|
@@ -297,8 +293,8 @@ export async function readsIn(
|
|
|
297
293
|
names.slice(0, topLevel),
|
|
298
294
|
symbols,
|
|
299
295
|
reactive,
|
|
300
|
-
|
|
301
|
-
|
|
296
|
+
projection.modules,
|
|
297
|
+
projection.scripts,
|
|
302
298
|
))
|
|
303
299
|
warnings.push(warning)
|
|
304
300
|
const answer = new CheckerAnswer(reads, statics)
|
|
@@ -317,7 +313,7 @@ export async function readsIn(
|
|
|
317
313
|
|
|
318
314
|
/**
|
|
319
315
|
* Where each binding starts whose type the projection cannot vouch for, for the lowered program to
|
|
320
|
-
* answer (`
|
|
316
|
+
* answer (`reactiveReads`).
|
|
321
317
|
*
|
|
322
318
|
* 31.1 — A BINDING TYPED FROM A READ IS TYPED FROM THE `Reactive` IN THE PROJECTION, and the read is
|
|
323
319
|
* of its value. `reactive(route.url.pathname)` asks a `Reactive<URL>` for a `pathname` it does not
|
|
@@ -549,7 +545,7 @@ function annotated(
|
|
|
549
545
|
async function proveSlots(
|
|
550
546
|
checker: Checker,
|
|
551
547
|
expressions: readonly { position: Position; node: Node }[],
|
|
552
|
-
reads: Read[],
|
|
548
|
+
reads: readonly Read[],
|
|
553
549
|
/** Where each position it proves static starts, added to. */
|
|
554
550
|
statics: Set<number>,
|
|
555
551
|
): Promise<void> {
|
|
@@ -761,9 +757,9 @@ async function routeReads(
|
|
|
761
757
|
checker: Checker,
|
|
762
758
|
abide: Abide,
|
|
763
759
|
sourceFile: Node,
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
if (abide.route === undefined) return
|
|
760
|
+
): Promise<Read[]> {
|
|
761
|
+
const reads: Read[] = []
|
|
762
|
+
if (abide.route === undefined) return reads
|
|
767
763
|
const accesses: Node[] = []
|
|
768
764
|
const owners: Node[] = []
|
|
769
765
|
const visit = (node: Node): void => {
|
|
@@ -779,7 +775,7 @@ async function routeReads(
|
|
|
779
775
|
node.forEachChild(visit)
|
|
780
776
|
}
|
|
781
777
|
sourceFile.forEachChild(visit)
|
|
782
|
-
if (owners.length === 0) return
|
|
778
|
+
if (owners.length === 0) return reads
|
|
783
779
|
const symbols = await checker.getSymbolAtLocation(owners)
|
|
784
780
|
for (let at = 0; at < accesses.length; at += 1) {
|
|
785
781
|
const symbol = symbols[at]
|
|
@@ -794,6 +790,7 @@ async function routeReads(
|
|
|
794
790
|
(access as { name?: { text: string } }).name?.text === 'params'
|
|
795
791
|
reads.push(read)
|
|
796
792
|
}
|
|
793
|
+
return reads
|
|
797
794
|
}
|
|
798
795
|
|
|
799
796
|
/**
|
|
@@ -810,10 +807,12 @@ async function rowReads(
|
|
|
810
807
|
checker: Checker,
|
|
811
808
|
sourceFile: Node,
|
|
812
809
|
shadowPath: string,
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
810
|
+
positions: PositionTable,
|
|
811
|
+
/** Where each `{#for}` row's item binding starts. */
|
|
812
|
+
items: Set<number>,
|
|
813
|
+
): Promise<Read[]> {
|
|
814
|
+
const reads: Read[] = []
|
|
815
|
+
if (items.size === 0) return reads
|
|
817
816
|
const bound: Node[] = []
|
|
818
817
|
const loops: Node[] = []
|
|
819
818
|
const bind = (name: Node | undefined, loop: Node): void => {
|
|
@@ -828,12 +827,12 @@ async function rowReads(
|
|
|
828
827
|
bind((element as { name?: Node }).name, loop)
|
|
829
828
|
}
|
|
830
829
|
const visit = (node: Node): void => {
|
|
831
|
-
const declared = rowItemOf(node,
|
|
830
|
+
const declared = rowItemOf(node, items)
|
|
832
831
|
if (declared !== undefined) bind(declared, node)
|
|
833
832
|
node.forEachChild(visit)
|
|
834
833
|
}
|
|
835
834
|
sourceFile.forEachChild(visit)
|
|
836
|
-
if (bound.length === 0) return
|
|
835
|
+
if (bound.length === 0) return reads
|
|
837
836
|
const symbols = await checker.getSymbolAtLocation(bound)
|
|
838
837
|
const found = await Promise.all(
|
|
839
838
|
symbols.map(async (symbol) =>
|
|
@@ -852,12 +851,12 @@ async function rowReads(
|
|
|
852
851
|
for (let at = 0; at < found.length; at += 1) {
|
|
853
852
|
const name = bound[at] as Node
|
|
854
853
|
const loop = loops[at] as Node
|
|
855
|
-
const item = (rowItemOf(loop,
|
|
854
|
+
const item = (rowItemOf(loop, items) as Node).getStart()
|
|
856
855
|
for (const node of found[at] as (Node | undefined)[]) {
|
|
857
856
|
if (node === undefined || node === name) continue
|
|
858
857
|
if (
|
|
859
|
-
|
|
860
|
-
rowLoopOf(node,
|
|
858
|
+
positions.holding(node.getStart())?.kind === 'key' &&
|
|
859
|
+
rowLoopOf(node, items) === loop
|
|
861
860
|
)
|
|
862
861
|
continue
|
|
863
862
|
const read = classifyPropRead(node)
|
|
@@ -866,6 +865,7 @@ async function rowReads(
|
|
|
866
865
|
reads.push(read)
|
|
867
866
|
}
|
|
868
867
|
}
|
|
868
|
+
return reads
|
|
869
869
|
}
|
|
870
870
|
|
|
871
871
|
/** The item binding `node` declares, where it is a `{#for}` row loop. */
|
|
@@ -933,7 +933,7 @@ function refuseDeclared(
|
|
|
933
933
|
|
|
934
934
|
/**
|
|
935
935
|
* 27.13 — each `onConfig(…)` standing as a statement at the top of a `<script server>`, as a read of
|
|
936
|
-
* its callee
|
|
936
|
+
* its callee.
|
|
937
937
|
*
|
|
938
938
|
* The callee says which are abide's, as `props`'s does. A call anywhere else — inside a function,
|
|
939
939
|
* or handing its unfiling back to a name — is filed all the same, and declares nothing.
|
|
@@ -943,9 +943,9 @@ async function configReads(
|
|
|
943
943
|
abide: Abide,
|
|
944
944
|
sourceFile: Node & { statements: readonly Node[] },
|
|
945
945
|
servers: Span,
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
if (abide.onConfig === undefined) return
|
|
946
|
+
): Promise<Read[]> {
|
|
947
|
+
const reads: Read[] = []
|
|
948
|
+
if (abide.onConfig === undefined) return reads
|
|
949
949
|
const callees: Node[] = []
|
|
950
950
|
for (const statement of sourceFile.statements) {
|
|
951
951
|
const start = statement.getStart()
|
|
@@ -958,7 +958,7 @@ async function configReads(
|
|
|
958
958
|
)
|
|
959
959
|
callees.push(statement.expression.expression)
|
|
960
960
|
}
|
|
961
|
-
if (callees.length === 0) return
|
|
961
|
+
if (callees.length === 0) return reads
|
|
962
962
|
const named = await checker.getSymbolAtLocation(callees)
|
|
963
963
|
for (let at = 0; at < callees.length; at += 1) {
|
|
964
964
|
const symbol = named[at]
|
|
@@ -972,10 +972,11 @@ async function configReads(
|
|
|
972
972
|
const node = callees[at] as Node
|
|
973
973
|
reads.push(newRead(node.getStart(), node.end, 'config'))
|
|
974
974
|
}
|
|
975
|
+
return reads
|
|
975
976
|
}
|
|
976
977
|
|
|
977
978
|
/**
|
|
978
|
-
* The reads a `props<Props>()` destructuring contributes
|
|
979
|
+
* The reads a `props<Props>()` destructuring contributes.
|
|
979
980
|
*
|
|
980
981
|
* 33 — WHAT `props<Props>()` BINDS, and it is a second question from 31.3's rather than a case of
|
|
981
982
|
* it: a prop is not a `Reactive` and is not typed as one, so no type answers this. The CALLEE
|
|
@@ -997,8 +998,8 @@ async function propsReads(
|
|
|
997
998
|
shadowPath: string,
|
|
998
999
|
scriptsEnd: number,
|
|
999
1000
|
positions: PositionTable,
|
|
1000
|
-
|
|
1001
|
-
|
|
1001
|
+
): Promise<Read[]> {
|
|
1002
|
+
const reads: Read[] = []
|
|
1002
1003
|
// The spelling only says which calls are worth asking about; the callee says which are abide's.
|
|
1003
1004
|
const calls: { pattern: Node; call: Node; local: boolean }[] = []
|
|
1004
1005
|
const callees: Node[] = []
|
|
@@ -1024,7 +1025,7 @@ async function propsReads(
|
|
|
1024
1025
|
// 32.40 — AND EACH INLINE COMPONENT'S HEAD, inside the function it is projected as. Its names
|
|
1025
1026
|
// are that function's, so a read of one is local as a branch-local name is (14.8).
|
|
1026
1027
|
for (const { node } of positions.nodesIn(sourceFile, HEAD)) take(node, true)
|
|
1027
|
-
if (calls.length === 0 || abide.props === undefined) return
|
|
1028
|
+
if (calls.length === 0 || abide.props === undefined) return reads
|
|
1028
1029
|
const named = await checker.getSymbolAtLocation(callees)
|
|
1029
1030
|
// One entry per name a pattern binds: the key it takes, the call's read its key joins where
|
|
1030
1031
|
// the child writes it, and its default's pair.
|
|
@@ -1091,7 +1092,7 @@ async function propsReads(
|
|
|
1091
1092
|
})
|
|
1092
1093
|
}
|
|
1093
1094
|
}
|
|
1094
|
-
if (bound.length === 0) return
|
|
1095
|
+
if (bound.length === 0) return reads
|
|
1095
1096
|
|
|
1096
1097
|
const symbols = await checker.getSymbolAtLocation(bound)
|
|
1097
1098
|
const found = await Promise.all(
|
|
@@ -1111,6 +1112,9 @@ async function propsReads(
|
|
|
1111
1112
|
const owned = await ownMembers(checker, found.flat(), (node) =>
|
|
1112
1113
|
checker.getTypeAtLocation(node),
|
|
1113
1114
|
)
|
|
1115
|
+
const lifted = await liftedMutators(checker, found.flat(), (node) =>
|
|
1116
|
+
checker.getTypeAtLocation(node),
|
|
1117
|
+
)
|
|
1114
1118
|
// 33.27 — each prop asked whether its position holds the `Reactive`, all at once.
|
|
1115
1119
|
const holding: Promise<void>[] = []
|
|
1116
1120
|
for (let at = 0; at < found.length; at += 1) {
|
|
@@ -1128,11 +1132,12 @@ async function propsReads(
|
|
|
1128
1132
|
node.parent?.kind === SyntaxKind.BindingElement
|
|
1129
1133
|
)
|
|
1130
1134
|
continue
|
|
1131
|
-
const read = classifyRead(node, owned.has(node))
|
|
1135
|
+
const read = classifyRead(node, owned.has(node), lifted.has(node))
|
|
1132
1136
|
if (read === undefined) continue
|
|
1133
1137
|
if (
|
|
1134
1138
|
read.shape === 'write' ||
|
|
1135
1139
|
read.shape === 'update' ||
|
|
1140
|
+
read.shape === 'mutate' ||
|
|
1136
1141
|
isBindOperand(positions, node) ||
|
|
1137
1142
|
writesThrough(node, owned)
|
|
1138
1143
|
)
|
|
@@ -1178,6 +1183,7 @@ async function propsReads(
|
|
|
1178
1183
|
}
|
|
1179
1184
|
}
|
|
1180
1185
|
await Promise.all(holding)
|
|
1186
|
+
return reads
|
|
1181
1187
|
}
|
|
1182
1188
|
|
|
1183
1189
|
/**
|
|
@@ -1232,6 +1238,108 @@ async function ownMembers(
|
|
|
1232
1238
|
return owned
|
|
1233
1239
|
}
|
|
1234
1240
|
|
|
1241
|
+
/**
|
|
1242
|
+
* 31.9 — the occurrences among `nodes` that call an array mutator through a member path, where the
|
|
1243
|
+
* receiver's type is an array: `tags.push(t)`, `deal.notes.splice(0, 1)`. The name says which
|
|
1244
|
+
* method and the type says whose, so a class's own `push` is left a call on the class.
|
|
1245
|
+
*
|
|
1246
|
+
* The receiver's type is walked down from the value's, step by step: in the projection a member of
|
|
1247
|
+
* the name is the `Reactive`'s, which declares none of the value's.
|
|
1248
|
+
*/
|
|
1249
|
+
async function liftedMutators(
|
|
1250
|
+
checker: Checker,
|
|
1251
|
+
nodes: readonly (Node | undefined)[],
|
|
1252
|
+
valueOf: (node: Node) => Promise<Type | undefined>,
|
|
1253
|
+
): Promise<Set<Node>> {
|
|
1254
|
+
const lifted = new Set<Node>()
|
|
1255
|
+
const asked: Promise<void>[] = []
|
|
1256
|
+
for (const node of nodes) {
|
|
1257
|
+
if (node === undefined) continue
|
|
1258
|
+
const steps: (string | undefined)[] = []
|
|
1259
|
+
let receiver: Node = node
|
|
1260
|
+
let mutator = false
|
|
1261
|
+
while (true) {
|
|
1262
|
+
const up = receiver.parent
|
|
1263
|
+
if (isPropertyAccessExpression(up) && up.expression === receiver) {
|
|
1264
|
+
if (
|
|
1265
|
+
ARRAY_MUTATORS.has(up.name.text) &&
|
|
1266
|
+
isCallExpression(up.parent) &&
|
|
1267
|
+
up.parent.expression === up
|
|
1268
|
+
) {
|
|
1269
|
+
mutator = true
|
|
1270
|
+
break
|
|
1271
|
+
}
|
|
1272
|
+
steps.push(up.name.text)
|
|
1273
|
+
} else if (
|
|
1274
|
+
isElementAccessExpression(up) &&
|
|
1275
|
+
up.expression === receiver
|
|
1276
|
+
)
|
|
1277
|
+
steps.push(undefined)
|
|
1278
|
+
else break
|
|
1279
|
+
receiver = up
|
|
1280
|
+
}
|
|
1281
|
+
if (!mutator) continue
|
|
1282
|
+
asked.push(
|
|
1283
|
+
valueOf(node).then(async (value) => {
|
|
1284
|
+
if (value === undefined) return
|
|
1285
|
+
let types: Type[] = [value]
|
|
1286
|
+
for (const step of steps)
|
|
1287
|
+
types = await stepTypes(checker, types, step)
|
|
1288
|
+
if (types.length === 0) return
|
|
1289
|
+
for (const type of types)
|
|
1290
|
+
if (
|
|
1291
|
+
!(await checker.isArrayType(type)) &&
|
|
1292
|
+
!(await checker.isTupleType(type))
|
|
1293
|
+
)
|
|
1294
|
+
return
|
|
1295
|
+
lifted.add(node)
|
|
1296
|
+
}),
|
|
1297
|
+
)
|
|
1298
|
+
}
|
|
1299
|
+
await Promise.all(asked)
|
|
1300
|
+
return lifted
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
/**
|
|
1304
|
+
* The types one step down from `types`, `null` and `undefined` aside: a property's by its name, and
|
|
1305
|
+
* an element's or an index signature's where the step is computed.
|
|
1306
|
+
*/
|
|
1307
|
+
async function stepTypes(
|
|
1308
|
+
checker: Checker,
|
|
1309
|
+
types: readonly Type[],
|
|
1310
|
+
step: string | undefined,
|
|
1311
|
+
): Promise<Type[]> {
|
|
1312
|
+
const next: Type[] = []
|
|
1313
|
+
for (const type of types)
|
|
1314
|
+
for (const held of await unionMembers(type)) {
|
|
1315
|
+
if ((held.flags & NULLISH) !== 0) continue
|
|
1316
|
+
if (step !== undefined) {
|
|
1317
|
+
const member = await checker.getPropertyOfType(held, step)
|
|
1318
|
+
const stepped =
|
|
1319
|
+
member === undefined
|
|
1320
|
+
? undefined
|
|
1321
|
+
: await checker.getTypeOfSymbol(member)
|
|
1322
|
+
if (stepped !== undefined)
|
|
1323
|
+
next.push(...(await unionMembers(stepped)))
|
|
1324
|
+
continue
|
|
1325
|
+
}
|
|
1326
|
+
if (await checker.isArrayType(held)) {
|
|
1327
|
+
const [element] = await checker.getTypeArguments(
|
|
1328
|
+
held as TypeReference,
|
|
1329
|
+
)
|
|
1330
|
+
if (element !== undefined)
|
|
1331
|
+
next.push(...(await unionMembers(element)))
|
|
1332
|
+
continue
|
|
1333
|
+
}
|
|
1334
|
+
for (const index of await checker.getIndexInfosOfType(held))
|
|
1335
|
+
next.push(...(await unionMembers(index.valueType)))
|
|
1336
|
+
}
|
|
1337
|
+
const present: Type[] = []
|
|
1338
|
+
for (const type of next)
|
|
1339
|
+
if ((type.flags & NULLISH) === 0) present.push(type)
|
|
1340
|
+
return present
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1235
1343
|
/**
|
|
1236
1344
|
* 31.7, 33.6, 33.7 — `n.set(v)` and `n.patch(…)` are writes by name, called or handed on, where the
|
|
1237
1345
|
* value's own type declares no member of that name — a `Map`'s `set` writes the map, not the prop.
|
|
@@ -1323,15 +1431,6 @@ function isBindOperand(positions: PositionTable, node: Node): boolean {
|
|
|
1323
1431
|
return holdsReactive(positions.of(node))
|
|
1324
1432
|
}
|
|
1325
1433
|
|
|
1326
|
-
async function isReactiveSymbol(
|
|
1327
|
-
checker: Checker,
|
|
1328
|
-
abide: Abide,
|
|
1329
|
-
symbol: CheckedSymbol,
|
|
1330
|
-
): Promise<boolean> {
|
|
1331
|
-
const type = await checker.getTypeOfSymbol(symbol)
|
|
1332
|
-
return type !== undefined && isReactiveType(checker, abide, type, false)
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
1434
|
/**
|
|
1336
1435
|
* Orders reads by offset, a default's open before a read at the same offset and its close after.
|
|
1337
1436
|
*
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { parseRoutePattern } from '#shared/routePattern.ts'
|
|
2
|
+
|
|
3
|
+
import type { AppRoute } from './appLayout.ts'
|
|
4
|
+
|
|
5
|
+
const URL_SOURCE = Bun.fileURLToPath(new URL('../shared/url', import.meta.url))
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 30.26 — each of `addresses` as a key of abide's `Routes`, and 23.9, 30.3 — a page route's params
|
|
9
|
+
* narrowed where its matcher is an alternation of literals, which is the one matcher a type can say.
|
|
10
|
+
*/
|
|
11
|
+
export function routesDeclaration(
|
|
12
|
+
addresses: readonly string[],
|
|
13
|
+
routes: readonly AppRoute[],
|
|
14
|
+
): string {
|
|
15
|
+
const narrowed = new Map<string, string>()
|
|
16
|
+
for (const route of routes) {
|
|
17
|
+
let members = ''
|
|
18
|
+
for (const segment of parseRoutePattern(route.pattern)) {
|
|
19
|
+
if (segment.kind !== 'required' && segment.kind !== 'optional')
|
|
20
|
+
continue
|
|
21
|
+
const literals = literalsOf(
|
|
22
|
+
segment.matcher === undefined
|
|
23
|
+
? undefined
|
|
24
|
+
: route.matchers[segment.matcher],
|
|
25
|
+
)
|
|
26
|
+
if (literals === undefined) continue
|
|
27
|
+
members += ` ${JSON.stringify(segment.name)}${segment.kind === 'optional' ? '?' : ''}: ${literals};`
|
|
28
|
+
}
|
|
29
|
+
if (members !== '') narrowed.set(route.pattern, `{${members} }`)
|
|
30
|
+
}
|
|
31
|
+
let keys = ''
|
|
32
|
+
for (const route of addresses)
|
|
33
|
+
keys += ` ${JSON.stringify(route)}: ${narrowed.get(route) ?? 'true'}\n`
|
|
34
|
+
return `export {}\ndeclare module ${JSON.stringify(URL_SOURCE)} {\n interface Routes {\n${keys} }\n}\n`
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** The union of the literals an anchored matcher source alternates between, or `undefined`. */
|
|
38
|
+
function literalsOf(source: string | undefined): string | undefined {
|
|
39
|
+
const body = source?.match(/^\^\(\?!\\\.\\\.\?\$\)\(\?:(.*)\)\$$/)?.[1]
|
|
40
|
+
if (body === undefined || !/^[\w~-]+(?:\|[\w~-]+)*$/.test(body))
|
|
41
|
+
return undefined
|
|
42
|
+
return body
|
|
43
|
+
.split('|')
|
|
44
|
+
.map((literal) => JSON.stringify(literal))
|
|
45
|
+
.join(' | ')
|
|
46
|
+
}
|
package/src/compiler/scaffold.ts
CHANGED
|
@@ -40,14 +40,19 @@ const SOURCE_ALIAS = { '#src/*': ['./src/*', './src/*.ts'] }
|
|
|
40
40
|
* lands first on the PATH inside `bun run`, shadowing the bun that runs everything else.
|
|
41
41
|
*/
|
|
42
42
|
export const BUNFIG =
|
|
43
|
-
'# 38.122 —
|
|
43
|
+
'# 38.122 — no peers: an installed `bun` would shadow this one in `bun run`.\n[install]\npeer = false\n\n# 38.43, 38.131 — the plugin building the stylesheet `src/lib/app.css` opens.\n[serve.static]\nplugins = ["bun-plugin-tailwind"]\n\n# 38.109, 38.119 — a DOM to measure in, then the `.abide` files as modules.\n[test]\nroot = "src"\npreload = ["@abide/harness/abide/preload"]\n\n# What `bun fmt` and `abide format` print.\n[fmt]\ntabWidth = 4\nsemi = false\nsingleQuote = true\nsortImports = true\n'
|
|
44
44
|
|
|
45
45
|
// WRITTEN, NOT COPIED, beside the manifests: a packer drops a `bunfig.toml` or a `.gitignore` from
|
|
46
|
-
// the tarball (`bun pm pack` both, `npm pack` the second), so neither survives as a starter file
|
|
46
|
+
// the tarball (`bun pm pack` both, `npm pack` the second), so neither survives as a starter file —
|
|
47
|
+
// and the Dockerfile names the bun that is running, which no file can.
|
|
47
48
|
function filesFor(name: string): Record<string, string> {
|
|
48
49
|
return {
|
|
49
50
|
'bunfig.toml': BUNFIG,
|
|
50
51
|
'.gitignore': 'node_modules\n.abide\n',
|
|
52
|
+
// 38.132 — the build stage on the bun that scaffolded the project, the image on nothing but
|
|
53
|
+
// the executable it compiles.
|
|
54
|
+
Dockerfile: `FROM oven/bun:${Bun.version} AS build\nWORKDIR /app\nCOPY package.json bun.lock bunfig.toml ./\nRUN bun install --frozen-lockfile\nCOPY . .\nRUN bun x abide compile --out /app/server\n\nFROM gcr.io/distroless/cc-debian12\nCOPY --from=build /app/server /server\nENV NODE_ENV=production\nEXPOSE 3000\nCMD ["/server", "start"]\n`,
|
|
55
|
+
'.dockerignore': 'node_modules\n.abide\n.git\n.env\n.env.*\n',
|
|
51
56
|
'package.json': `${JSON.stringify(
|
|
52
57
|
{
|
|
53
58
|
name,
|