@abide/abide 0.101.0 → 0.103.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 +26 -0
- package/bin/abide.ts +226 -164
- package/docs/REGISTRY.md +730 -0
- package/docs/RULEBOOK.md +2768 -0
- package/docs/abide.md +13127 -0
- package/package.json +6 -3
- package/scaffold/AGENTS.md +38 -0
- package/scaffold/CLAUDE.md +1 -0
- package/scaffold/src/layout.abide +18 -0
- package/src/compiler/Lowering.ts +78 -9
- package/src/compiler/ShadowMap.ts +2 -2
- package/src/compiler/TypeSession.ts +341 -71
- package/src/compiler/appMap.ts +96 -0
- package/src/compiler/argsSchemaOf.ts +1 -1
- package/src/compiler/checkApp.ts +53 -8
- package/src/compiler/checkedShadow.ts +73 -6
- package/src/compiler/classifyRead.ts +6 -4
- package/src/compiler/classifyTemplate.ts +1 -1
- package/src/compiler/compileApp.ts +598 -200
- package/src/compiler/compiledEntry.ts +48 -40
- package/src/compiler/componentGraph.ts +57 -71
- package/src/compiler/declaredExports.ts +26 -9
- package/src/compiler/emitClient.ts +142 -72
- package/src/compiler/emitComponent.ts +41 -12
- package/src/compiler/emitSource.ts +102 -13
- package/src/compiler/handlersUnder.ts +164 -76
- package/src/compiler/hooksCalled.ts +35 -0
- package/src/compiler/index.ts +7 -0
- package/src/compiler/mountsOf.ts +150 -0
- package/src/compiler/nearest.ts +14 -0
- package/src/compiler/parseAbide.ts +157 -20
- package/src/compiler/projectShadow.ts +250 -19
- package/src/compiler/reactiveTypes.ts +20 -7
- package/src/compiler/readSources.ts +15 -2
- package/src/compiler/readsIn.ts +62 -8
- package/src/compiler/renderMarkup.ts +3 -3
- package/src/compiler/scaffold.ts +37 -15
- package/src/compiler/serveLanguage.ts +101 -34
- package/src/compiler/sharedModuleWrites.ts +2 -2
- package/src/compiler/sourceMapOf.ts +193 -0
- package/src/compiler/splitImports.ts +205 -83
- package/src/compiler/tokenize.ts +14 -0
- package/src/compiler/writeDefaultProject.ts +29 -2
- package/src/server/CLIENT_BUILD.ts +11 -13
- package/src/server/DEFAULT_PORT.ts +1 -1
- package/src/server/MODULE_QUERIES.ts +27 -0
- package/src/server/TABLES.ts +7 -0
- package/src/server/VIRTUAL_DIRECTORY.ts +4 -5
- package/src/server/abideModules.ts +457 -0
- package/src/server/answerFailure.ts +16 -6
- package/src/server/answerRequest.ts +241 -107
- package/src/server/answerRpc.ts +107 -33
- package/src/server/answerSocket.ts +65 -20
- package/src/server/answerValue.ts +22 -9
- package/src/server/appDirectories.ts +1 -1
- package/src/server/appPlugins.ts +1 -1
- package/src/server/appTree.ts +238 -0
- package/src/server/argsFor.ts +2 -2
- package/src/server/assistant.ts +2 -2
- package/src/server/bridgeRelay.ts +6 -8
- package/src/server/claudeCode.ts +0 -6
- package/src/server/clientBuildOptions.ts +8 -3
- package/src/server/coerceArgs.ts +1 -1
- package/src/server/commands.ts +38 -37
- package/src/server/componentModules.d.ts +2 -1
- package/src/server/compressAnswer.ts +4 -4
- package/src/server/config.ts +25 -29
- package/src/server/configFields.ts +31 -25
- package/src/server/createApp.ts +77 -79
- package/src/server/devWorker.ts +118 -181
- package/src/server/executableName.ts +4 -0
- package/src/server/fenceServer.ts +57 -32
- package/src/server/generatedSurfaces.ts +99 -66
- package/src/server/hooks.ts +137 -52
- package/src/server/iconAccent.ts +191 -0
- package/src/server/index.ts +12 -7
- package/src/server/installSourceMaps.ts +93 -0
- package/src/server/launcherOptions.ts +33 -31
- package/src/server/launcherPage.ts +107 -64
- package/src/server/manifestOf.ts +16 -13
- package/src/server/namesUnder.ts +2 -2
- package/src/server/nativeMenu.ts +6 -6
- package/src/server/negotiateEncoding.ts +1 -1
- package/src/server/preload.ts +13 -0
- package/src/server/principal.ts +10 -5
- package/src/server/realOrAsGiven.ts +10 -0
- package/src/server/refuseAddressCollision.ts +21 -14
- package/src/server/remote.ts +49 -52
- package/src/server/render.ts +65 -94
- package/src/server/renderComponent.ts +5 -0
- package/src/server/reportHealth.ts +2 -2
- package/src/server/requestScope.ts +12 -1
- package/src/server/runBuild.ts +24 -0
- package/src/server/runCompiled.ts +5 -5
- package/src/server/runDesktop.ts +48 -28
- package/src/server/serveBuild.ts +43 -52
- package/src/server/serveDev.ts +79 -37
- package/src/server/shell.ts +7 -22
- package/src/server/sourceMappings.ts +139 -0
- package/src/server/surfaceOf.ts +20 -3
- package/src/server/testBuild.ts +1 -19
- package/src/server/withAbideSources.ts +117 -0
- package/src/shared/ADDRESSES.ts +10 -18
- package/src/shared/APP_META.ts +7 -0
- package/src/shared/CopyOptions.ts +1 -1
- package/src/shared/DEBUG_META.ts +1 -1
- package/src/shared/LAUNCHER_FILES.ts +9 -0
- package/src/shared/RESERVED_DIRECTORIES.ts +24 -0
- package/src/shared/cookies.ts +2 -2
- package/src/shared/csp.ts +1 -1
- package/src/shared/escapeText.ts +1 -1
- package/src/shared/filledPath.ts +85 -0
- package/src/shared/framing.ts +79 -6
- package/src/shared/headKeyOf.ts +2 -0
- package/src/shared/index.ts +2 -1
- package/src/shared/log.ts +27 -13
- package/src/shared/matchRoute.ts +23 -4
- package/src/{ui → shared}/menuBridge.ts +2 -2
- package/src/shared/menuItem.ts +4 -4
- package/src/shared/onMenu.ts +58 -0
- package/src/shared/reactive/Keyed.ts +2 -2
- package/src/shared/reactive/NotLanded.ts +1 -1
- package/src/shared/reactive/Reactive.ts +43 -43
- package/src/shared/reactive/argsContract.ts +4 -4
- package/src/shared/reactive/canonicalKey.ts +4 -4
- package/src/shared/reactive/chunksOfStream.ts +6 -0
- package/src/shared/reactive/index.ts +1 -1
- package/src/shared/reactive/recordWrites.ts +103 -5
- package/src/shared/reactive/structural.ts +1 -1
- package/src/shared/reactive/tracking.ts +1 -1
- package/src/shared/reactive/watch.ts +3 -3
- package/src/shared/refuse.ts +1 -1
- package/src/{ui → shared}/reportMenu.ts +8 -10
- package/src/shared/routePattern.ts +122 -48
- package/src/shared/rpc.ts +37 -8
- package/src/shared/rpcRequest.ts +27 -3
- package/src/shared/url.ts +4 -44
- package/src/shared/writePath.ts +1 -1
- package/src/ui/LAYER_NAMES.ts +1 -1
- package/src/ui/List.ts +3 -3
- package/src/ui/MOUNTING_LEVEL.ts +18 -0
- package/src/ui/Mismatch.ts +1 -1
- package/src/ui/RawRun.ts +1 -1
- package/src/ui/Router.ts +37 -36
- package/src/ui/Scope.ts +1 -1
- package/src/ui/TryRegion.ts +2 -2
- package/src/ui/applyHead.ts +33 -0
- package/src/ui/index.ts +1 -1
- package/src/ui/moveNode.ts +1 -1
- package/src/ui/reconcile.ts +2 -2
- package/src/ui/routing.ts +2 -2
- package/src/ui/setAttribute.ts +1 -1
- package/src/ui/setText.ts +1 -1
- package/src/ui/socketWire.ts +9 -7
- package/src/ui/wire.ts +1 -1
- package/scaffold/src/server/app.ts +0 -6
- package/scaffold/src/shared/.gitkeep +0 -0
- package/scaffold/src/ui/app.html +0 -8
- package/src/server/componentImports.ts +0 -56
- /package/scaffold/src/{ui/pages/page.abide → index.abide} +0 -0
- /package/src/{compiler → shared}/SPECIFIERS.ts +0 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// 38.108 — THE APP'S MAP: every address it answers, the file answering each, and where every hook is
|
|
2
|
+
// filed. What `abide routes` prints, off the build's own reading of the tree — the routes and the
|
|
3
|
+
// handler mounts `compileApp` answers with — so the map and what the server answers are one walk.
|
|
4
|
+
//
|
|
5
|
+
// THE HOOKS ARE READ, NOT RUN, as the handlers are: a call of one of abide's hook names in a server
|
|
6
|
+
// block, a `<script>` or a handler file, at the route 37.1 files it at.
|
|
7
|
+
|
|
8
|
+
import { readFileSync } from 'node:fs' // bun exposes no sync read of its own
|
|
9
|
+
import { appFiles } from '#server/appTree.ts'
|
|
10
|
+
import { scopeOf } from '#shared/routePattern.ts'
|
|
11
|
+
import { compileApp } from './compileApp.ts'
|
|
12
|
+
import { hooksCalled } from './hooksCalled.ts'
|
|
13
|
+
import { parseAbide } from './parseAbide.ts'
|
|
14
|
+
|
|
15
|
+
/** One address the app answers, and the file answering it. */
|
|
16
|
+
type MappedRoute = {
|
|
17
|
+
method: string
|
|
18
|
+
address: string
|
|
19
|
+
file: string
|
|
20
|
+
kind: 'page' | 'handler' | 'private'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** One hook call, the route it is filed at, and the file making it. */
|
|
24
|
+
type MappedHook = { hook: string; route: string; file: string }
|
|
25
|
+
|
|
26
|
+
/** Every address the app at `root` answers, and every hook it files. */
|
|
27
|
+
export async function appMap(
|
|
28
|
+
root: string,
|
|
29
|
+
): Promise<{ routes: MappedRoute[]; hooks: MappedHook[] }> {
|
|
30
|
+
const app = await compileApp(root)
|
|
31
|
+
const routes: MappedRoute[] = []
|
|
32
|
+
for (const route of app.routes)
|
|
33
|
+
routes.push({
|
|
34
|
+
method: 'GET',
|
|
35
|
+
address: route.pattern,
|
|
36
|
+
file: route.source,
|
|
37
|
+
kind: 'page',
|
|
38
|
+
})
|
|
39
|
+
for (const mount of app.mounts)
|
|
40
|
+
routes.push({
|
|
41
|
+
method: mount.method,
|
|
42
|
+
address: mount.address,
|
|
43
|
+
file: mount.source,
|
|
44
|
+
kind: mount.private ? 'private' : 'handler',
|
|
45
|
+
})
|
|
46
|
+
routes.sort((one, two) =>
|
|
47
|
+
one.address === two.address
|
|
48
|
+
? one.method < two.method
|
|
49
|
+
? -1
|
|
50
|
+
: 1
|
|
51
|
+
: one.address < two.address
|
|
52
|
+
? -1
|
|
53
|
+
: 1,
|
|
54
|
+
)
|
|
55
|
+
const hooks: MappedHook[] = []
|
|
56
|
+
const { abide, handlers, modules } = appFiles(root, () => {})
|
|
57
|
+
for (const file of abide) {
|
|
58
|
+
const parsed = parseAbide(readFileSync(`${root}/${file}`, 'utf8'))
|
|
59
|
+
// Block by block: one block's last token would decide how the next one's first reads.
|
|
60
|
+
for (const script of [...parsed.servers, ...parsed.scripts])
|
|
61
|
+
for (const hook of hooksCalled(script))
|
|
62
|
+
hooks.push({ hook, route: scopeOf(file), file })
|
|
63
|
+
}
|
|
64
|
+
for (const file of handlers)
|
|
65
|
+
for (const hook of hooksCalled(readFileSync(`${root}/${file}`, 'utf8')))
|
|
66
|
+
hooks.push({ hook, route: scopeOf(file), file })
|
|
67
|
+
// 37.1 — A MODULE THAT IS NEITHER files at the root: it runs as it is imported, before the
|
|
68
|
+
// importing file's route is set.
|
|
69
|
+
for (const file of modules)
|
|
70
|
+
for (const hook of hooksCalled(readFileSync(`${root}/${file}`, 'utf8')))
|
|
71
|
+
hooks.push({ hook, route: '/', file })
|
|
72
|
+
return { routes, hooks }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** The map as `abide routes` prints it: one line an address, then one a hook. */
|
|
76
|
+
export function mapText(map: Awaited<ReturnType<typeof appMap>>): string {
|
|
77
|
+
const lines: string[] = []
|
|
78
|
+
const width = (values: string[]): number =>
|
|
79
|
+
values.reduce((most, value) => Math.max(most, value.length), 0)
|
|
80
|
+
const methods = width(map.routes.map((route) => route.method))
|
|
81
|
+
const addresses = width(map.routes.map((route) => route.address))
|
|
82
|
+
for (const route of map.routes)
|
|
83
|
+
lines.push(
|
|
84
|
+
`${route.method.padEnd(methods)} ${route.address.padEnd(addresses)} ${route.file}${route.kind === 'private' ? ' (private)' : ''}`,
|
|
85
|
+
)
|
|
86
|
+
if (map.hooks.length > 0) {
|
|
87
|
+
lines.push('', 'hooks')
|
|
88
|
+
const hooks = width(map.hooks.map((hook) => hook.hook))
|
|
89
|
+
const routes = width(map.hooks.map((hook) => hook.route))
|
|
90
|
+
for (const hook of map.hooks)
|
|
91
|
+
lines.push(
|
|
92
|
+
` ${hook.hook.padEnd(hooks)} ${hook.route.padEnd(routes)} ${hook.file}`,
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
return lines.join('\n')
|
|
96
|
+
}
|
|
@@ -134,7 +134,7 @@ async function objectOf(
|
|
|
134
134
|
}
|
|
135
135
|
const schema: Schema = { type: 'object', properties }
|
|
136
136
|
if (required.length > 0) schema.required = required
|
|
137
|
-
// 19.
|
|
137
|
+
// 19.18 — A MEMBER THE TYPE DOES NOT DECLARE IS REFUSED, where no index signature declares the
|
|
138
138
|
// rest: a caller outside the process sends whatever it likes, and a body writing its args to a
|
|
139
139
|
// row would write that too. An author who wants the rest declares a schema of their own.
|
|
140
140
|
schema.additionalProperties = false
|
package/src/compiler/checkApp.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { relative } from 'node:path' // bun exposes no path helpers of its own
|
|
2
|
-
import {
|
|
2
|
+
import { appFiles } from '#server/appTree.ts'
|
|
3
|
+
import { placeOf } from '#shared/routePattern.ts'
|
|
3
4
|
import {
|
|
4
5
|
type AppChecker,
|
|
5
6
|
emitApp,
|
|
6
7
|
resolverIn,
|
|
7
8
|
sourcesUnder,
|
|
8
9
|
withChecker,
|
|
10
|
+
withDependencies,
|
|
9
11
|
} from './compileApp.ts'
|
|
10
12
|
import { abideImportsOf } from './componentGraph.ts'
|
|
13
|
+
import { projectShadow } from './projectShadow.ts'
|
|
11
14
|
import {
|
|
12
15
|
parseSources,
|
|
13
16
|
type ReadSource,
|
|
@@ -36,10 +39,34 @@ export async function checkApp(
|
|
|
36
39
|
const { root, sources } = await sourcesUnder(appRoot)
|
|
37
40
|
return withChecker(root, kept, async (session) => {
|
|
38
41
|
const refused: Diagnosis[] = []
|
|
39
|
-
const
|
|
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
|
+
}
|
|
40
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)
|
|
41
68
|
const { diagnoses: found, refused: loweringRefused } =
|
|
42
|
-
await session.diagnose(
|
|
69
|
+
await session.diagnose(reported, root)
|
|
43
70
|
const { ambient, unkeyed } = await session.lints(root, parsed)
|
|
44
71
|
// A FILE WHOSE LOWERING REFUSED is `found` already, at its line — emitted too, it throws the
|
|
45
72
|
// same refusal at the root and takes the rest of the app's findings with it.
|
|
@@ -54,7 +81,13 @@ export async function checkApp(
|
|
|
54
81
|
root,
|
|
55
82
|
session,
|
|
56
83
|
emittable,
|
|
57
|
-
|
|
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
|
+
),
|
|
58
91
|
)
|
|
59
92
|
).warnings,
|
|
60
93
|
)
|
|
@@ -65,10 +98,12 @@ export async function checkApp(
|
|
|
65
98
|
})
|
|
66
99
|
}
|
|
67
100
|
|
|
101
|
+
const FILES_CONFIG = /^\s*onConfig\s*\(/m
|
|
102
|
+
|
|
68
103
|
/**
|
|
69
104
|
* The findings for one file as an editor holds it, saved or not.
|
|
70
105
|
*
|
|
71
|
-
* 38.
|
|
106
|
+
* 38.11 — ONE FILE AS AN EDITOR HOLDS IT, saved or not: the same findings `checkApp` has for it, for
|
|
72
107
|
* `abide lsp` to publish. The rest of the app is read from disk, as it stands — save a file `held`
|
|
73
108
|
* has, which is one the editor holds too.
|
|
74
109
|
*
|
|
@@ -78,12 +113,11 @@ export async function checkApp(
|
|
|
78
113
|
*/
|
|
79
114
|
export async function checkSource(
|
|
80
115
|
checker: AppChecker,
|
|
81
|
-
root: string,
|
|
82
116
|
path: string,
|
|
83
117
|
text: string,
|
|
84
118
|
held: (path: string) => string | undefined = () => undefined,
|
|
85
119
|
): Promise<Diagnosis[]> {
|
|
86
|
-
const session = checker
|
|
120
|
+
const { session, root } = checker
|
|
87
121
|
const refused: Diagnosis[] = []
|
|
88
122
|
const [page] = parseSources([{ path, text }], refused)
|
|
89
123
|
if (page === undefined) return refused
|
|
@@ -91,7 +125,7 @@ export async function checkSource(
|
|
|
91
125
|
for (const imported of abideImportsOf(
|
|
92
126
|
path,
|
|
93
127
|
page.file,
|
|
94
|
-
resolverIn(
|
|
128
|
+
resolverIn(),
|
|
95
129
|
).values())
|
|
96
130
|
try {
|
|
97
131
|
imports.push({
|
|
@@ -102,6 +136,17 @@ export async function checkSource(
|
|
|
102
136
|
} catch {
|
|
103
137
|
// A component that cannot be read takes anything here.
|
|
104
138
|
}
|
|
139
|
+
// 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 scan
|
|
141
|
+
// only names candidates (a statement opening on the call); the checker decides which file.
|
|
142
|
+
const lowered = new Set(imports.map((one) => one.path))
|
|
143
|
+
const { sources } = await checker.sources()
|
|
144
|
+
for (const other of sources) {
|
|
145
|
+
if (other.path === path || lowered.has(other.path)) continue
|
|
146
|
+
const text = held(other.path) ?? other.text
|
|
147
|
+
if (text.includes('onConfig') && FILES_CONFIG.test(text))
|
|
148
|
+
imports.push({ path: other.path, text, quiet: true })
|
|
149
|
+
}
|
|
105
150
|
// A component that does not parse, or that the checker refuses, is its own file's finding and
|
|
106
151
|
// takes anything here: a quiet source's refusal is reported by nobody.
|
|
107
152
|
const { read, warnings } = await readSources(
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
+
import { SERVER_SPECIFIER } from '#shared/SPECIFIERS.ts'
|
|
1
2
|
import type { CheckerAnswer } from './CheckerAnswer.ts'
|
|
2
3
|
import type { Finding } from './Finding.ts'
|
|
3
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
CHECKED_CONFIG,
|
|
6
|
+
CHECKED_CONFIG_OF,
|
|
7
|
+
CHECKED_VALUES,
|
|
8
|
+
CHECKED_WRITE,
|
|
9
|
+
Lowering,
|
|
10
|
+
} from './Lowering.ts'
|
|
4
11
|
import { holdsReactive } from './PositionTable.ts'
|
|
5
12
|
import type { AbideFile } from './parseAbide.ts'
|
|
6
13
|
import { NO_PROPS, type Origin, type Span } from './projectShadow.ts'
|
|
@@ -18,7 +25,7 @@ import { abideImports } from './splitImports.ts'
|
|
|
18
25
|
//
|
|
19
26
|
// A `.abide` IMPORT NAMES ITS CHECKED MODULE, so a component a page imports resolves to something
|
|
20
27
|
// the checker can read rather than to a file it cannot parse. What a checked module exports as its
|
|
21
|
-
// default is a component taking what its `props` call declares, so a tag is held to it (38.
|
|
28
|
+
// default is a component taking what its `props` call declares, so a tag is held to it (38.40).
|
|
22
29
|
|
|
23
30
|
/** A `.abide` lowered as the program it runs as, with its runs mapped back to the projection. */
|
|
24
31
|
export type CheckedShadow = {
|
|
@@ -35,15 +42,17 @@ export type CheckedShadow = {
|
|
|
35
42
|
export const COMPONENT_STAND_IN = '__component'
|
|
36
43
|
|
|
37
44
|
/**
|
|
38
|
-
* The
|
|
39
|
-
*
|
|
45
|
+
* The component a checked module exports as its default: called with the record its tag passes
|
|
46
|
+
* (`projectShadow`'s `invocation`), and answering the `Component` a server module's call of it
|
|
47
|
+
* renders (34.16). What its blocks export by name is beside it.
|
|
40
48
|
*/
|
|
41
49
|
function componentModule(props: string, generics?: string): string {
|
|
42
50
|
// 34.13 — generic over what the file's `<script>` declares, so a caller's props infer it, the
|
|
43
|
-
// signature's own parameters shadowing the opaque stand-ins the body was checked against.
|
|
51
|
+
// signature's own parameters shadowing the opaque stand-ins the body was checked against. 34.16 —
|
|
52
|
+
// a component no prop is required of is called with none, as the runtime's bound face takes it.
|
|
44
53
|
const parameters = generics === undefined ? '' : `<${generics}>`
|
|
45
54
|
return `
|
|
46
|
-
declare const ${COMPONENT_STAND_IN}: ${parameters}(record: ${props}) =>
|
|
55
|
+
declare const ${COMPONENT_STAND_IN}: ${parameters}(...record: {} extends ${props} ? [record?: ${props}] : [record: ${props}]) => import('${SERVER_SPECIFIER}').Component
|
|
47
56
|
export default ${COMPONENT_STAND_IN}
|
|
48
57
|
`
|
|
49
58
|
}
|
|
@@ -61,6 +70,7 @@ export function checkedShadow(
|
|
|
61
70
|
// Every span the emitters lower, with whether it holds the `Reactive` rather than reading it.
|
|
62
71
|
const units: { span: Span; holds: boolean }[] = [
|
|
63
72
|
{ span: projection.modules, holds: false },
|
|
73
|
+
{ span: projection.servers, holds: false },
|
|
64
74
|
{ span: projection.scripts, holds: false },
|
|
65
75
|
]
|
|
66
76
|
for (const span of projection.slots.values())
|
|
@@ -101,6 +111,7 @@ export function checkedShadow(
|
|
|
101
111
|
}\n`
|
|
102
112
|
text += `declare const ${CHECKED_VALUES}: {\n${values}}\n`
|
|
103
113
|
text += `declare function ${CHECKED_WRITE}<Cell extends { (): unknown; set(value: never): unknown }, Value extends Parameters<Cell['set']>[0]>(cell: Cell, value: Value): Value extends ReturnType<Cell> ? Value : ReturnType<Cell>\n`
|
|
114
|
+
text += configDeclarations(lowering)
|
|
104
115
|
return {
|
|
105
116
|
...pointAtCheckedModules(text, origins, scriptsEnd),
|
|
106
117
|
refusals: lowering.refusals,
|
|
@@ -123,6 +134,62 @@ function propsOf(lowering: Lowering): string {
|
|
|
123
134
|
.replace(/\(\s*\)$/, '')}>`
|
|
124
135
|
}
|
|
125
136
|
|
|
137
|
+
/**
|
|
138
|
+
* 27.13 — WHAT EACH `onConfig` DEFAULTS IS DECLARED INTO `Config`, so `config().RETRIES` is the
|
|
139
|
+
* `number` its defaults return rather than the index's `unknown`. Each call is bound by the lowering
|
|
140
|
+
* (`CHECKED_CONFIG`) and typed by `CHECKED_CONFIG_OF` as what its defaults return; an interface can
|
|
141
|
+
* only extend a name, so each is aliased, and a union is narrowed to the keys every member has.
|
|
142
|
+
* Nothing where the file files none.
|
|
143
|
+
*/
|
|
144
|
+
function configDeclarations(lowering: Lowering): string {
|
|
145
|
+
const bound: string[] = []
|
|
146
|
+
for (const read of lowering.answer.reads)
|
|
147
|
+
if (read.shape === 'config')
|
|
148
|
+
bound.push(`${CHECKED_CONFIG}${read.start}`)
|
|
149
|
+
return configInterface(bound)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** `Config` extended by what each of `bound` holds — the binding of one `onConfig` call each. */
|
|
153
|
+
function configInterface(bound: readonly string[]): string {
|
|
154
|
+
if (bound.length === 0) return ''
|
|
155
|
+
let aliases = ''
|
|
156
|
+
let names = ''
|
|
157
|
+
for (const name of bound) {
|
|
158
|
+
aliases += `type ${name}Type = typeof ${name}\n`
|
|
159
|
+
names += names === '' ? `${name}Type` : `, ${name}Type`
|
|
160
|
+
}
|
|
161
|
+
return `declare function ${CHECKED_CONFIG_OF}<Options>(on: (defaults: never, options?: Options) => () => void): <Defaults>(defaults: ((env: import('${SERVER_SPECIFIER}').Env) => Defaults) | null, options?: Options) => [Defaults] extends [object] ? Pick<Defaults, keyof Defaults> : {}
|
|
162
|
+
${aliases}declare module '${SERVER_SPECIFIER}' {
|
|
163
|
+
interface Config extends ${names} {}
|
|
164
|
+
}
|
|
165
|
+
`
|
|
166
|
+
}
|
|
167
|
+
|
|
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
|
+
* 27.13 FOR A MODULE THAT IS NO `.abide` — a handler file, or one under a `server` directory: its
|
|
173
|
+
* text with each `onConfig(…)` at its top level bound as the lowering binds a server block's, and
|
|
174
|
+
* `Config` extended by each, or `undefined` where it files none.
|
|
175
|
+
*
|
|
176
|
+
* A MODULE OF ITS OWN BESIDE THE FILE, which the checker reads and nothing reports on: the file's own
|
|
177
|
+
* text is the one its diagnostics are placed in, and binding a call shifts every column after it.
|
|
178
|
+
*/
|
|
179
|
+
export function moduleConfigShadow(text: string): string | undefined {
|
|
180
|
+
const bound: string[] = []
|
|
181
|
+
let out = ''
|
|
182
|
+
let from = 0
|
|
183
|
+
for (const found of text.matchAll(TOP_LEVEL_CONFIG)) {
|
|
184
|
+
const name = `${CHECKED_CONFIG}${bound.length}`
|
|
185
|
+
bound.push(name)
|
|
186
|
+
out += `${text.slice(from, found.index)}const ${name} = ${CHECKED_CONFIG_OF}(onConfig)(`
|
|
187
|
+
from = found.index + found[0].length
|
|
188
|
+
}
|
|
189
|
+
if (bound.length === 0) return undefined
|
|
190
|
+
return `${out}${text.slice(from)}\n${configInterface(bound)}`
|
|
191
|
+
}
|
|
192
|
+
|
|
126
193
|
/**
|
|
127
194
|
* Points each `.abide` import in the script blocks at its checked module, and moves `origins` to
|
|
128
195
|
* match.
|
|
@@ -27,10 +27,11 @@ import { REACTIVE_MEMBERS } from './REACTIVE_MEMBERS.ts'
|
|
|
27
27
|
* statement ends and whether `{ n }` is a literal are TypeScript's answers rather than a token
|
|
28
28
|
* walk's guess at them. Every offset is into the file's projection (`projectShadow`).
|
|
29
29
|
*
|
|
30
|
-
*
|
|
31
|
-
* (32.25), which lowers to the framework's own `raw`; a `<script>`'s is left as written.
|
|
32
|
-
*
|
|
33
|
-
*
|
|
30
|
+
* TWO SHAPES ARE NOT A REACTIVE NAME: `raw` is the callee of a template expression's bare `raw(…)`
|
|
31
|
+
* (32.25), which lowers to the framework's own `raw`; a `<script>`'s is left as written. `config`
|
|
32
|
+
* is the callee of an `onConfig(…)` statement in a `<script server>` (27.13), which only a checked
|
|
33
|
+
* lowering rewrites. They ride this list because each is a rewrite at an occurrence, which is what
|
|
34
|
+
* the splice walks; what the checker proved about a whole position is `CheckerAnswer`'s.
|
|
34
35
|
*
|
|
35
36
|
* THE SHAPE IS FIXED: every field on every read, so `Lowering`'s splice sees one hidden class.
|
|
36
37
|
*/
|
|
@@ -69,6 +70,7 @@ export type Read = {
|
|
|
69
70
|
| 'restMember'
|
|
70
71
|
| 'restWhole'
|
|
71
72
|
| 'raw'
|
|
73
|
+
| 'config'
|
|
72
74
|
/** What the rewrite replaces: the name, or a whole write or update from its first token. */
|
|
73
75
|
from: number
|
|
74
76
|
to: number
|
|
@@ -132,7 +132,7 @@ export function classifyAttributes(
|
|
|
132
132
|
// 32.10, 32.19 — THE PLAIN `style={expr}` OR `class={expr}` BESIDE A DIRECTIVE IS LEFT
|
|
133
133
|
// UNBOUND. `setAttribute` replaces the whole merged value and takes the directives'
|
|
134
134
|
// halves with it, and the two bindings then overwrite each other in whichever order
|
|
135
|
-
// they
|
|
135
|
+
// they reran.
|
|
136
136
|
//
|
|
137
137
|
// ALLOWED, rather than refused. The page still RENDERS right — the server writes both
|
|
138
138
|
// halves into the one merged attribute — so the only thing a build error would buy is
|