@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
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type Attribute,
|
|
9
9
|
type Branch,
|
|
10
10
|
bodiesOf,
|
|
11
|
+
type ElementNode,
|
|
11
12
|
type ForBlock,
|
|
12
13
|
type InlineComponent,
|
|
13
14
|
type Setup,
|
|
@@ -18,6 +19,7 @@ import {
|
|
|
18
19
|
nesting,
|
|
19
20
|
skipQuoted,
|
|
20
21
|
slotEnd,
|
|
22
|
+
startsToken,
|
|
21
23
|
type Token,
|
|
22
24
|
tokenize,
|
|
23
25
|
} from './tokenize.ts'
|
|
@@ -37,6 +39,11 @@ export type AbideFile = {
|
|
|
37
39
|
* `scripts` because the two run a different number of times, which is the whole of the clause.
|
|
38
40
|
*/
|
|
39
41
|
modules: string[]
|
|
42
|
+
/**
|
|
43
|
+
* `<script server>` bodies in source order — 34.19, module scope on a server alone. Apart from
|
|
44
|
+
* `modules` because the browser's module never carries them.
|
|
45
|
+
*/
|
|
46
|
+
servers: string[]
|
|
40
47
|
/**
|
|
41
48
|
* `<style>` bodies in source order, outside any block body — each scoped to the component (34.4). A
|
|
42
49
|
* body's own is on its block (34.11).
|
|
@@ -47,6 +54,12 @@ export type AbideFile = {
|
|
|
47
54
|
* template here so no walk over the body steps around it. In tree order, which is 39.5.
|
|
48
55
|
*/
|
|
49
56
|
head: TemplateNode[]
|
|
57
|
+
/**
|
|
58
|
+
* 35.15 — the document's `<html>` and `<body>`, where the file is written inside them: each with
|
|
59
|
+
* its attributes and none of its children, which are the head and the template. `undefined`
|
|
60
|
+
* where the template is not.
|
|
61
|
+
*/
|
|
62
|
+
document: { html: ElementNode; body: ElementNode } | undefined
|
|
50
63
|
template: TemplateNode[]
|
|
51
64
|
/** 32.43 — the inline components the template's own body declares, in source order. */
|
|
52
65
|
components: InlineComponent[]
|
|
@@ -68,11 +81,19 @@ export type AbideFile = {
|
|
|
68
81
|
origins: Origins
|
|
69
82
|
}
|
|
70
83
|
|
|
84
|
+
/** Every script body a file holds — module, server and per-instance — for a walk over its imports. */
|
|
85
|
+
export function scriptBodies(file: AbideFile): string[] {
|
|
86
|
+
return [...file.modules, ...file.servers, ...file.scripts]
|
|
87
|
+
}
|
|
88
|
+
|
|
71
89
|
type Origins = {
|
|
72
90
|
modules: number[]
|
|
91
|
+
servers: number[]
|
|
73
92
|
scripts: number[]
|
|
74
93
|
holders: Map<object, number>
|
|
75
94
|
keys: Map<object, number>
|
|
95
|
+
/** 38.39 — per `{#for}` block, where its item and its index start (`-1` where it has none). */
|
|
96
|
+
bindings: Map<object, { item: number; index: number }>
|
|
76
97
|
}
|
|
77
98
|
|
|
78
99
|
/**
|
|
@@ -111,13 +132,16 @@ function unwindTo(
|
|
|
111
132
|
export function parseAbide(source: string): AbideFile {
|
|
112
133
|
const scripts: string[] = []
|
|
113
134
|
const modules: string[] = []
|
|
135
|
+
const servers: string[] = []
|
|
114
136
|
const styles: string[] = []
|
|
115
137
|
let generics: string | undefined
|
|
116
138
|
const origins: Origins = {
|
|
117
139
|
modules: [],
|
|
140
|
+
servers: [],
|
|
118
141
|
scripts: [],
|
|
119
142
|
holders: new Map(),
|
|
120
143
|
keys: new Map(),
|
|
144
|
+
bindings: new Map(),
|
|
121
145
|
}
|
|
122
146
|
const root: TemplateNode[] = []
|
|
123
147
|
// The open elements, innermost last. `root` is the floor and is never popped.
|
|
@@ -152,11 +176,16 @@ export function parseAbide(source: string): AbideFile {
|
|
|
152
176
|
// the div — while the browser re-parsing that markup closes at `</DIV>`, so every
|
|
153
177
|
// position the client walks to after it is a different node and the page hydrates
|
|
154
178
|
// against the wrong tree.
|
|
179
|
+
const closing = source.slice(at + 2, close).trim()
|
|
180
|
+
// 34.29 — a `<script>` is raw text whose body takes its close tag with it, so one met
|
|
181
|
+
// here is what is left of a block that ended early: a `</script>` in a comment or a
|
|
182
|
+
// string in it, and the source after it would be rendered as the page.
|
|
183
|
+
if (sameTag(closing, 'script'))
|
|
184
|
+
throw new Error(
|
|
185
|
+
'abide: a </script> that closes no <script> — a block ends at the first `</script>` in it, even one in a comment or a string. Write it `<\\/script>` there. RULEBOOK 34.29.',
|
|
186
|
+
)
|
|
155
187
|
const innermost = open[open.length - 1]
|
|
156
|
-
if (
|
|
157
|
-
typeof innermost === 'string' &&
|
|
158
|
-
sameTag(innermost, source.slice(at + 2, close).trim())
|
|
159
|
-
) {
|
|
188
|
+
if (typeof innermost === 'string' && sameTag(innermost, closing)) {
|
|
160
189
|
stack.pop()
|
|
161
190
|
open.pop()
|
|
162
191
|
}
|
|
@@ -213,7 +242,7 @@ export function parseAbide(source: string): AbideFile {
|
|
|
213
242
|
lowerTag === 'style'
|
|
214
243
|
? styles
|
|
215
244
|
: lowerTag === 'script'
|
|
216
|
-
? scriptBlock(tag.attributes, scripts, modules)
|
|
245
|
+
? scriptBlock(tag.attributes, scripts, modules, servers)
|
|
217
246
|
: undefined
|
|
218
247
|
const declared =
|
|
219
248
|
block === undefined || block === styles
|
|
@@ -224,7 +253,7 @@ export function parseAbide(source: string): AbideFile {
|
|
|
224
253
|
// setup have no call to be generic over.
|
|
225
254
|
if (block !== scripts || blocks.length > 0)
|
|
226
255
|
throw new Error(
|
|
227
|
-
'abide: generics="…" belongs on the component\'s own <script>, and this one is a <script module> or a block body\'s. RULEBOOK 34.13.',
|
|
256
|
+
'abide: generics="…" belongs on the component\'s own <script>, and this one is a <script module>, a <script server> or a block body\'s. RULEBOOK 34.13.',
|
|
228
257
|
)
|
|
229
258
|
if (generics !== undefined)
|
|
230
259
|
throw new Error(
|
|
@@ -233,7 +262,12 @@ export function parseAbide(source: string): AbideFile {
|
|
|
233
262
|
generics = declared
|
|
234
263
|
}
|
|
235
264
|
if (lowerTag === 'script' || lowerTag === 'style') {
|
|
236
|
-
const raw = rawBody(
|
|
265
|
+
const raw = rawBody(
|
|
266
|
+
source,
|
|
267
|
+
lowerTag,
|
|
268
|
+
tag,
|
|
269
|
+
block !== undefined && block !== styles,
|
|
270
|
+
)
|
|
237
271
|
// 14.8 — A `<script>` INSIDE A BLOCK IS THAT BODY'S, set up once each time the body is
|
|
238
272
|
// built, and not the component's: lifted, it ran once for the page and a row's own
|
|
239
273
|
// names (`{#for item …}`) were not in scope.
|
|
@@ -272,6 +306,7 @@ export function parseAbide(source: string): AbideFile {
|
|
|
272
306
|
if (block !== undefined) {
|
|
273
307
|
if (block === scripts) origins.scripts.push(tag.end)
|
|
274
308
|
else if (block === modules) origins.modules.push(tag.end)
|
|
309
|
+
else if (block === servers) origins.servers.push(tag.end)
|
|
275
310
|
block.push(raw.body)
|
|
276
311
|
at = raw.end
|
|
277
312
|
continue
|
|
@@ -508,11 +543,16 @@ export function parseAbide(source: string): AbideFile {
|
|
|
508
543
|
}
|
|
509
544
|
|
|
510
545
|
const components = fileComponents(root, declared)
|
|
511
|
-
refuseImported(components, [...modules, ...scripts])
|
|
546
|
+
refuseImported(components, [...modules, ...servers, ...scripts])
|
|
512
547
|
collapseWhitespace(root)
|
|
513
548
|
const head: TemplateNode[] = []
|
|
514
549
|
const template: TemplateNode[] = []
|
|
550
|
+
let document: AbideFile['document']
|
|
515
551
|
for (const node of root) {
|
|
552
|
+
if (node.kind === 'element' && node.lowerTag === 'html') {
|
|
553
|
+
document = documentOf(node, head, template)
|
|
554
|
+
continue
|
|
555
|
+
}
|
|
516
556
|
if (node.kind === 'element' && node.lowerTag === 'head') {
|
|
517
557
|
assertNoNestedHead(node.children, '<head>')
|
|
518
558
|
for (const child of node.children) head.push(child)
|
|
@@ -521,11 +561,23 @@ export function parseAbide(source: string): AbideFile {
|
|
|
521
561
|
assertNoNestedHead([node], '')
|
|
522
562
|
template.push(node)
|
|
523
563
|
}
|
|
564
|
+
// 35.16 — the document is written once, around everything else.
|
|
565
|
+
if (document !== undefined)
|
|
566
|
+
for (const node of root)
|
|
567
|
+
if (
|
|
568
|
+
!(node.kind === 'element' && node.lowerTag === 'html') &&
|
|
569
|
+
!(node.kind === 'text' && node.text.trim() === '')
|
|
570
|
+
)
|
|
571
|
+
throw new Error(
|
|
572
|
+
`abide: ${node.kind === 'element' ? `<${node.tag}>` : 'markup'} stands beside <html>, and a file written inside <html> has nothing outside it. RULEBOOK 35.16.`,
|
|
573
|
+
)
|
|
524
574
|
return {
|
|
525
575
|
scripts,
|
|
526
576
|
modules,
|
|
577
|
+
servers,
|
|
527
578
|
styles,
|
|
528
579
|
head,
|
|
580
|
+
document,
|
|
529
581
|
template,
|
|
530
582
|
components,
|
|
531
583
|
generics,
|
|
@@ -578,7 +630,7 @@ function inlineComponent(header: {
|
|
|
578
630
|
}
|
|
579
631
|
if (close !== tokens.length - 1)
|
|
580
632
|
throw new Error(
|
|
581
|
-
`abide: {#component ${header.text}} is not a name and one
|
|
633
|
+
`abide: {#component ${header.text}} is not a name and one parenthesized props pattern. Write {#component ${name.text}(props)}. RULEBOOK 32.39.`,
|
|
582
634
|
)
|
|
583
635
|
const inside = (from: number, to: number): { text: string; at: number } =>
|
|
584
636
|
trimmed(header.text, from, to)
|
|
@@ -695,6 +747,43 @@ function refuseImported(components: InlineComponent[], blocks: string[]): void {
|
|
|
695
747
|
)
|
|
696
748
|
}
|
|
697
749
|
|
|
750
|
+
/**
|
|
751
|
+
* 35.15 — the `<html>` a file is written inside: its `<head>`'s children added to `head`, its
|
|
752
|
+
* `<body>`'s to `template`, and the two elements handed back with their attributes alone.
|
|
753
|
+
*/
|
|
754
|
+
function documentOf(
|
|
755
|
+
html: ElementNode,
|
|
756
|
+
head: TemplateNode[],
|
|
757
|
+
template: TemplateNode[],
|
|
758
|
+
): { html: ElementNode; body: ElementNode } {
|
|
759
|
+
let body: ElementNode | undefined
|
|
760
|
+
for (const child of html.children) {
|
|
761
|
+
if (child.kind === 'text' && child.text.trim() === '') continue
|
|
762
|
+
if (child.kind === 'element' && child.lowerTag === 'head') {
|
|
763
|
+
assertNoNestedHead(child.children, '<head>')
|
|
764
|
+
for (const node of child.children) head.push(node)
|
|
765
|
+
continue
|
|
766
|
+
}
|
|
767
|
+
if (child.kind === 'element' && child.lowerTag === 'body' && !body) {
|
|
768
|
+
body = child
|
|
769
|
+
assertNoNestedHead(child.children, '<body>')
|
|
770
|
+
for (const node of child.children) template.push(node)
|
|
771
|
+
continue
|
|
772
|
+
}
|
|
773
|
+
throw new Error(
|
|
774
|
+
`abide: <html> holds a <head> and a <body> and nothing else, and this one holds ${child.kind === 'element' ? `<${child.tag}>` : 'more'}. RULEBOOK 35.16.`,
|
|
775
|
+
)
|
|
776
|
+
}
|
|
777
|
+
if (body === undefined)
|
|
778
|
+
throw new Error(
|
|
779
|
+
'abide: <html> holds no <body>, so the page has nowhere to go. RULEBOOK 35.16.',
|
|
780
|
+
)
|
|
781
|
+
// The elements themselves, emptied, so where each attribute was written is still known.
|
|
782
|
+
html.children = []
|
|
783
|
+
body.children = []
|
|
784
|
+
return { html, body }
|
|
785
|
+
}
|
|
786
|
+
|
|
698
787
|
/**
|
|
699
788
|
* Throws where a `<head>` stands anywhere but the top level of the file.
|
|
700
789
|
*
|
|
@@ -712,6 +801,14 @@ function assertNoNestedHead(nodes: TemplateNode[], within: string): void {
|
|
|
712
801
|
throw new Error(
|
|
713
802
|
`abide: a <head> must be top level in a .abide file, and this one is inside ${within}. RULEBOOK 39.2.`,
|
|
714
803
|
)
|
|
804
|
+
// 35.16 — the document's own elements are written once, at the top, around the rest.
|
|
805
|
+
if (
|
|
806
|
+
node.kind === 'element' &&
|
|
807
|
+
(node.lowerTag === 'html' || node.lowerTag === 'body')
|
|
808
|
+
)
|
|
809
|
+
throw new Error(
|
|
810
|
+
`abide: <${node.tag}> ${within === '' ? 'stands among the markup' : `is inside ${within}`}, and the document's <html> and <body> are written once, at the top of the root layout.abide. RULEBOOK 35.16.`,
|
|
811
|
+
)
|
|
715
812
|
const label =
|
|
716
813
|
node.kind === 'element'
|
|
717
814
|
? `<${node.tag}>`
|
|
@@ -783,29 +880,49 @@ function sameTag(left: string, right: string): boolean {
|
|
|
783
880
|
/**
|
|
784
881
|
* The list a `<script>`'s body belongs in, or `undefined` where it is markup.
|
|
785
882
|
*
|
|
786
|
-
* 34.1 is the bare tag
|
|
883
|
+
* 34.1 is the bare tag, 34.2 the bare `module` attribute and 34.19 the bare `server` one; anything
|
|
884
|
+
* else on the tag — `src`, `type`, `defer`, `nonce` —
|
|
787
885
|
* is a `<script>` the author wrote for the DOCUMENT, and lifting it into component setup moves it
|
|
788
|
-
* out of the markup and throws its attributes away.
|
|
886
|
+
* out of the markup and throws its attributes away — save beside `server`, which 34.28 refuses.
|
|
789
887
|
*
|
|
790
|
-
* 34.
|
|
791
|
-
* so
|
|
792
|
-
*
|
|
888
|
+
* 34.13 — BUT `generics="…"` IS THE COMPONENT'S. 34.12 — `lang` is refused on any `<script>`: every
|
|
889
|
+
* block is TypeScript, so it says nothing, and read as markup it took the author's whole setup out of
|
|
890
|
+
* the component without a word.
|
|
793
891
|
*/
|
|
794
892
|
function scriptBlock(
|
|
795
893
|
attributes: Attribute[],
|
|
796
894
|
scripts: string[],
|
|
797
895
|
modules: string[],
|
|
896
|
+
servers: string[],
|
|
798
897
|
): string[] | undefined {
|
|
799
898
|
let module = false
|
|
899
|
+
let server = false
|
|
900
|
+
let markup = false
|
|
800
901
|
for (const attribute of attributes) {
|
|
801
902
|
// `<script module>` carries the marker and no value. `module="x"` names something else.
|
|
802
903
|
if (attribute.lowerName === 'module' && attribute.value === '')
|
|
803
904
|
module = true
|
|
804
|
-
else if (attribute.lowerName === '
|
|
805
|
-
|
|
806
|
-
|
|
905
|
+
else if (attribute.lowerName === 'server') {
|
|
906
|
+
server = true
|
|
907
|
+
if (attribute.value !== '') markup = true
|
|
908
|
+
} else if (attribute.lowerName === 'lang')
|
|
909
|
+
throw new Error(
|
|
910
|
+
'abide: every <script> in a .abide file is TypeScript, so `lang` says nothing — remove it. RULEBOOK 34.12.',
|
|
911
|
+
)
|
|
912
|
+
else if (attribute.lowerName !== 'generics') markup = true
|
|
807
913
|
}
|
|
808
|
-
|
|
914
|
+
// 34.28 — a `server` attribute on a tag that would be markup ships the server's source to the
|
|
915
|
+
// document and the browser's module, so it is refused rather than demoted.
|
|
916
|
+
if (server && markup)
|
|
917
|
+
throw new Error(
|
|
918
|
+
'abide: a <script> carrying `server` is a server block, written `<script server>` with nothing beside it. RULEBOOK 34.28.',
|
|
919
|
+
)
|
|
920
|
+
if (markup) return undefined
|
|
921
|
+
if (module && server)
|
|
922
|
+
throw new Error(
|
|
923
|
+
'abide: a <script> is `module` or `server`, not both — a server block is module scope already. RULEBOOK 34.19.',
|
|
924
|
+
)
|
|
925
|
+
return server ? servers : module ? modules : scripts
|
|
809
926
|
}
|
|
810
927
|
|
|
811
928
|
/**
|
|
@@ -826,13 +943,22 @@ function rawBody(
|
|
|
826
943
|
source: string,
|
|
827
944
|
lowerTag: string,
|
|
828
945
|
tag: { selfClosing: boolean; end: number },
|
|
946
|
+
code: boolean,
|
|
829
947
|
): { body: string; end: number } {
|
|
830
948
|
if (tag.selfClosing) return { body: '', end: tag.end }
|
|
831
|
-
// The body ends at the first literal close tag, so a `</script>` inside
|
|
832
|
-
// block ends it early — the
|
|
949
|
+
// The body ends at the first literal close tag, as a browser's does, so a `</script>` inside
|
|
950
|
+
// a string or a comment in a script block ends it early — and the real close tag after it
|
|
951
|
+
// is refused where the template meets it (34.29).
|
|
833
952
|
const close = closeTagAt(source, lowerTag, tag.end)
|
|
834
953
|
if (close === null)
|
|
835
954
|
return { body: source.slice(tag.end), end: source.length }
|
|
955
|
+
// 34.30 — a component block's early end is refused AT the early end, because what is left
|
|
956
|
+
// over can open a `<script>` of its own that takes the real close tag, and then 34.29 meets
|
|
957
|
+
// nothing to refuse while the block's source between them renders as the page.
|
|
958
|
+
if (code && !startsToken(source, tag.end, close.start))
|
|
959
|
+
throw new Error(
|
|
960
|
+
'abide: a </script> inside a string, a comment or a regex ends the <script> block there, as a browser would. Write it `<\\/script>`. RULEBOOK 34.30.',
|
|
961
|
+
)
|
|
836
962
|
return { body: source.slice(tag.end, close.start), end: close.end }
|
|
837
963
|
}
|
|
838
964
|
|
|
@@ -1005,9 +1131,20 @@ function forBlock(
|
|
|
1005
1131
|
}
|
|
1006
1132
|
origins.holders.set(block, subject.at)
|
|
1007
1133
|
if (key.text !== '') origins.keys.set(block, key.at)
|
|
1134
|
+
origins.bindings.set(block, {
|
|
1135
|
+
item: header.at + leadingSpace(source),
|
|
1136
|
+
index:
|
|
1137
|
+
comma === undefined
|
|
1138
|
+
? -1
|
|
1139
|
+
: header.at + comma.end + leadingSpace(source.slice(comma.end)),
|
|
1140
|
+
})
|
|
1008
1141
|
return block
|
|
1009
1142
|
}
|
|
1010
1143
|
|
|
1144
|
+
function leadingSpace(text: string): number {
|
|
1145
|
+
return text.length - text.trimStart().length
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1011
1148
|
function readTag(
|
|
1012
1149
|
source: string,
|
|
1013
1150
|
open: number,
|