@abide/abide 0.123.3 → 0.124.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/ABIDE.md +8 -5
- package/CHANGELOG.md +38 -0
- package/docs/REGISTRY.md +45 -12
- package/docs/RULEBOOK.md +29 -16
- package/docs/abide.md +999 -459
- package/package.json +1 -1
- package/src/compiler/ARRAY_MUTATORS.ts +20 -0
- package/src/compiler/LoweredFile.ts +609 -0
- package/src/compiler/Lowering.ts +46 -4
- 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 +71 -1715
- package/src/compiler/appLints.ts +109 -0
- package/src/compiler/checkedShadow.ts +1 -1
- package/src/compiler/classifyRead.ts +16 -1
- package/src/compiler/compileApp.ts +2 -2
- package/src/compiler/declaredExports.ts +44 -17
- package/src/compiler/diagnoseSources.ts +232 -0
- package/src/compiler/formatAbide.ts +5 -6
- package/src/compiler/handlersUnder.ts +16 -35
- package/src/compiler/projectShadow.ts +1 -0
- package/src/compiler/reactiveReads.ts +235 -0
- package/src/compiler/reactiveTypes.ts +13 -0
- package/src/compiler/readsIn.ts +180 -81
- package/src/compiler/routesDeclaration.ts +46 -0
- package/src/compiler/scaffold.ts +7 -2
- package/src/compiler/serveLanguage.ts +26 -20
- package/src/compiler/splitImports.ts +2 -2
- package/src/server/BRIDGE_VERSION.ts +1 -1
- 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/abideModules.ts +7 -7
- package/src/server/answerFailure.ts +3 -2
- package/src/server/answerRequest.ts +6 -70
- package/src/server/answerRpc.ts +48 -260
- package/src/server/answerSocket.ts +41 -463
- package/src/server/appKit.ts +1 -1
- package/src/server/argsContract.ts +1 -1
- package/src/server/createApp.ts +8 -2
- package/src/server/csp.ts +1 -1
- package/src/server/deferredBuilds.ts +3 -16
- package/src/server/faceOf.ts +1 -1
- package/src/server/generatedSurfaces.ts +2 -1
- package/src/server/handlerFileMount.ts +1 -1
- package/src/server/index.ts +1 -1
- 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/preload.ts +18 -0
- package/src/server/principal.ts +1 -1
- package/src/server/remote.ts +1 -1
- package/src/server/render.ts +31 -59
- package/src/server/requestScope.ts +5 -36
- package/src/server/runDesktop.ts +19 -447
- package/src/server/seedBuffer.ts +11 -11
- package/src/server/servedApp.ts +1 -1
- package/src/server/shell.ts +1 -1
- package/src/server/surfaceOf.ts +1 -1
- package/src/server/versionOf.ts +1 -1
- package/src/shared/RPC_SERVER.ts +27 -31
- package/src/shared/Schema.ts +1 -2
- 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/reactive/Keyed.ts +23 -15
- package/src/shared/reactive/Reactive.ts +46 -1002
- 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 -24
- package/src/shared/reactive/pairReactive.ts +5 -3
- package/src/shared/reactive/producer.ts +121 -0
- package/src/shared/reactive/scoped.ts +3 -2
- package/src/shared/reactive/selections.ts +209 -0
- package/src/shared/reactive/watch.ts +3 -4
- package/src/shared/rpc.ts +8 -5
- package/src/shared/validateJson.ts +1 -2
- package/src/shared/writePath.ts +29 -0
- package/src/ui/Router.ts +53 -69
- package/src/ui/outboxQueue.ts +2 -8
- package/src/ui/outcomeOf.ts +120 -0
- package/src/ui/socketWire.ts +3 -3
- package/src/ui/wire.ts +9 -10
- 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
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { getTouchingPropertyName } from 'typescript/unstable/ast'
|
|
2
|
+
import type { Project } from 'typescript/unstable/async'
|
|
3
|
+
|
|
4
|
+
import { checkedShadow } from './checkedShadow.ts'
|
|
5
|
+
import { type CheckerAnswer, UNCHECKED } from './CheckerAnswer.ts'
|
|
6
|
+
import { byPlace, type Diagnosis, placedFindings } from './Diagnostics.ts'
|
|
7
|
+
import { type Projection, projectShadow } from './projectShadow.ts'
|
|
8
|
+
import { type Abide, abideIn, isReactiveSymbol } from './reactiveTypes.ts'
|
|
9
|
+
import { readsIn } from './readsIn.ts'
|
|
10
|
+
import type { ParsedSource } from './readSources.ts'
|
|
11
|
+
import type { ShadowEdit, ShadowHost } from './ShadowHost.ts'
|
|
12
|
+
import { mapForward, ShadowMap } from './ShadowMap.ts'
|
|
13
|
+
import { templateWarnings } from './templateWarnings.ts'
|
|
14
|
+
|
|
15
|
+
/** Whether a script can bind a name at all — a file that cannot is never handed to the checker. */
|
|
16
|
+
const DECLARES = /\b(?:const|let|var|import)\b/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Reads and compiler warnings for every file, in one checker snapshot.
|
|
20
|
+
*
|
|
21
|
+
* 31.3 — THE READS IN EACH FILE: every occurrence of a name bound to a `Reactive`, classified, in
|
|
22
|
+
* its projection (`projectShadow`). Everything else is left exactly as written. What each file's
|
|
23
|
+
* shadow is asked is `readsIn`'s; the rounds a binding typed from a read takes are this module's.
|
|
24
|
+
*
|
|
25
|
+
* EVERY FILE IN ONE SNAPSHOT, which is the whole of the compiler's build time. Measured at
|
|
26
|
+
* 19.7 ms a file taking a snapshot each against 0.8 ms a file sharing one — 25x, because
|
|
27
|
+
* adding a file to the project's root set is what costs, not asking about it.
|
|
28
|
+
*
|
|
29
|
+
* WHAT IT HAS TO SAY THAT IS NOT A TYPE ERROR comes back beside the reads: the compiler's own
|
|
30
|
+
* warnings (14.14 to 14.17, 32.14), each on the `.abide` line where the source's text
|
|
31
|
+
* was handed in.
|
|
32
|
+
*/
|
|
33
|
+
export async function reactiveReads(
|
|
34
|
+
host: ShadowHost,
|
|
35
|
+
sources: readonly (Pick<ParsedSource, 'path' | 'file'> & {
|
|
36
|
+
text?: string
|
|
37
|
+
})[],
|
|
38
|
+
): Promise<{ answers: CheckerAnswer[]; warnings: Diagnosis[] }> {
|
|
39
|
+
// Per source, its shadow and what is asked of it, or `undefined` where nothing is declared.
|
|
40
|
+
const queries: ({ map: ShadowMap; projection: Projection } | undefined)[] =
|
|
41
|
+
[]
|
|
42
|
+
const edit: ShadowEdit = { created: [], changed: [] }
|
|
43
|
+
|
|
44
|
+
// EVERY SOURCE PROJECTS BEFORE ANY SHADOW IS REGISTERED. A projection refuses (a top-level
|
|
45
|
+
// `await`), and a shadow registered ahead of the refusal was in the host with the edit that
|
|
46
|
+
// would have reported it dropped — so the retry found nothing moved, and the checker answered
|
|
47
|
+
// from the old shadow at the new one's offsets, or never had the file at all.
|
|
48
|
+
const projected = sources.map((source) => ({
|
|
49
|
+
source,
|
|
50
|
+
projection: projectShadow(source.file),
|
|
51
|
+
}))
|
|
52
|
+
for (const { source, projection } of projected) {
|
|
53
|
+
// 34.2 — A `<script module>` DECLARES NAMES TOO, and the `<script>` under it is inside
|
|
54
|
+
// that scope on both sides. The projection holds both from offset 0.
|
|
55
|
+
const scripts = projection.text.slice(0, projection.scripts.end)
|
|
56
|
+
// A TEMPLATE POSITION IS A QUESTION TOO (14.2): a file that declares nothing but
|
|
57
|
+
// writes `{format(total)}` has a position only the checker can prove static. And a
|
|
58
|
+
// `<script server>` always is (16.70): what it binds is read off this shadow, re-exported
|
|
59
|
+
// under `PRIVATE_EXPORT`.
|
|
60
|
+
if (
|
|
61
|
+
!DECLARES.test(scripts) &&
|
|
62
|
+
projection.slots.size === 0 &&
|
|
63
|
+
source.file.servers.length === 0
|
|
64
|
+
) {
|
|
65
|
+
queries.push(undefined)
|
|
66
|
+
continue
|
|
67
|
+
}
|
|
68
|
+
// CANONICAL AT THE KEY, not at the comparison. A doubled separator or a relative
|
|
69
|
+
// path is the same file and a raw string is not the same string — registered as it
|
|
70
|
+
// arrived, the shadow appeared in nobody's directory listing AND named a file the
|
|
71
|
+
// checker could not open, and the refusal blamed the app's tsconfig for it.
|
|
72
|
+
const map = new ShadowMap(
|
|
73
|
+
host.canonical(source.path),
|
|
74
|
+
projection.origins,
|
|
75
|
+
)
|
|
76
|
+
// AN UNCHANGED SHADOW IS NOT REPORTED, which is what a kept session saves on a save:
|
|
77
|
+
// the one page that moved is re-read rather than all of them.
|
|
78
|
+
host.write(map.shadowPath, projection.text, edit)
|
|
79
|
+
host.openOutside(source.path, map.shadowPath, edit)
|
|
80
|
+
queries.push({ map, projection })
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const answered = queries.some((query) => query !== undefined)
|
|
84
|
+
? await host.ask(
|
|
85
|
+
(project) => fixedPoint(host, project, sources, queries),
|
|
86
|
+
{ edit },
|
|
87
|
+
)
|
|
88
|
+
: undefined
|
|
89
|
+
const answers = answered?.answers ?? sources.map(() => UNCHECKED)
|
|
90
|
+
const warnings = answered?.warnings ?? []
|
|
91
|
+
for (let at = 0; at < sources.length; at += 1) {
|
|
92
|
+
const source = sources[at] as (typeof sources)[number]
|
|
93
|
+
if (source.text !== undefined)
|
|
94
|
+
warnings.push(
|
|
95
|
+
...templateWarnings(
|
|
96
|
+
source.path,
|
|
97
|
+
source.text,
|
|
98
|
+
source.file,
|
|
99
|
+
answers[at] ?? UNCHECKED,
|
|
100
|
+
),
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
warnings.sort(byPlace)
|
|
104
|
+
return { answers, warnings }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Each query's answer, round after round, and the warnings each file's last answer earned — a
|
|
109
|
+
* source with no query answered `UNCHECKED`.
|
|
110
|
+
*
|
|
111
|
+
* 31.1 — A BINDING THE PROJECTION TYPED FROM A `Reactive` IS ASKED OF THE PROGRAM IT RUNS AS: each
|
|
112
|
+
* file holding one is lowered with what it has answered so far, and a binding that is a `Reactive`
|
|
113
|
+
* there is one in the projection too. A binding typed from one that just turned out to be a
|
|
114
|
+
* `Reactive` is asked again, until a round turns none. Nothing is asked, and no second snapshot
|
|
115
|
+
* taken, where no file holds one (`readsIn`'s `suspects`).
|
|
116
|
+
*/
|
|
117
|
+
async function fixedPoint(
|
|
118
|
+
host: ShadowHost,
|
|
119
|
+
first: Project,
|
|
120
|
+
sources: readonly (Pick<ParsedSource, 'path' | 'file'> & {
|
|
121
|
+
text?: string
|
|
122
|
+
})[],
|
|
123
|
+
queries: readonly (
|
|
124
|
+
{ map: ShadowMap; projection: Projection } | undefined
|
|
125
|
+
)[],
|
|
126
|
+
): Promise<{ answers: CheckerAnswer[]; warnings: Diagnosis[] }> {
|
|
127
|
+
const answers = sources.map(() => UNCHECKED)
|
|
128
|
+
// Per source, its compiler warnings, the bindings the projection could not type, and those the
|
|
129
|
+
// lowered program proved a `Reactive`.
|
|
130
|
+
const warned: Diagnosis[][] = sources.map(() => [])
|
|
131
|
+
const suspects: number[][] = sources.map(() => [])
|
|
132
|
+
const held: Set<number>[] = sources.map(() => new Set())
|
|
133
|
+
const answer = async (
|
|
134
|
+
asked: Project,
|
|
135
|
+
abide: Abide,
|
|
136
|
+
at: number,
|
|
137
|
+
): Promise<void> => {
|
|
138
|
+
const query = queries[at]
|
|
139
|
+
if (query === undefined) return
|
|
140
|
+
const source = sources[at] as (typeof sources)[number]
|
|
141
|
+
const sourceFile = await host.sourceFile(
|
|
142
|
+
asked,
|
|
143
|
+
query.map.shadowPath,
|
|
144
|
+
source.path,
|
|
145
|
+
'no name in it can be typed (RULEBOOK 31.3)',
|
|
146
|
+
)
|
|
147
|
+
const read = await readsIn(
|
|
148
|
+
asked.checker,
|
|
149
|
+
abide,
|
|
150
|
+
sourceFile,
|
|
151
|
+
query.map.shadowPath,
|
|
152
|
+
query.projection,
|
|
153
|
+
source.file,
|
|
154
|
+
held[at] as Set<number>,
|
|
155
|
+
)
|
|
156
|
+
answers[at] = read.answer
|
|
157
|
+
suspects[at] = read.suspects
|
|
158
|
+
warned[at] = placedFindings(
|
|
159
|
+
source.path,
|
|
160
|
+
source.text,
|
|
161
|
+
read.warnings,
|
|
162
|
+
(offset) => query.map.toSource(query.map.shadowPath, offset),
|
|
163
|
+
'warning',
|
|
164
|
+
true,
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
const every = queries.map((_, at) => at)
|
|
168
|
+
const firstAbide = await abideIn(first)
|
|
169
|
+
await Promise.all(every.map((at) => answer(first, firstAbide, at)))
|
|
170
|
+
for (;;) {
|
|
171
|
+
const asking = every.filter(
|
|
172
|
+
(at) => (suspects[at] as number[]).length > 0,
|
|
173
|
+
)
|
|
174
|
+
if (asking.length === 0) break
|
|
175
|
+
const edit: ShadowEdit = { created: [], changed: [] }
|
|
176
|
+
const lowered = asking.map((at) => {
|
|
177
|
+
const source = sources[at] as (typeof sources)[number]
|
|
178
|
+
const query = queries[at] as { map: ShadowMap }
|
|
179
|
+
const shadow = checkedShadow(
|
|
180
|
+
source.file,
|
|
181
|
+
answers[at] as CheckerAnswer,
|
|
182
|
+
)
|
|
183
|
+
host.write(query.map.checkedPath, shadow.text, edit)
|
|
184
|
+
host.openOutside(source.path, query.map.checkedPath, edit)
|
|
185
|
+
return { at, path: query.map.checkedPath, origins: shadow.origins }
|
|
186
|
+
})
|
|
187
|
+
const turned = await host.ask(
|
|
188
|
+
async (asked) => {
|
|
189
|
+
const abide = await abideIn(asked)
|
|
190
|
+
const turning = new Set<number>()
|
|
191
|
+
await Promise.all(
|
|
192
|
+
lowered.map(async ({ at, path, origins }) => {
|
|
193
|
+
const sourceFile =
|
|
194
|
+
await asked.program.getSourceFile(path)
|
|
195
|
+
if (sourceFile === undefined) return
|
|
196
|
+
for (const start of suspects[at] as number[]) {
|
|
197
|
+
const position = mapForward(origins, start)
|
|
198
|
+
if (position === undefined) continue
|
|
199
|
+
const symbol =
|
|
200
|
+
await asked.checker.getSymbolAtLocation(
|
|
201
|
+
getTouchingPropertyName(
|
|
202
|
+
sourceFile,
|
|
203
|
+
position,
|
|
204
|
+
),
|
|
205
|
+
)
|
|
206
|
+
if (
|
|
207
|
+
symbol !== undefined &&
|
|
208
|
+
(await isReactiveSymbol(
|
|
209
|
+
asked.checker,
|
|
210
|
+
abide,
|
|
211
|
+
symbol,
|
|
212
|
+
))
|
|
213
|
+
) {
|
|
214
|
+
;(held[at] as Set<number>).add(start)
|
|
215
|
+
turning.add(at)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}),
|
|
219
|
+
)
|
|
220
|
+
if (turning.size > 0)
|
|
221
|
+
await Promise.all(
|
|
222
|
+
[...turning].map((at) => answer(asked, abide, at)),
|
|
223
|
+
)
|
|
224
|
+
return turning
|
|
225
|
+
},
|
|
226
|
+
{ edit },
|
|
227
|
+
)
|
|
228
|
+
// A snapshot the checker opened no project in answers nothing further, and no warning of
|
|
229
|
+
// an earlier round is reported.
|
|
230
|
+
if (turned === undefined) return { answers, warnings: [] }
|
|
231
|
+
if (turned.size === 0) break
|
|
232
|
+
for (const at of asking) if (!turned.has(at)) suspects[at] = []
|
|
233
|
+
}
|
|
234
|
+
return { answers, warnings: warned.flat() }
|
|
235
|
+
}
|
|
@@ -182,6 +182,19 @@ export async function isReactiveType(
|
|
|
182
182
|
)
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
+
/**
|
|
186
|
+
* Whether the value `symbol` names is a `Reactive` (31.3) — the one question a binding is asked,
|
|
187
|
+
* whether of its projection or of the program it lowers to.
|
|
188
|
+
*/
|
|
189
|
+
export async function isReactiveSymbol(
|
|
190
|
+
checker: Checker,
|
|
191
|
+
abide: Abide,
|
|
192
|
+
symbol: CheckedSymbol,
|
|
193
|
+
): Promise<boolean> {
|
|
194
|
+
const type = await checker.getTypeOfSymbol(symbol)
|
|
195
|
+
return type !== undefined && isReactiveType(checker, abide, type, false)
|
|
196
|
+
}
|
|
197
|
+
|
|
185
198
|
/**
|
|
186
199
|
* Whether a type is abide's `Selection`, or a type parameter constrained by one.
|
|
187
200
|
*
|