@abide/abide 0.103.0 → 0.105.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/CHANGELOG.md +29 -0
- package/bin/abide.ts +80 -173
- package/docs/REGISTRY.md +27 -27
- package/docs/RULEBOOK.md +68 -24
- package/docs/abide.md +31 -46
- package/package.json +1 -1
- package/src/compiler/Lowering.ts +15 -5
- package/src/compiler/Refusals.ts +57 -0
- package/src/compiler/TypeSession.ts +46 -89
- package/src/compiler/appLayout.ts +447 -0
- package/src/compiler/appMap.ts +21 -25
- package/src/compiler/checkApp.ts +61 -97
- package/src/compiler/checkedShadow.ts +12 -10
- package/src/compiler/classifyRead.ts +3 -6
- package/src/compiler/compileApp.ts +183 -478
- package/src/compiler/componentGraph.ts +2 -71
- package/src/compiler/declaredExports.ts +79 -60
- package/src/compiler/emitClient.ts +11 -13
- package/src/compiler/emitComponent.ts +21 -6
- package/src/compiler/emitSource.ts +20 -20
- package/src/compiler/handlersUnder.ts +46 -36
- package/src/compiler/hookCalls.ts +152 -0
- package/src/compiler/mountsOf.ts +31 -54
- package/src/compiler/projectShadow.ts +1 -1
- package/src/compiler/readSources.ts +27 -42
- package/src/compiler/readsIn.ts +5 -16
- package/src/compiler/resolverIn.ts +77 -0
- package/src/server/abideModules.ts +152 -146
- package/src/server/answerRequest.ts +18 -4
- package/src/server/answerSocket.ts +136 -76
- package/src/server/answerValue.ts +28 -15
- package/src/server/appTree.ts +59 -73
- package/src/server/claudeApi.ts +2 -5
- package/src/server/clientBundle.ts +198 -0
- package/src/server/coerceArgs.ts +1 -1
- package/src/server/commands.ts +93 -57
- package/src/server/createApp.ts +90 -16
- package/src/server/deferredBuilds.ts +175 -0
- package/src/server/devWorker.ts +107 -319
- package/src/server/faceOf.ts +120 -0
- package/src/server/fenceServer.ts +10 -18
- package/src/server/generatedSurfaces.ts +12 -53
- package/src/server/index.ts +3 -0
- package/src/server/placement.ts +167 -0
- package/src/server/remote.ts +14 -42
- package/src/server/render.ts +25 -17
- package/src/server/renderComponent.ts +9 -5
- package/src/server/requestScope.ts +10 -4
- package/src/server/responseRelay.ts +389 -0
- package/src/server/runCompiled.ts +9 -12
- package/src/server/runDesktop.ts +6 -9
- package/src/server/serveBuild.ts +26 -73
- package/src/server/serveDev.ts +19 -176
- package/src/server/streamEvent.ts +29 -0
- package/src/server/versionOf.ts +1 -2
- package/src/server/withAbideSources.ts +2 -1
- package/src/shared/HandlerRoute.ts +68 -0
- package/src/shared/REFUSAL_KEY.ts +5 -0
- package/src/shared/RESERVED_DIRECTORIES.ts +6 -0
- package/src/shared/answerOf.ts +105 -78
- package/src/shared/filledPath.ts +1 -9
- package/src/shared/framing.ts +127 -128
- package/src/shared/queryText.ts +15 -7
- package/src/shared/reactive/Cursor.ts +69 -124
- package/src/shared/reactive/Reactive.ts +296 -162
- package/src/shared/reactive/callLoad.ts +4 -18
- package/src/shared/reactive/index.ts +11 -8
- package/src/shared/reactive/pairReactive.ts +1 -1
- package/src/shared/reactive/scoped.ts +4 -3
- package/src/shared/reactive/share.ts +11 -1
- package/src/shared/routePattern.ts +0 -110
- package/src/shared/rpc.ts +17 -24
- package/src/shared/rpcRequest.ts +18 -47
- package/src/shared/takeEarlyCall.ts +12 -37
- package/src/shared/url.ts +1 -1
- package/src/ui/Level.ts +276 -0
- package/src/ui/List.ts +17 -17
- package/src/ui/Router.ts +20 -87
- package/src/ui/applyHead.ts +92 -79
- package/src/ui/index.ts +4 -2
- package/src/ui/reconcile.ts +4 -110
- package/src/ui/rows.ts +123 -2
- package/src/ui/wire.ts +12 -27
- package/src/compiler/hooksCalled.ts +0 -35
- package/src/server/clientBuildOptions.ts +0 -54
- package/src/server/refuseAddressCollision.ts +0 -37
- package/src/shared/reactive/chunksOfStream.ts +0 -57
- package/src/ui/MOUNTING_LEVEL.ts +0 -18
package/src/compiler/checkApp.ts
CHANGED
|
@@ -1,22 +1,11 @@
|
|
|
1
1
|
import { relative } from 'node:path' // bun exposes no path helpers of its own
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
type AppChecker,
|
|
6
|
-
emitApp,
|
|
7
|
-
resolverIn,
|
|
8
|
-
sourcesUnder,
|
|
9
|
-
withChecker,
|
|
10
|
-
withDependencies,
|
|
11
|
-
} from './compileApp.ts'
|
|
2
|
+
import { type Placement, placement } from '#server/placement.ts'
|
|
3
|
+
import { type AppChecker, compiled } from './compileApp.ts'
|
|
12
4
|
import { abideImportsOf } from './componentGraph.ts'
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
readSources,
|
|
18
|
-
refusal,
|
|
19
|
-
} from './readSources.ts'
|
|
5
|
+
import { hooksIn } from './hookCalls.ts'
|
|
6
|
+
import { parseAbide, scriptBodies } from './parseAbide.ts'
|
|
7
|
+
import { Refusals } from './Refusals.ts'
|
|
8
|
+
import { parseSources, readSources } from './readSources.ts'
|
|
20
9
|
import type { Diagnosis } from './TypeSession.ts'
|
|
21
10
|
|
|
22
11
|
/**
|
|
@@ -30,82 +19,21 @@ import type { Diagnosis } from './TypeSession.ts'
|
|
|
30
19
|
* which is right for a build and wrong for a check: a file the parser or the read classifier
|
|
31
20
|
* refuses is reported at its first line, and the rest of the app is checked around it. A refusal
|
|
32
21
|
* only the whole app can see — two handlers at one address — comes from emitting it over the reads
|
|
33
|
-
*
|
|
22
|
+
* of every file that got that far (`compiled`).
|
|
34
23
|
*/
|
|
35
24
|
export async function checkApp(
|
|
36
25
|
appRoot: string,
|
|
37
26
|
kept?: AppChecker,
|
|
38
27
|
): Promise<Diagnosis[]> {
|
|
39
|
-
|
|
40
|
-
return withChecker(root, kept, async (session) => {
|
|
41
|
-
const refused: Diagnosis[] = []
|
|
42
|
-
const own = parseSources(sources, refused)
|
|
43
|
-
// 23.16-23.18 — what the build's walk would refuse of the tree, at each file it names.
|
|
44
|
-
const { handlers } = appFiles(root, (path, message) => {
|
|
45
|
-
refused.push(refusal(`${root}/${path}`, message))
|
|
46
|
-
})
|
|
47
|
-
const resolve = resolverIn()
|
|
48
|
-
const { sources: everyParsed, handlers: handlerFiles } =
|
|
49
|
-
await withDependencies(root, own, handlers, resolve, refused)
|
|
50
|
-
// 38.33 — A FILE WHOSE PROJECTION IS REFUSED (34.3, 34.17) is a finding at its file, found
|
|
51
|
-
// here because every later step projects it and would throw the refusal for the whole app.
|
|
52
|
-
// AFTER THE IMPORTS ARE FOLLOWED, so a refused file another one imports is found too.
|
|
53
|
-
const parsed: typeof everyParsed = []
|
|
54
|
-
for (const source of everyParsed)
|
|
55
|
-
try {
|
|
56
|
-
projectShadow(source.file)
|
|
57
|
-
parsed.push(source)
|
|
58
|
-
} catch (thrown) {
|
|
59
|
-
refused.push(refusal(source.path, thrown))
|
|
60
|
-
}
|
|
61
|
-
const { read, warnings } = await readSources(session, parsed, refused)
|
|
62
|
-
// THE APP'S OWN FILES ARE REPORTED ON: a dependency's is compiled with them (34.23), and
|
|
63
|
-
// what is wrong inside a package is the package's to report.
|
|
64
|
-
const reported: ReadSource[] = []
|
|
65
|
-
for (const source of read)
|
|
66
|
-
if (placeOf(relative(root, source.path)) !== 'dependency')
|
|
67
|
-
reported.push(source)
|
|
68
|
-
const { diagnoses: found, refused: loweringRefused } =
|
|
69
|
-
await session.diagnose(reported, root)
|
|
70
|
-
const { ambient, unkeyed } = await session.lints(root, parsed)
|
|
71
|
-
// A FILE WHOSE LOWERING REFUSED is `found` already, at its line — emitted too, it throws the
|
|
72
|
-
// same refusal at the root and takes the rest of the app's findings with it.
|
|
73
|
-
const emittable: ReadSource[] = []
|
|
74
|
-
for (const source of read)
|
|
75
|
-
if (!loweringRefused.has(source.path)) emittable.push(source)
|
|
76
|
-
if (refused.length === 0 && ambient.length === 0)
|
|
77
|
-
try {
|
|
78
|
-
warnings.push(
|
|
79
|
-
...(
|
|
80
|
-
await emitApp(
|
|
81
|
-
root,
|
|
82
|
-
session,
|
|
83
|
-
emittable,
|
|
84
|
-
resolve,
|
|
85
|
-
handlerFiles,
|
|
86
|
-
// 38.33 — each at its file, and every one rather than the first.
|
|
87
|
-
(relative, thrown) =>
|
|
88
|
-
refused.push(
|
|
89
|
-
refusal(`${root}/${relative}`, thrown),
|
|
90
|
-
),
|
|
91
|
-
)
|
|
92
|
-
).warnings,
|
|
93
|
-
)
|
|
94
|
-
} catch (thrown) {
|
|
95
|
-
refused.push(refusal(root, thrown))
|
|
96
|
-
}
|
|
97
|
-
return [...refused, ...ambient, ...unkeyed, ...warnings, ...found]
|
|
98
|
-
})
|
|
28
|
+
return (await compiled(appRoot, kept, true)).findings
|
|
99
29
|
}
|
|
100
30
|
|
|
101
|
-
const FILES_CONFIG = /^\s*onConfig\s*\(/m
|
|
102
|
-
|
|
103
31
|
/**
|
|
104
32
|
* The findings for one file as an editor holds it, saved or not.
|
|
105
33
|
*
|
|
106
34
|
* 38.11 — ONE FILE AS AN EDITOR HOLDS IT, saved or not: the same findings `checkApp` has for it, for
|
|
107
35
|
* `abide lsp` to publish. The rest of the app is read from disk, as it stands — save a file `held`
|
|
108
|
-
* has, which is one the editor holds too.
|
|
36
|
+
* has, which is one the editor holds too — and so is what the tree refuses of this one (38.34).
|
|
109
37
|
*
|
|
110
38
|
* 33.4 — THE COMPONENTS IT IMPORTS ARE LOWERED BESIDE IT, quietly, so a tag is held to the props its
|
|
111
39
|
* component declares rather than to a stand-in that takes anything. One level: what a component
|
|
@@ -118,14 +46,24 @@ export async function checkSource(
|
|
|
118
46
|
held: (path: string) => string | undefined = () => undefined,
|
|
119
47
|
): Promise<Diagnosis[]> {
|
|
120
48
|
const { session, root } = checker
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
49
|
+
const layout = await checker.layout()
|
|
50
|
+
// 38.33 — WHAT THE TREE REFUSES OF IT, off the layout the checker keeps until a move is heard.
|
|
51
|
+
const tree: Diagnosis[] = []
|
|
52
|
+
for (const refused of layout.refused)
|
|
53
|
+
if (refused.path === path) tree.push(refused)
|
|
54
|
+
const refusals = new Refusals(root, true)
|
|
55
|
+
const [page] = parseSources([{ path, text }], refusals)
|
|
56
|
+
if (page === undefined) return [...tree, ...refusals.found]
|
|
57
|
+
// 23.19 — WHERE THE APP PLACES IT, so a mounted page's `route.params` is typed by the route its
|
|
58
|
+
// mount gives it, as `checkApp` types it; a file the layout has not seen is placed at its path.
|
|
59
|
+
const placed: Pick<Placement, 'kind' | 'route'> =
|
|
60
|
+
layout.sources.find((source) => source.path === path) ??
|
|
61
|
+
placement(relative(root, path))
|
|
124
62
|
const imports: { path: string; text: string; quiet: boolean }[] = []
|
|
125
63
|
for (const imported of abideImportsOf(
|
|
126
64
|
path,
|
|
127
65
|
page.file,
|
|
128
|
-
|
|
66
|
+
layout.resolve,
|
|
129
67
|
).values())
|
|
130
68
|
try {
|
|
131
69
|
imports.push({
|
|
@@ -137,26 +75,52 @@ export async function checkSource(
|
|
|
137
75
|
// A component that cannot be read takes anything here.
|
|
138
76
|
}
|
|
139
77
|
// 27.13 — AND EVERY FILE FILING `onConfig`, quietly, so a page that only reads `config()` is
|
|
140
|
-
// typed by defaults it never imported — as `checkApp`'s whole-app program types it. The
|
|
141
|
-
// only
|
|
78
|
+
// typed by defaults it never imported — as `checkApp`'s whole-app program types it. The calls
|
|
79
|
+
// only name candidates; the checker decides which file.
|
|
142
80
|
const lowered = new Set(imports.map((one) => one.path))
|
|
143
|
-
const
|
|
144
|
-
for (const other of sources) {
|
|
81
|
+
for (const other of layout.sources) {
|
|
145
82
|
if (other.path === path || lowered.has(other.path)) continue
|
|
146
|
-
const
|
|
147
|
-
if (
|
|
148
|
-
|
|
83
|
+
const editing = held(other.path)
|
|
84
|
+
if (
|
|
85
|
+
editing === undefined || editing === other.text
|
|
86
|
+
? other.hooks.includes('onConfig')
|
|
87
|
+
: filesConfig(editing)
|
|
88
|
+
)
|
|
89
|
+
imports.push({
|
|
90
|
+
path: other.path,
|
|
91
|
+
text: editing ?? other.text,
|
|
92
|
+
quiet: true,
|
|
93
|
+
})
|
|
149
94
|
}
|
|
150
95
|
// A component that does not parse, or that the checker refuses, is its own file's finding and
|
|
151
96
|
// takes anything here: a quiet source's refusal is reported by nobody.
|
|
152
97
|
const { read, warnings } = await readSources(
|
|
153
98
|
session,
|
|
154
|
-
[page, ...parseSources(imports,
|
|
155
|
-
|
|
99
|
+
[page, ...parseSources(imports, refusals)],
|
|
100
|
+
refusals,
|
|
156
101
|
)
|
|
157
|
-
if (
|
|
102
|
+
if (refusals.found.length > 0) return [...tree, ...refusals.found]
|
|
158
103
|
const own: Diagnosis[] = []
|
|
159
104
|
for (const warning of warnings) if (warning.path === path) own.push(warning)
|
|
160
|
-
const
|
|
161
|
-
|
|
105
|
+
const [checked, ...beside] = read
|
|
106
|
+
const { diagnoses } = await session.diagnose(
|
|
107
|
+
checked === undefined
|
|
108
|
+
? beside
|
|
109
|
+
: [
|
|
110
|
+
{ ...checked, kind: placed.kind, route: placed.route },
|
|
111
|
+
...beside,
|
|
112
|
+
],
|
|
113
|
+
layout,
|
|
114
|
+
false,
|
|
115
|
+
)
|
|
116
|
+
return [...tree, ...own, ...diagnoses]
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Whether a `.abide` an editor holds calls `onConfig`, where its text parses. */
|
|
120
|
+
function filesConfig(text: string): boolean {
|
|
121
|
+
try {
|
|
122
|
+
return hooksIn(scriptBodies(parseAbide(text))).includes('onConfig')
|
|
123
|
+
} catch {
|
|
124
|
+
return false
|
|
125
|
+
}
|
|
162
126
|
}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { SERVER_SPECIFIER } from '#shared/SPECIFIERS.ts'
|
|
2
2
|
import type { CheckerAnswer } from './CheckerAnswer.ts'
|
|
3
3
|
import type { Finding } from './Finding.ts'
|
|
4
|
+
import { hookCalls } from './hookCalls.ts'
|
|
4
5
|
import {
|
|
5
6
|
CHECKED_CONFIG,
|
|
6
7
|
CHECKED_CONFIG_OF,
|
|
7
8
|
CHECKED_VALUES,
|
|
8
9
|
CHECKED_WRITE,
|
|
9
10
|
Lowering,
|
|
11
|
+
type Routed,
|
|
10
12
|
} from './Lowering.ts'
|
|
11
13
|
import { holdsReactive } from './PositionTable.ts'
|
|
12
14
|
import type { AbideFile } from './parseAbide.ts'
|
|
@@ -64,8 +66,10 @@ export const COMPONENT_MODULE = componentModule('any')
|
|
|
64
66
|
export function checkedShadow(
|
|
65
67
|
file: AbideFile,
|
|
66
68
|
answer: CheckerAnswer,
|
|
69
|
+
/** 23.9 — a page's or a layout's route, which its `route.params` is typed by. */
|
|
70
|
+
routed?: Routed,
|
|
67
71
|
): CheckedShadow {
|
|
68
|
-
const lowering = new Lowering(file, answer, true)
|
|
72
|
+
const lowering = new Lowering(file, answer, true, undefined, routed)
|
|
69
73
|
const projection = lowering.projection
|
|
70
74
|
// Every span the emitters lower, with whether it holds the `Reactive` rather than reading it.
|
|
71
75
|
const units: { span: Span; holds: boolean }[] = [
|
|
@@ -165,13 +169,10 @@ ${aliases}declare module '${SERVER_SPECIFIER}' {
|
|
|
165
169
|
`
|
|
166
170
|
}
|
|
167
171
|
|
|
168
|
-
// A call standing as a statement at a module's top level, as a formatted module writes one.
|
|
169
|
-
const TOP_LEVEL_CONFIG = /^onConfig\s*\(/gm
|
|
170
|
-
|
|
171
172
|
/**
|
|
172
|
-
* 27.13 FOR A
|
|
173
|
-
* text with each `onConfig(…)` at its top level bound as the lowering binds
|
|
174
|
-
* `Config` extended by each, or `undefined` where it files none.
|
|
173
|
+
* 27.13 FOR A HANDLER FILE, the one module that is no `.abide` and may call a hook (37.13): its
|
|
174
|
+
* text with each `onConfig(…)` standing as a statement at its top level bound as the lowering binds
|
|
175
|
+
* a server block's, and `Config` extended by each, or `undefined` where it files none.
|
|
175
176
|
*
|
|
176
177
|
* A MODULE OF ITS OWN BESIDE THE FILE, which the checker reads and nothing reports on: the file's own
|
|
177
178
|
* text is the one its diagnostics are placed in, and binding a call shifts every column after it.
|
|
@@ -180,11 +181,12 @@ export function moduleConfigShadow(text: string): string | undefined {
|
|
|
180
181
|
const bound: string[] = []
|
|
181
182
|
let out = ''
|
|
182
183
|
let from = 0
|
|
183
|
-
for (const
|
|
184
|
+
for (const call of hookCalls(text)) {
|
|
185
|
+
if (call.hook !== 'onConfig' || !call.topLevel) continue
|
|
184
186
|
const name = `${CHECKED_CONFIG}${bound.length}`
|
|
185
187
|
bound.push(name)
|
|
186
|
-
out += `${text.slice(from,
|
|
187
|
-
from =
|
|
188
|
+
out += `${text.slice(from, call.at)}const ${name} = ${CHECKED_CONFIG_OF}(${text.slice(call.at, call.end)})`
|
|
189
|
+
from = call.end
|
|
188
190
|
}
|
|
189
191
|
if (bound.length === 0) return undefined
|
|
190
192
|
return `${out}${text.slice(from)}\n${configInterface(bound)}`
|
|
@@ -101,11 +101,8 @@ export type Read = {
|
|
|
101
101
|
local: boolean
|
|
102
102
|
/** 33.20 — on the `props` call, the keys its pattern defaults; on a default's opening, its key. */
|
|
103
103
|
keys: string[]
|
|
104
|
-
/**
|
|
105
|
-
|
|
106
|
-
* file is.
|
|
107
|
-
*/
|
|
108
|
-
routeParams: [pattern: string, kind: string] | undefined
|
|
104
|
+
/** 23.9 — the read is abide's `route.params`, which a page or a layout types by its route. */
|
|
105
|
+
routeParams: boolean
|
|
109
106
|
}
|
|
110
107
|
|
|
111
108
|
/**
|
|
@@ -136,7 +133,7 @@ export function newRead(
|
|
|
136
133
|
byValue: false,
|
|
137
134
|
local: false,
|
|
138
135
|
keys: [],
|
|
139
|
-
routeParams:
|
|
136
|
+
routeParams: false,
|
|
140
137
|
}
|
|
141
138
|
}
|
|
142
139
|
|