@nextwebwg/html-next 1.0.0-alpha.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/LICENSE +21 -0
- package/README.md +26 -0
- package/dist/ast.d.ts +7 -0
- package/dist/ast.d.ts.map +1 -0
- package/dist/ast.js +2 -0
- package/dist/ast.js.map +1 -0
- package/dist/browser-loader.bundle.js +9 -0
- package/dist/browser-loader.d.ts +35 -0
- package/dist/browser-loader.d.ts.map +1 -0
- package/dist/browser-loader.js +83 -0
- package/dist/browser-loader.js.map +1 -0
- package/dist/browser-source.d.ts +10 -0
- package/dist/browser-source.d.ts.map +1 -0
- package/dist/browser-source.js +97 -0
- package/dist/browser-source.js.map +1 -0
- package/dist/browser.d.ts +5 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +9 -0
- package/dist/browser.js.map +1 -0
- package/dist/cli.d.ts +31 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +138 -0
- package/dist/cli.js.map +1 -0
- package/dist/component-styles-build.d.ts +11 -0
- package/dist/component-styles-build.d.ts.map +1 -0
- package/dist/component-styles-build.js +74 -0
- package/dist/component-styles-build.js.map +1 -0
- package/dist/component-styles.d.ts +37 -0
- package/dist/component-styles.d.ts.map +1 -0
- package/dist/component-styles.js +236 -0
- package/dist/component-styles.js.map +1 -0
- package/dist/contract-platform.d.ts +5 -0
- package/dist/contract-platform.d.ts.map +1 -0
- package/dist/contract-platform.js +8 -0
- package/dist/contract-platform.js.map +1 -0
- package/dist/contract.d.ts +14 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +234 -0
- package/dist/contract.js.map +1 -0
- package/dist/controller-files.d.ts +10 -0
- package/dist/controller-files.d.ts.map +1 -0
- package/dist/controller-files.js +54 -0
- package/dist/controller-files.js.map +1 -0
- package/dist/controller.d.ts +15 -0
- package/dist/controller.d.ts.map +1 -0
- package/dist/controller.js +33 -0
- package/dist/controller.js.map +1 -0
- package/dist/data.d.ts +28 -0
- package/dist/data.d.ts.map +1 -0
- package/dist/data.js +100 -0
- package/dist/data.js.map +1 -0
- package/dist/diagnostics.d.ts +11 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +13 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/expression.d.ts +80 -0
- package/dist/expression.d.ts.map +1 -0
- package/dist/expression.js +474 -0
- package/dist/expression.js.map +1 -0
- package/dist/freeze.d.ts +2 -0
- package/dist/freeze.d.ts.map +1 -0
- package/dist/freeze.js +8 -0
- package/dist/freeze.js.map +1 -0
- package/dist/generate.d.ts +11 -0
- package/dist/generate.d.ts.map +1 -0
- package/dist/generate.js +41 -0
- package/dist/generate.js.map +1 -0
- package/dist/generated/dom-properties.d.ts +8 -0
- package/dist/generated/dom-properties.d.ts.map +1 -0
- package/dist/generated/dom-properties.js +1410 -0
- package/dist/generated/dom-properties.js.map +1 -0
- package/dist/generated-runtime.d.ts +42 -0
- package/dist/generated-runtime.d.ts.map +1 -0
- package/dist/generated-runtime.js +266 -0
- package/dist/generated-runtime.js.map +1 -0
- package/dist/graph.d.ts +37 -0
- package/dist/graph.d.ts.map +1 -0
- package/dist/graph.js +81 -0
- package/dist/graph.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/language.d.ts +7 -0
- package/dist/language.d.ts.map +1 -0
- package/dist/language.js +43 -0
- package/dist/language.js.map +1 -0
- package/dist/names.d.ts +4 -0
- package/dist/names.d.ts.map +1 -0
- package/dist/names.js +19 -0
- package/dist/names.js.map +1 -0
- package/dist/node-loader.d.ts +22 -0
- package/dist/node-loader.d.ts.map +1 -0
- package/dist/node-loader.js +109 -0
- package/dist/node-loader.js.map +1 -0
- package/dist/package-config.d.ts +22 -0
- package/dist/package-config.d.ts.map +1 -0
- package/dist/package-config.js +2 -0
- package/dist/package-config.js.map +1 -0
- package/dist/package.d.ts +9 -0
- package/dist/package.d.ts.map +1 -0
- package/dist/package.js +208 -0
- package/dist/package.js.map +1 -0
- package/dist/parser.d.ts +11 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +938 -0
- package/dist/parser.js.map +1 -0
- package/dist/platform.d.ts +3 -0
- package/dist/platform.d.ts.map +1 -0
- package/dist/platform.js +26 -0
- package/dist/platform.js.map +1 -0
- package/dist/reactivity.d.ts +88 -0
- package/dist/reactivity.d.ts.map +1 -0
- package/dist/reactivity.js +594 -0
- package/dist/reactivity.js.map +1 -0
- package/dist/registry.d.ts +14 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +39 -0
- package/dist/registry.js.map +1 -0
- package/dist/resolve.d.ts +24 -0
- package/dist/resolve.d.ts.map +1 -0
- package/dist/resolve.js +65 -0
- package/dist/resolve.js.map +1 -0
- package/dist/runtime.d.ts +110 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +1926 -0
- package/dist/runtime.js.map +1 -0
- package/dist/sanitize.d.ts +8 -0
- package/dist/sanitize.d.ts.map +1 -0
- package/dist/sanitize.js +38 -0
- package/dist/sanitize.js.map +1 -0
- package/dist/source-graph.d.ts +5 -0
- package/dist/source-graph.d.ts.map +1 -0
- package/dist/source-graph.js +60 -0
- package/dist/source-graph.js.map +1 -0
- package/dist/source-parser.d.ts +4 -0
- package/dist/source-parser.d.ts.map +1 -0
- package/dist/source-parser.js +38 -0
- package/dist/source-parser.js.map +1 -0
- package/dist/source.d.ts +4 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +6 -0
- package/dist/source.js.map +1 -0
- package/dist/targets/backend.d.ts +28 -0
- package/dist/targets/backend.d.ts.map +1 -0
- package/dist/targets/backend.js +42 -0
- package/dist/targets/backend.js.map +1 -0
- package/dist/targets/docs.d.ts +3 -0
- package/dist/targets/docs.d.ts.map +1 -0
- package/dist/targets/docs.js +85 -0
- package/dist/targets/docs.js.map +1 -0
- package/dist/targets/native-reactive.d.ts +41 -0
- package/dist/targets/native-reactive.d.ts.map +1 -0
- package/dist/targets/native-reactive.js +169 -0
- package/dist/targets/native-reactive.js.map +1 -0
- package/dist/targets/shared.d.ts +15 -0
- package/dist/targets/shared.d.ts.map +1 -0
- package/dist/targets/shared.js +122 -0
- package/dist/targets/shared.js.map +1 -0
- package/dist/targets/vanilla.d.ts +6 -0
- package/dist/targets/vanilla.d.ts.map +1 -0
- package/dist/targets/vanilla.js +442 -0
- package/dist/targets/vanilla.js.map +1 -0
- package/dist/targets/vue.d.ts +3 -0
- package/dist/targets/vue.d.ts.map +1 -0
- package/dist/targets/vue.js +577 -0
- package/dist/targets/vue.js.map +1 -0
- package/dist/template.d.ts +172 -0
- package/dist/template.d.ts.map +1 -0
- package/dist/template.js +2 -0
- package/dist/template.js.map +1 -0
- package/dist/type-system.d.ts +80 -0
- package/dist/type-system.d.ts.map +1 -0
- package/dist/type-system.js +414 -0
- package/dist/type-system.js.map +1 -0
- package/dist/types.d.ts +51 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/validate.d.ts +38 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +297 -0
- package/dist/validate.js.map +1 -0
- package/dist/validation.d.ts +6 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +6 -0
- package/dist/validation.js.map +1 -0
- package/dist/validity-css.d.ts +6 -0
- package/dist/validity-css.d.ts.map +1 -0
- package/dist/validity-css.js +11 -0
- package/dist/validity-css.js.map +1 -0
- package/dist/validity.d.ts +45 -0
- package/dist/validity.d.ts.map +1 -0
- package/dist/validity.js +445 -0
- package/dist/validity.js.map +1 -0
- package/package.json +124 -0
package/dist/parser.js
ADDED
|
@@ -0,0 +1,938 @@
|
|
|
1
|
+
import { parseTypeAttribute } from "./contract.js";
|
|
2
|
+
import { fail } from "./diagnostics.js";
|
|
3
|
+
import { compileExpression, getWritablePath } from "./expression.js";
|
|
4
|
+
import { deepFreeze } from "./freeze.js";
|
|
5
|
+
import { isReservedElement, validateDefinitionElementName, validateLiteralAttributeName, validateMvpDomProperty, } from "./language.js";
|
|
6
|
+
import { componentName } from "./names.js";
|
|
7
|
+
import { isAttributeType, parseTypedValue } from "./type-system.js";
|
|
8
|
+
function isElement(node) {
|
|
9
|
+
return "tagName" in node;
|
|
10
|
+
}
|
|
11
|
+
function isText(node) {
|
|
12
|
+
return node.nodeName === "#text";
|
|
13
|
+
}
|
|
14
|
+
function sourceTag(element) {
|
|
15
|
+
return "localName" in element ? element.localName : element.tagName;
|
|
16
|
+
}
|
|
17
|
+
function sourceAttributes(element) {
|
|
18
|
+
return "attrs" in element ? element.attrs : element.attributes;
|
|
19
|
+
}
|
|
20
|
+
function sourceChildren(element) {
|
|
21
|
+
const content = sourceTag(element) === "template"
|
|
22
|
+
? element.content
|
|
23
|
+
: undefined;
|
|
24
|
+
const children = content?.childNodes ?? element.childNodes;
|
|
25
|
+
return children;
|
|
26
|
+
}
|
|
27
|
+
function sourceText(node) {
|
|
28
|
+
if ("value" in node)
|
|
29
|
+
return node.value;
|
|
30
|
+
return "nodeValue" in node ? node.nodeValue ?? "" : "";
|
|
31
|
+
}
|
|
32
|
+
function significant(nodes) {
|
|
33
|
+
const result = [];
|
|
34
|
+
for (const node of nodes) {
|
|
35
|
+
if (node.nodeName !== "#comment" && (!isText(node) || sourceText(node).trim() !== "")) {
|
|
36
|
+
result.push(node);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
function attr(element, name) {
|
|
42
|
+
return "attrs" in element
|
|
43
|
+
? element.attrs.find((item) => item.name === name)?.value
|
|
44
|
+
: element.getAttribute(name) ?? undefined;
|
|
45
|
+
}
|
|
46
|
+
function textContent(element) {
|
|
47
|
+
let text = "";
|
|
48
|
+
for (const node of sourceChildren(element)) {
|
|
49
|
+
if (isText(node))
|
|
50
|
+
text += sourceText(node);
|
|
51
|
+
}
|
|
52
|
+
return text;
|
|
53
|
+
}
|
|
54
|
+
function directElements(element, name) {
|
|
55
|
+
const matches = [];
|
|
56
|
+
for (const node of sourceChildren(element)) {
|
|
57
|
+
if (isElement(node) && sourceTag(node) === name)
|
|
58
|
+
matches.push(node);
|
|
59
|
+
}
|
|
60
|
+
return matches;
|
|
61
|
+
}
|
|
62
|
+
function validateDeclarationContent(element, source) {
|
|
63
|
+
for (const child of sourceChildren(element)) {
|
|
64
|
+
if (!isElement(child))
|
|
65
|
+
continue;
|
|
66
|
+
const name = sourceTag(child);
|
|
67
|
+
if (name === "link") {
|
|
68
|
+
fail("HL001", "External definition dependencies require the application-owned graph resolver.", source);
|
|
69
|
+
}
|
|
70
|
+
validateDefinitionElementName(name, source);
|
|
71
|
+
for (const attribute of sourceAttributes(child)) {
|
|
72
|
+
if (/^on(?!:)/i.test(attribute.name)) {
|
|
73
|
+
validateLiteralAttributeName(attribute.name, source, attribute.value);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
validateDeclarationContent(child, source);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const FLOW_NAME_RE = /^\$(?:if|each|where|sort|limit|key|with|match|when|else)$/;
|
|
80
|
+
const RAW_SINK_RE = /^(?:innerhtml|outerhtml|textcontent|innertext|srcdoc)$/;
|
|
81
|
+
const EACH_RE = /^\s*([A-Za-z_$][\w$]*)\s*(?:,\s*([A-Za-z_$][\w$]*)\s*)?\bof\b\s*(.+)$/;
|
|
82
|
+
const AS_RE = /^\s*(.+?)\s+\bas\b\s+([A-Za-z_$][\w$]*)\s*$/;
|
|
83
|
+
const EVENT_PART_RE = /^[a-z][a-z0-9-]*$/;
|
|
84
|
+
const EVENT_MODIFIER_RE = /^(?:prevent|stop|self|once|passive|capture|left|middle|right|ctrl|shift|alt|meta|exact|enter|escape|space|tab|up|down)$/;
|
|
85
|
+
const NAME_RE = /^[A-Za-z_$][A-Za-z0-9_$-]*$/;
|
|
86
|
+
function withRoots(scope, ...roots) {
|
|
87
|
+
const expanded = new Set(scope.roots);
|
|
88
|
+
for (const root of roots) {
|
|
89
|
+
if (root !== undefined)
|
|
90
|
+
expanded.add(root);
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
roots: expanded,
|
|
94
|
+
writableRoots: scope.writableRoots,
|
|
95
|
+
handlers: scope.handlers,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function compileScopedExpression(value, scope, source) {
|
|
99
|
+
const expression = compileDeclarationExpression(value, source);
|
|
100
|
+
validateCompiledExpression(expression, scope, source);
|
|
101
|
+
return expression;
|
|
102
|
+
}
|
|
103
|
+
function validateCompiledExpression(expression, scope, source) {
|
|
104
|
+
for (const dependency of expression.dependencies) {
|
|
105
|
+
const root = dependency.split(".", 1)[0];
|
|
106
|
+
if (!scope.roots.has(root)) {
|
|
107
|
+
fail("HT003", `Expression root \`${root}\` is not declared in this scope.`, source);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* A prop's target is defined by where it is bound in the markup, not restated: a
|
|
113
|
+
* `:attr="prop"` binding targets that attribute, a `.prop="prop"` binding that DOM property. A prop
|
|
114
|
+
* bound in several places targets its first binding in document order; the others only render it.
|
|
115
|
+
*/
|
|
116
|
+
function collectTargets(root, source, platform) {
|
|
117
|
+
const targets = Object.create(null);
|
|
118
|
+
const record = (name, target) => {
|
|
119
|
+
targets[name] ??= target;
|
|
120
|
+
};
|
|
121
|
+
const visit = (element) => {
|
|
122
|
+
for (const attribute of sourceAttributes(element)) {
|
|
123
|
+
if (attribute.name.startsWith(":")) {
|
|
124
|
+
if (/^[A-Za-z][A-Za-z0-9_-]*$/.test(attribute.value)) {
|
|
125
|
+
record(attribute.value, { attribute: attribute.name.slice(1).toLowerCase() });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else if (attribute.name.startsWith(".")) {
|
|
129
|
+
const key = attribute.name.slice(1).toLowerCase();
|
|
130
|
+
if (/^[A-Za-z][A-Za-z0-9_-]*$/.test(attribute.value)) {
|
|
131
|
+
const property = platform.resolveDomProperty(sourceTag(element), key) ??
|
|
132
|
+
(attribute.value.toLowerCase() === key ? attribute.value : key);
|
|
133
|
+
record(attribute.value, { property });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
for (const child of sourceChildren(element)) {
|
|
138
|
+
if (isElement(child) && sourceTag(child) !== "slot")
|
|
139
|
+
visit(child);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
visit(root);
|
|
143
|
+
return targets;
|
|
144
|
+
}
|
|
145
|
+
function readProps(group, targets, source, requireBinding = true) {
|
|
146
|
+
const props = Object.create(null);
|
|
147
|
+
const normalizedNames = Object.create(null);
|
|
148
|
+
if (group === undefined)
|
|
149
|
+
return props;
|
|
150
|
+
const elements = directElements(group, "prop");
|
|
151
|
+
elements.sort((left, right) => {
|
|
152
|
+
const leftName = attr(left, "name") ?? "";
|
|
153
|
+
const rightName = attr(right, "name") ?? "";
|
|
154
|
+
return leftName < rightName ? -1 : leftName > rightName ? 1 : 0;
|
|
155
|
+
});
|
|
156
|
+
for (const element of elements) {
|
|
157
|
+
const name = attr(element, "name");
|
|
158
|
+
if (name === undefined || name === "") {
|
|
159
|
+
fail("HC010", "A <prop> requires a `name` attribute.", source);
|
|
160
|
+
}
|
|
161
|
+
if (!/^[A-Za-z][A-Za-z0-9_-]*$/.test(name)) {
|
|
162
|
+
fail("HC010", `Invalid prop name \`${name}\`.`, source);
|
|
163
|
+
}
|
|
164
|
+
const typeAttribute = attr(element, "type");
|
|
165
|
+
if (typeAttribute === undefined || typeAttribute === "") {
|
|
166
|
+
fail("HC013", `Prop \`${name}\` requires a \`type\` attribute.`, source);
|
|
167
|
+
}
|
|
168
|
+
const normalizedName = name.toLowerCase();
|
|
169
|
+
const priorName = normalizedNames[normalizedName];
|
|
170
|
+
if (priorName !== undefined) {
|
|
171
|
+
fail("HC011", `Props \`${priorName}\` and \`${name}\` collide after lowercase normalization.`, source);
|
|
172
|
+
}
|
|
173
|
+
normalizedNames[normalizedName] = name;
|
|
174
|
+
let target = targets[name];
|
|
175
|
+
if (target === undefined && requireBinding) {
|
|
176
|
+
fail("HC018", `Prop \`${name}\` is declared but never bound in the markup.`, source);
|
|
177
|
+
}
|
|
178
|
+
target ??= { attribute: name.toLowerCase() };
|
|
179
|
+
const type = parseTypeAttribute(typeAttribute);
|
|
180
|
+
const required = attr(element, "required") !== undefined;
|
|
181
|
+
const description = textContent(element).trim();
|
|
182
|
+
if (description === "") {
|
|
183
|
+
fail("HC003", `\`props.${name}.description\` must be a non-empty string.`, source);
|
|
184
|
+
}
|
|
185
|
+
if (!isAttributeType(type)) {
|
|
186
|
+
fail("HC017", `Prop \`${name}\` cannot be written as an HTML attribute; function, unknown, and trusted content types have no text form.`, source);
|
|
187
|
+
}
|
|
188
|
+
const defaultValue = attr(element, "default");
|
|
189
|
+
if (required && defaultValue !== undefined) {
|
|
190
|
+
fail("HC019", `Required prop \`${name}\` cannot also declare a default.`, source);
|
|
191
|
+
}
|
|
192
|
+
const spec = { type, required, target, description };
|
|
193
|
+
if (defaultValue !== undefined) {
|
|
194
|
+
const parsed = parseTypedValue(defaultValue, type);
|
|
195
|
+
if (!parsed.ok)
|
|
196
|
+
fail("HC015", `Default for prop \`${name}\` does not satisfy its type.`, source);
|
|
197
|
+
spec.default = parsed.value;
|
|
198
|
+
}
|
|
199
|
+
props[name] = spec;
|
|
200
|
+
}
|
|
201
|
+
return props;
|
|
202
|
+
}
|
|
203
|
+
function readContract(wrapper, group, nativeElement, delegatedRoot, targets, source, requireBinding) {
|
|
204
|
+
const tag = attr(wrapper, "component") ?? "";
|
|
205
|
+
if (tag.trim() === "")
|
|
206
|
+
fail("HC003", "`component` must be a non-empty string.", source);
|
|
207
|
+
if (!/^[a-z][a-z0-9]*(?:-[a-z0-9]+)+$/.test(tag)) {
|
|
208
|
+
fail("HC005", "The `component` tag must be lowercase and contain a hyphen.", source);
|
|
209
|
+
}
|
|
210
|
+
const status = attr(wrapper, "status");
|
|
211
|
+
if (status !== undefined && !/^(?:early|experimental|stable|deprecated)$/.test(status)) {
|
|
212
|
+
fail("HC007", "Component `status` is not recognized.", source);
|
|
213
|
+
}
|
|
214
|
+
const summary = attr(wrapper, "summary");
|
|
215
|
+
if (summary !== undefined && summary.trim() === "")
|
|
216
|
+
fail("HC003", "`summary` must be a non-empty string.", source);
|
|
217
|
+
if (!/^[a-z][a-z0-9-]*$/.test(nativeElement) ||
|
|
218
|
+
(delegatedRoot && !/^[a-z][a-z0-9]*(?:-[a-z0-9]+)+$/.test(nativeElement))) {
|
|
219
|
+
fail("HC008", "`nativeElement` must be a lowercase HTML element or component tag.", source);
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
version: 1,
|
|
223
|
+
name: componentName(tag),
|
|
224
|
+
tag,
|
|
225
|
+
...(status === undefined ? {} : { status: status }),
|
|
226
|
+
...(summary === undefined ? {} : { summary }),
|
|
227
|
+
nativeElement,
|
|
228
|
+
props: readProps(group, targets, source, requireBinding),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function compileDeclarationExpression(value, source) {
|
|
232
|
+
try {
|
|
233
|
+
return compileExpression(value);
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
fail("HT013", `Malformed expression \`${value}\`.`, source);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
function readHandlerSteps(handler, scope, source) {
|
|
240
|
+
const steps = [];
|
|
241
|
+
for (const step of significant(sourceChildren(handler))) {
|
|
242
|
+
if (!isElement(step))
|
|
243
|
+
fail("HC023", "Handler bodies contain declarative step elements only.", source);
|
|
244
|
+
const guardSource = attr(step, "$if");
|
|
245
|
+
const guard = guardSource === undefined ? undefined : compileScopedExpression(guardSource, scope, source);
|
|
246
|
+
if (sourceTag(step) === "set") {
|
|
247
|
+
const path = attr(step, "name") ?? "";
|
|
248
|
+
const expressionSource = attr(step, ":value");
|
|
249
|
+
const literal = attr(step, "value");
|
|
250
|
+
if (path === "" || (expressionSource === undefined) === (literal === undefined)) {
|
|
251
|
+
fail("HC023", "A <set> requires `name` and exactly one of `value` or `:value`.", source);
|
|
252
|
+
}
|
|
253
|
+
compileScopedExpression(path, scope, source);
|
|
254
|
+
const writablePath = getWritablePath(path, scope.writableRoots);
|
|
255
|
+
if (writablePath === undefined) {
|
|
256
|
+
fail("HT005", `Handler write \`${path}\` is not rooted in declared state.`, source);
|
|
257
|
+
}
|
|
258
|
+
const parsed = {
|
|
259
|
+
kind: "set",
|
|
260
|
+
path,
|
|
261
|
+
writablePath,
|
|
262
|
+
value: compileScopedExpression(expressionSource ?? JSON.stringify(literal), scope, source),
|
|
263
|
+
};
|
|
264
|
+
if (guard !== undefined)
|
|
265
|
+
parsed.guard = guard;
|
|
266
|
+
steps.push(parsed);
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
if (sourceTag(step) === "dispatch") {
|
|
270
|
+
const event = attr(step, "event") ?? "";
|
|
271
|
+
if (!EVENT_PART_RE.test(event)) {
|
|
272
|
+
fail("HC023", "A <dispatch> requires a valid `event` name.", source);
|
|
273
|
+
}
|
|
274
|
+
const expressionSource = attr(step, ":value");
|
|
275
|
+
const literal = attr(step, "value");
|
|
276
|
+
if (expressionSource !== undefined && literal !== undefined) {
|
|
277
|
+
fail("HC023", "A <dispatch> may declare only one of `value` or `:value`.", source);
|
|
278
|
+
}
|
|
279
|
+
const value = expressionSource === undefined && literal === undefined
|
|
280
|
+
? undefined
|
|
281
|
+
: compileScopedExpression(expressionSource ?? JSON.stringify(literal), scope, source);
|
|
282
|
+
const parsed = {
|
|
283
|
+
kind: "dispatch",
|
|
284
|
+
event,
|
|
285
|
+
};
|
|
286
|
+
if (value !== undefined)
|
|
287
|
+
parsed.value = value;
|
|
288
|
+
if (guard !== undefined)
|
|
289
|
+
parsed.guard = guard;
|
|
290
|
+
steps.push(parsed);
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
if (sourceTag(step) === "validate" || sourceTag(step) === "focus") {
|
|
294
|
+
const target = attr(step, "target") ?? attr(step, "ref") ?? attr(step, "name") ?? "";
|
|
295
|
+
if (!NAME_RE.test(target)) {
|
|
296
|
+
fail("HC023", `<${sourceTag(step)}> requires a valid target reference.`, source);
|
|
297
|
+
}
|
|
298
|
+
const parsed = {
|
|
299
|
+
kind: sourceTag(step),
|
|
300
|
+
target,
|
|
301
|
+
};
|
|
302
|
+
if (guard !== undefined)
|
|
303
|
+
parsed.guard = guard;
|
|
304
|
+
steps.push(parsed);
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
fail("HC023", `<${sourceTag(step)}> is not a recognized handler step.`, source);
|
|
308
|
+
}
|
|
309
|
+
return steps;
|
|
310
|
+
}
|
|
311
|
+
function readDeclarations(group, contract, source) {
|
|
312
|
+
const declarations = [];
|
|
313
|
+
const roots = new Set(Object.keys(contract.props));
|
|
314
|
+
const writableRoots = new Set();
|
|
315
|
+
const handlers = new Set();
|
|
316
|
+
if (group === undefined)
|
|
317
|
+
return { declarations, scope: { roots, writableRoots, handlers } };
|
|
318
|
+
const elements = [];
|
|
319
|
+
const names = new Set();
|
|
320
|
+
const eventNames = new Set();
|
|
321
|
+
for (const node of sourceChildren(group)) {
|
|
322
|
+
if (!isElement(node))
|
|
323
|
+
continue;
|
|
324
|
+
const element = node;
|
|
325
|
+
elements.push(element);
|
|
326
|
+
const kind = sourceTag(element);
|
|
327
|
+
if (!/^(?:prop|state|computed|data|handler|event|method)$/.test(kind)) {
|
|
328
|
+
fail("HC021", `<${kind}> is not a recognized definition declaration.`, source);
|
|
329
|
+
}
|
|
330
|
+
const name = attr(element, "name") ?? "";
|
|
331
|
+
if (name === "")
|
|
332
|
+
fail("HC010", `A <${kind}> requires a \`name\` attribute.`, source);
|
|
333
|
+
if (kind === "event") {
|
|
334
|
+
if (eventNames.has(name))
|
|
335
|
+
fail("HC020", `Event \`${name}\` is declared more than once.`, source);
|
|
336
|
+
eventNames.add(name);
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
if (names.has(name)) {
|
|
340
|
+
fail("HC020", `Declaration \`${name}\` collides in the flat component scope.`, source);
|
|
341
|
+
}
|
|
342
|
+
names.add(name);
|
|
343
|
+
roots.add(name);
|
|
344
|
+
if (kind === "state")
|
|
345
|
+
writableRoots.add(name);
|
|
346
|
+
else if (kind === "handler")
|
|
347
|
+
handlers.add(name);
|
|
348
|
+
}
|
|
349
|
+
const scope = { roots, writableRoots, handlers };
|
|
350
|
+
for (const element of elements) {
|
|
351
|
+
const kind = sourceTag(element);
|
|
352
|
+
if (kind === "prop")
|
|
353
|
+
continue;
|
|
354
|
+
const name = attr(element, "name");
|
|
355
|
+
if (kind === "state") {
|
|
356
|
+
const expressionSource = attr(element, ":value");
|
|
357
|
+
const literal = attr(element, "value");
|
|
358
|
+
if (expressionSource !== undefined && literal !== undefined) {
|
|
359
|
+
fail("HC013", `<state name="${name}"> may declare only one of \`value\` or \`:value\`.`, source);
|
|
360
|
+
}
|
|
361
|
+
const expression = expressionSource === undefined && literal === undefined
|
|
362
|
+
? undefined
|
|
363
|
+
: compileScopedExpression(expressionSource ?? JSON.stringify(literal), scope, source);
|
|
364
|
+
const declaration = {
|
|
365
|
+
kind,
|
|
366
|
+
name,
|
|
367
|
+
};
|
|
368
|
+
if (literal !== undefined)
|
|
369
|
+
declaration.value = literal;
|
|
370
|
+
if (expression !== undefined)
|
|
371
|
+
declaration.expression = expression;
|
|
372
|
+
declarations.push(declaration);
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
if (kind === "computed") {
|
|
376
|
+
const expressionSource = attr(element, "from");
|
|
377
|
+
if (expressionSource === undefined || expressionSource === "") {
|
|
378
|
+
fail("HC013", `<computed name="${name}"> requires a \`from\` expression.`, source);
|
|
379
|
+
}
|
|
380
|
+
declarations.push({
|
|
381
|
+
kind,
|
|
382
|
+
name,
|
|
383
|
+
expression: compileScopedExpression(expressionSource, scope, source),
|
|
384
|
+
});
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
if (kind === "data") {
|
|
388
|
+
const dataSource = attr(element, "src");
|
|
389
|
+
const dataType = attr(element, "type");
|
|
390
|
+
const dataDebounce = attr(element, "debounce");
|
|
391
|
+
const dataPoll = attr(element, "poll");
|
|
392
|
+
const parameters = [];
|
|
393
|
+
const parameterNames = new Set();
|
|
394
|
+
for (const parameter of directElements(element, "param")) {
|
|
395
|
+
const parameterName = attr(parameter, "name") ?? "";
|
|
396
|
+
const expressionSource = attr(parameter, ":value");
|
|
397
|
+
if (!NAME_RE.test(parameterName) || expressionSource === undefined) {
|
|
398
|
+
fail("HC024", "A data <param> requires a valid `name` and a `:value` expression.", source);
|
|
399
|
+
}
|
|
400
|
+
if (parameterNames.has(parameterName)) {
|
|
401
|
+
fail("HC024", `Data source \`${name}\` repeats a parameter name.`, source);
|
|
402
|
+
}
|
|
403
|
+
parameterNames.add(parameterName);
|
|
404
|
+
parameters.push({
|
|
405
|
+
name: parameterName,
|
|
406
|
+
expression: compileScopedExpression(expressionSource, scope, source),
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
const declaration = {
|
|
410
|
+
kind,
|
|
411
|
+
name,
|
|
412
|
+
parameters,
|
|
413
|
+
};
|
|
414
|
+
if (dataSource !== undefined)
|
|
415
|
+
declaration.source = dataSource;
|
|
416
|
+
if (dataType !== undefined)
|
|
417
|
+
declaration.type = dataType;
|
|
418
|
+
if (dataDebounce !== undefined)
|
|
419
|
+
declaration.debounce = dataDebounce;
|
|
420
|
+
if (dataPoll !== undefined)
|
|
421
|
+
declaration.poll = dataPoll;
|
|
422
|
+
declarations.push(declaration);
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
if (kind === "event") {
|
|
426
|
+
declarations.push({
|
|
427
|
+
kind,
|
|
428
|
+
name,
|
|
429
|
+
type: attr(element, "type") ?? "object",
|
|
430
|
+
bubbles: attr(element, "bubbles") !== "false",
|
|
431
|
+
composed: attr(element, "composed") !== "false",
|
|
432
|
+
cancelable: attr(element, "cancelable") === "true",
|
|
433
|
+
});
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
if (kind === "method") {
|
|
437
|
+
declarations.push({
|
|
438
|
+
kind,
|
|
439
|
+
name,
|
|
440
|
+
exportName: attr(element, "export") ?? name,
|
|
441
|
+
returns: attr(element, "returns") ?? "undefined",
|
|
442
|
+
});
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
declarations.push({
|
|
446
|
+
kind: "handler",
|
|
447
|
+
name,
|
|
448
|
+
steps: readHandlerSteps(element, scope, source),
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
for (const declaration of declarations) {
|
|
452
|
+
if (declaration.kind !== "handler")
|
|
453
|
+
continue;
|
|
454
|
+
for (const step of declaration.steps) {
|
|
455
|
+
if (step.kind === "dispatch" && !eventNames.has(step.event)) {
|
|
456
|
+
fail("HC023", `Handler \`${declaration.name}\` dispatches undeclared component event \`${step.event}\`.`, source);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return { declarations, scope };
|
|
461
|
+
}
|
|
462
|
+
function parseAttributes(sourceAttributes, tagName, contract, scope, source, platform) {
|
|
463
|
+
const parsed = [];
|
|
464
|
+
for (const attribute of sourceAttributes) {
|
|
465
|
+
if (attribute.name.startsWith("bind:")) {
|
|
466
|
+
const name = attribute.name.slice("bind:".length).toLowerCase();
|
|
467
|
+
if (name === "" || RAW_SINK_RE.test(name)) {
|
|
468
|
+
fail("HT007", `Two-way binding cannot target \`${name || attribute.name}\`.`, source);
|
|
469
|
+
}
|
|
470
|
+
const expressionPlan = compileScopedExpression(attribute.value, scope, source);
|
|
471
|
+
const writablePath = getWritablePath(attribute.value, scope.writableRoots);
|
|
472
|
+
if (writablePath === undefined) {
|
|
473
|
+
fail("HT005", `\`${attribute.value}\` is not a writable state-rooted path.`, source);
|
|
474
|
+
}
|
|
475
|
+
parsed.push({
|
|
476
|
+
kind: "attribute",
|
|
477
|
+
name,
|
|
478
|
+
expression: attribute.value,
|
|
479
|
+
expressionPlan,
|
|
480
|
+
twoWay: true,
|
|
481
|
+
writablePath,
|
|
482
|
+
});
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
if (attribute.name.startsWith("class:") || attribute.name.startsWith("style:")) {
|
|
486
|
+
const target = attribute.name.startsWith("class:") ? "class" : "style";
|
|
487
|
+
const name = attribute.name.slice(target.length + 1);
|
|
488
|
+
const valid = target === "class"
|
|
489
|
+
? name !== "" && !/\s/.test(name)
|
|
490
|
+
: /^(?:--[a-z0-9_-]+|[a-z][a-z0-9-]*)$/.test(name);
|
|
491
|
+
if (!valid) {
|
|
492
|
+
fail("HT020", `\`${attribute.name}\` does not name a valid ${target} binding target.`, source);
|
|
493
|
+
}
|
|
494
|
+
parsed.push({
|
|
495
|
+
kind: "attribute",
|
|
496
|
+
name,
|
|
497
|
+
expression: attribute.value,
|
|
498
|
+
expressionPlan: compileScopedExpression(attribute.value, scope, source),
|
|
499
|
+
target,
|
|
500
|
+
});
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
if (attribute.name.startsWith("$")) {
|
|
504
|
+
const name = attribute.name.slice(1).toLowerCase();
|
|
505
|
+
if (name !== "value" && name !== "html") {
|
|
506
|
+
fail("HT012", `\`$${name}\` is not a known content directive.`, source);
|
|
507
|
+
}
|
|
508
|
+
parsed.push({
|
|
509
|
+
kind: "directive",
|
|
510
|
+
name,
|
|
511
|
+
expression: attribute.value,
|
|
512
|
+
expressionPlan: compileScopedExpression(attribute.value, scope, source),
|
|
513
|
+
});
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
if (attribute.name.startsWith(":")) {
|
|
517
|
+
const name = attribute.name.slice(1).toLowerCase();
|
|
518
|
+
if (RAW_SINK_RE.test(name)) {
|
|
519
|
+
fail("HT007", `\`:${name}\` cannot bind a raw content sink.`, source);
|
|
520
|
+
}
|
|
521
|
+
const expressionPlan = compileScopedExpression(attribute.value, scope, source);
|
|
522
|
+
parsed.push({ kind: "attribute", name, expression: attribute.value, expressionPlan });
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
if (attribute.name.startsWith(".")) {
|
|
526
|
+
const key = attribute.name.slice(1).toLowerCase();
|
|
527
|
+
const expressionPlan = compileScopedExpression(attribute.value, scope, source);
|
|
528
|
+
// Property bindings reach native DOM properties only; component inputs are attributes.
|
|
529
|
+
const name = platform.resolveDomProperty(tagName, key);
|
|
530
|
+
if (name === undefined) {
|
|
531
|
+
fail("HP001", `\`${key}\` is not a native property of <${tagName}>.`, source);
|
|
532
|
+
}
|
|
533
|
+
validateMvpDomProperty(name, source);
|
|
534
|
+
parsed.push({ kind: "property", key, name, expression: attribute.value, expressionPlan });
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
validateLiteralAttributeName(attribute.name, source, attribute.value);
|
|
538
|
+
parsed.push({ kind: "literal", name: attribute.name, value: attribute.value });
|
|
539
|
+
}
|
|
540
|
+
return parsed;
|
|
541
|
+
}
|
|
542
|
+
function parseEvents(sourceAttributes, scope, source) {
|
|
543
|
+
if (sourceAttributes === undefined)
|
|
544
|
+
return undefined;
|
|
545
|
+
const events = [];
|
|
546
|
+
for (const attribute of sourceAttributes) {
|
|
547
|
+
const [name = "", ...modifiers] = attribute.name.slice("on:".length).split(".");
|
|
548
|
+
if (!EVENT_PART_RE.test(name) || modifiers.some((modifier) => !EVENT_PART_RE.test(modifier))) {
|
|
549
|
+
fail("HT010", `\`${attribute.name}\` is not a valid declarative event binding.`, source);
|
|
550
|
+
}
|
|
551
|
+
for (let index = 0; index < modifiers.length; index += 1) {
|
|
552
|
+
const modifier = modifiers[index];
|
|
553
|
+
if (modifiers.indexOf(modifier) !== index) {
|
|
554
|
+
fail("HT010", `\`${attribute.name}\` repeats an event modifier.`, source);
|
|
555
|
+
}
|
|
556
|
+
if (!EVENT_MODIFIER_RE.test(modifier)) {
|
|
557
|
+
fail("HT010", `\`${attribute.name}\` contains an unsupported event modifier.`, source);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
if (modifiers.includes("passive") && modifiers.includes("prevent")) {
|
|
561
|
+
fail("HT010", `\`${attribute.name}\` cannot combine passive and prevent.`, source);
|
|
562
|
+
}
|
|
563
|
+
if (!scope.handlers.has(attribute.value)) {
|
|
564
|
+
fail("HT010", `Event binding \`${attribute.name}\` names undeclared handler \`${attribute.value}\`.`, source);
|
|
565
|
+
}
|
|
566
|
+
events.push({ name, handler: attribute.value, modifiers });
|
|
567
|
+
}
|
|
568
|
+
return events;
|
|
569
|
+
}
|
|
570
|
+
function parseRef(name, refs, source) {
|
|
571
|
+
if (name === undefined)
|
|
572
|
+
return undefined;
|
|
573
|
+
if (!NAME_RE.test(name))
|
|
574
|
+
fail("HT019", "`$ref` requires a valid non-empty name.", source);
|
|
575
|
+
if (refs.has(name))
|
|
576
|
+
fail("HT019", `Reference \`${name}\` is duplicated.`, source);
|
|
577
|
+
refs.add(name);
|
|
578
|
+
return name;
|
|
579
|
+
}
|
|
580
|
+
function extractFlow(values, scope, source) {
|
|
581
|
+
if (values === undefined)
|
|
582
|
+
return undefined;
|
|
583
|
+
const structural = [];
|
|
584
|
+
const eachModifiers = [];
|
|
585
|
+
if (values.$if !== undefined)
|
|
586
|
+
structural.push("$if");
|
|
587
|
+
if (values.$each !== undefined)
|
|
588
|
+
structural.push("$each");
|
|
589
|
+
if (values.$with !== undefined)
|
|
590
|
+
structural.push("$with");
|
|
591
|
+
if (values.$match !== undefined)
|
|
592
|
+
structural.push("$match");
|
|
593
|
+
if (values.$when !== undefined)
|
|
594
|
+
structural.push("$when");
|
|
595
|
+
if (values.$else !== undefined)
|
|
596
|
+
structural.push("$else");
|
|
597
|
+
if (values.$where !== undefined)
|
|
598
|
+
eachModifiers.push("$where");
|
|
599
|
+
if (values.$sort !== undefined)
|
|
600
|
+
eachModifiers.push("$sort");
|
|
601
|
+
if (values.$limit !== undefined)
|
|
602
|
+
eachModifiers.push("$limit");
|
|
603
|
+
if (values.$key !== undefined)
|
|
604
|
+
eachModifiers.push("$key");
|
|
605
|
+
if (structural.length > 1) {
|
|
606
|
+
fail("HT014", `An element carries one structural directive; found ${structural.join(", ")}.`, source);
|
|
607
|
+
}
|
|
608
|
+
const eachSource = values.$each;
|
|
609
|
+
if (eachSource === undefined && eachModifiers.length > 0) {
|
|
610
|
+
fail("HT014", `${eachModifiers.join(", ")} may only modify \`$each\`.`, source);
|
|
611
|
+
}
|
|
612
|
+
const ifSource = values.$if;
|
|
613
|
+
if (ifSource !== undefined) {
|
|
614
|
+
const test = ifSource;
|
|
615
|
+
return { kind: "if", test, testPlan: compileScopedExpression(test, scope, source) };
|
|
616
|
+
}
|
|
617
|
+
const withSource = values.$with;
|
|
618
|
+
if (withSource !== undefined) {
|
|
619
|
+
const match = AS_RE.exec(withSource);
|
|
620
|
+
if (match === null)
|
|
621
|
+
fail("HT015", "`$with` must be written `expr as name`.", source);
|
|
622
|
+
return {
|
|
623
|
+
kind: "with",
|
|
624
|
+
expr: match[1],
|
|
625
|
+
expressionPlan: compileScopedExpression(match[1], scope, source),
|
|
626
|
+
alias: match[2],
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
if (eachSource !== undefined) {
|
|
630
|
+
const match = EACH_RE.exec(eachSource);
|
|
631
|
+
if (match === null) {
|
|
632
|
+
fail("HT016", "`$each` must be written `item of items` (optionally `item, i of items`).", source);
|
|
633
|
+
}
|
|
634
|
+
const item = match[1];
|
|
635
|
+
const index = match[2];
|
|
636
|
+
if (scope.roots.has(item) || (index !== undefined && scope.roots.has(index))) {
|
|
637
|
+
fail("HC020", "Loop locals may not collide with an existing scope name.", source);
|
|
638
|
+
}
|
|
639
|
+
const localScope = withRoots(scope, item, index, "loop");
|
|
640
|
+
const result = {
|
|
641
|
+
kind: "each",
|
|
642
|
+
item,
|
|
643
|
+
list: match[3],
|
|
644
|
+
listPlan: compileScopedExpression(match[3], scope, source),
|
|
645
|
+
};
|
|
646
|
+
if (index !== undefined)
|
|
647
|
+
result.index = index;
|
|
648
|
+
const where = values.$where;
|
|
649
|
+
if (where !== undefined) {
|
|
650
|
+
result.where = where;
|
|
651
|
+
result.wherePlan = compileScopedExpression(where, localScope, source);
|
|
652
|
+
}
|
|
653
|
+
if (values.$sort !== undefined)
|
|
654
|
+
result.sort = values.$sort;
|
|
655
|
+
const limit = values.$limit;
|
|
656
|
+
if (limit !== undefined) {
|
|
657
|
+
result.limit = limit;
|
|
658
|
+
result.limitPlan = compileScopedExpression(limit, localScope, source);
|
|
659
|
+
}
|
|
660
|
+
const key = values.$key;
|
|
661
|
+
if (key !== undefined) {
|
|
662
|
+
result.key = key;
|
|
663
|
+
result.keyPlan = compileScopedExpression(key, localScope, source);
|
|
664
|
+
}
|
|
665
|
+
return result;
|
|
666
|
+
}
|
|
667
|
+
const matchSource = values.$match;
|
|
668
|
+
if (matchSource !== undefined) {
|
|
669
|
+
const raw = matchSource.trim();
|
|
670
|
+
if (raw === "")
|
|
671
|
+
return { kind: "match" };
|
|
672
|
+
const match = AS_RE.exec(raw);
|
|
673
|
+
if (match === null)
|
|
674
|
+
fail("HT017", "`$match` scope must be written `expr as name`.", source);
|
|
675
|
+
return {
|
|
676
|
+
kind: "match",
|
|
677
|
+
expr: match[1],
|
|
678
|
+
expressionPlan: compileScopedExpression(match[1], scope, source),
|
|
679
|
+
alias: match[2],
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
const whenSource = values.$when;
|
|
683
|
+
if (whenSource !== undefined) {
|
|
684
|
+
const test = whenSource;
|
|
685
|
+
return { kind: "when", test, testPlan: compileScopedExpression(test, scope, source) };
|
|
686
|
+
}
|
|
687
|
+
if (values.$else !== undefined)
|
|
688
|
+
return { kind: "else" };
|
|
689
|
+
return undefined;
|
|
690
|
+
}
|
|
691
|
+
function parseElement(element, contract, scope, source, slotState, platform) {
|
|
692
|
+
const tagName = sourceTag(element);
|
|
693
|
+
if (isReservedElement(tagName)) {
|
|
694
|
+
fail("HT009", `<${tagName}> is reserved but not supported by this profile.`, source);
|
|
695
|
+
}
|
|
696
|
+
validateDefinitionElementName(tagName, source);
|
|
697
|
+
let flowValues;
|
|
698
|
+
const bindingAttributes = [];
|
|
699
|
+
let eventAttributes;
|
|
700
|
+
let refName;
|
|
701
|
+
for (const attribute of sourceAttributes(element)) {
|
|
702
|
+
if (FLOW_NAME_RE.test(attribute.name))
|
|
703
|
+
(flowValues ??= {})[attribute.name] = attribute.value;
|
|
704
|
+
else if (attribute.name === "$ref")
|
|
705
|
+
refName = attribute.value;
|
|
706
|
+
else if (attribute.name.startsWith("on:"))
|
|
707
|
+
(eventAttributes ??= []).push(attribute);
|
|
708
|
+
else if (attribute.name !== "as")
|
|
709
|
+
bindingAttributes.push(attribute);
|
|
710
|
+
}
|
|
711
|
+
const flow = extractFlow(flowValues, scope, source);
|
|
712
|
+
const nodeScope = flow?.kind === "each"
|
|
713
|
+
? withRoots(scope, flow.item, flow.index, "loop")
|
|
714
|
+
: flow?.kind === "with" || (flow?.kind === "match" && flow.alias !== undefined)
|
|
715
|
+
? withRoots(scope, flow.alias)
|
|
716
|
+
: scope;
|
|
717
|
+
const attributes = parseAttributes(bindingAttributes, tagName, contract, nodeScope, source, platform);
|
|
718
|
+
const events = parseEvents(eventAttributes, nodeScope, source);
|
|
719
|
+
const ref = parseRef(refName, slotState.refs, source);
|
|
720
|
+
const children = [];
|
|
721
|
+
const childNodes = sourceChildren(element);
|
|
722
|
+
for (const child of childNodes) {
|
|
723
|
+
if (child.nodeName === "#comment")
|
|
724
|
+
continue;
|
|
725
|
+
if (isText(child)) {
|
|
726
|
+
const value = sourceText(child);
|
|
727
|
+
if (value.trim() !== "")
|
|
728
|
+
children.push({ kind: "text", value });
|
|
729
|
+
continue;
|
|
730
|
+
}
|
|
731
|
+
if (!isElement(child))
|
|
732
|
+
continue;
|
|
733
|
+
if (sourceTag(child) === "slot") {
|
|
734
|
+
const name = attr(child, "name");
|
|
735
|
+
const nameExpression = attr(child, ":name");
|
|
736
|
+
if (name !== undefined && nameExpression !== undefined) {
|
|
737
|
+
fail("HT008", "A slot cannot declare both `name` and `:name`.", source);
|
|
738
|
+
}
|
|
739
|
+
for (const attribute of sourceAttributes(child)) {
|
|
740
|
+
if (attribute.name !== "name" && attribute.name !== ":name") {
|
|
741
|
+
fail("HT008", "A slot has an unsupported attribute.", source);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
if (name === undefined && nameExpression === undefined) {
|
|
745
|
+
slotState.defaults += 1;
|
|
746
|
+
if (slotState.defaults > 1)
|
|
747
|
+
fail("HT008", "A component may declare one default slot.", source);
|
|
748
|
+
}
|
|
749
|
+
if (name !== undefined) {
|
|
750
|
+
if (name === "" || slotState.names.has(name)) {
|
|
751
|
+
fail("HT008", `Slot name \`${name}\` is empty or duplicated.`, source);
|
|
752
|
+
}
|
|
753
|
+
slotState.names.add(name);
|
|
754
|
+
}
|
|
755
|
+
const fallback = [];
|
|
756
|
+
for (const fallbackNode of sourceChildren(child)) {
|
|
757
|
+
if (fallbackNode.nodeName === "#comment")
|
|
758
|
+
continue;
|
|
759
|
+
if (isText(fallbackNode)) {
|
|
760
|
+
const value = sourceText(fallbackNode);
|
|
761
|
+
if (value.trim() !== "")
|
|
762
|
+
fallback.push({ kind: "text", value });
|
|
763
|
+
}
|
|
764
|
+
else if (isElement(fallbackNode)) {
|
|
765
|
+
fallback.push(parseElement(fallbackNode, contract, nodeScope, source, slotState, platform));
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
const dynamic = nameExpression !== undefined;
|
|
769
|
+
const slotContract = {
|
|
770
|
+
dynamic,
|
|
771
|
+
required: fallback.length === 0,
|
|
772
|
+
};
|
|
773
|
+
if (name !== undefined)
|
|
774
|
+
slotContract.name = name;
|
|
775
|
+
slotState.contracts.push(slotContract);
|
|
776
|
+
if (name === undefined && nameExpression === undefined && fallback.length === 0) {
|
|
777
|
+
children.push({ kind: "slot" });
|
|
778
|
+
}
|
|
779
|
+
else {
|
|
780
|
+
const slot = {
|
|
781
|
+
kind: "slot",
|
|
782
|
+
fallback,
|
|
783
|
+
};
|
|
784
|
+
if (name !== undefined)
|
|
785
|
+
slot.name = name;
|
|
786
|
+
if (nameExpression !== undefined) {
|
|
787
|
+
slot.nameExpression = compileScopedExpression(nameExpression, nodeScope, source);
|
|
788
|
+
}
|
|
789
|
+
children.push(slot);
|
|
790
|
+
}
|
|
791
|
+
continue;
|
|
792
|
+
}
|
|
793
|
+
children.push(parseElement(child, contract, nodeScope, source, slotState, platform));
|
|
794
|
+
}
|
|
795
|
+
if (attributes.some((binding) => binding.kind === "directive") && children.length > 0) {
|
|
796
|
+
fail("HT006", "A content directive cannot coexist with children.", source);
|
|
797
|
+
}
|
|
798
|
+
if (attributes.some((binding) => binding.kind === "property" && binding.name === "textContent") &&
|
|
799
|
+
children.length > 0) {
|
|
800
|
+
fail("HT006", "A content-replacing property binding cannot coexist with children.", source);
|
|
801
|
+
}
|
|
802
|
+
if (flow?.kind === "match") {
|
|
803
|
+
let remainingArms = 0;
|
|
804
|
+
for (const child of children) {
|
|
805
|
+
if (child.kind === "element")
|
|
806
|
+
remainingArms += 1;
|
|
807
|
+
}
|
|
808
|
+
let elseSeen = false;
|
|
809
|
+
for (const arm of children) {
|
|
810
|
+
if (arm.kind !== "element")
|
|
811
|
+
continue;
|
|
812
|
+
remainingArms -= 1;
|
|
813
|
+
if (arm.flow?.kind === "when") {
|
|
814
|
+
if (elseSeen)
|
|
815
|
+
fail("HT018", "A `$when` arm may not follow `$else`.", source);
|
|
816
|
+
}
|
|
817
|
+
else if (arm.flow?.kind === "else") {
|
|
818
|
+
if (elseSeen)
|
|
819
|
+
fail("HT018", "A `$match` has at most one `$else`.", source);
|
|
820
|
+
elseSeen = true;
|
|
821
|
+
if (remainingArms !== 0)
|
|
822
|
+
fail("HT018", "`$else` must be the last arm.", source);
|
|
823
|
+
}
|
|
824
|
+
else {
|
|
825
|
+
fail("HT018", "Every direct child of a `$match` must be a `$when` or `$else` arm.", source);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
const parsed = {
|
|
830
|
+
kind: "element",
|
|
831
|
+
name: tagName,
|
|
832
|
+
attributes,
|
|
833
|
+
children,
|
|
834
|
+
};
|
|
835
|
+
if (flow !== undefined)
|
|
836
|
+
parsed.flow = flow;
|
|
837
|
+
if (events !== undefined)
|
|
838
|
+
parsed.events = events;
|
|
839
|
+
if (ref !== undefined)
|
|
840
|
+
parsed.ref = ref;
|
|
841
|
+
return parsed;
|
|
842
|
+
}
|
|
843
|
+
export function parseComponentNodes(childNodes, source, platform) {
|
|
844
|
+
const topLevel = significant(childNodes);
|
|
845
|
+
if (topLevel.length !== 1 ||
|
|
846
|
+
!isElement(topLevel[0]) ||
|
|
847
|
+
sourceTag(topLevel[0]) !== "template" ||
|
|
848
|
+
attr(topLevel[0], "component") === undefined) {
|
|
849
|
+
fail("HS001", "A source must contain exactly one top-level <template component>.", source);
|
|
850
|
+
}
|
|
851
|
+
const wrapper = topLevel[0];
|
|
852
|
+
// A <template>'s children live in its content fragment, inert and unrendered.
|
|
853
|
+
let declarationGroup;
|
|
854
|
+
let legacyProps = false;
|
|
855
|
+
let style;
|
|
856
|
+
let root;
|
|
857
|
+
for (const node of sourceChildren(wrapper)) {
|
|
858
|
+
if (node.nodeName === "#comment" || (isText(node) && sourceText(node).trim() === ""))
|
|
859
|
+
continue;
|
|
860
|
+
if (!isElement(node)) {
|
|
861
|
+
fail("HT001", "A component's markup must be exactly one element root.", source);
|
|
862
|
+
}
|
|
863
|
+
const name = sourceTag(node);
|
|
864
|
+
if (name === "props" || name === "defs") {
|
|
865
|
+
if (declarationGroup !== undefined) {
|
|
866
|
+
fail("HS002", "A component has one optional declaration group, one markup root, and one optional <style>.", source);
|
|
867
|
+
}
|
|
868
|
+
declarationGroup = node;
|
|
869
|
+
legacyProps = name === "props";
|
|
870
|
+
validateDeclarationContent(node, source);
|
|
871
|
+
}
|
|
872
|
+
else if (name === "style") {
|
|
873
|
+
if (style !== undefined) {
|
|
874
|
+
fail("HS002", "A component has one optional declaration group, one markup root, and one optional <style>.", source);
|
|
875
|
+
}
|
|
876
|
+
style = node;
|
|
877
|
+
}
|
|
878
|
+
else if (root === undefined) {
|
|
879
|
+
root = node;
|
|
880
|
+
}
|
|
881
|
+
else {
|
|
882
|
+
fail("HT001", "A component's markup must be exactly one element root.", source);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
if (root === undefined) {
|
|
886
|
+
fail("HT001", "A component's markup must be exactly one element root.", source);
|
|
887
|
+
}
|
|
888
|
+
const rootName = sourceTag(root);
|
|
889
|
+
const rootChoices = (attr(root, "as") ?? rootName).split("|");
|
|
890
|
+
for (let index = rootChoices.length - 1; index >= 0; index -= 1) {
|
|
891
|
+
rootChoices[index] = rootChoices[index].trim();
|
|
892
|
+
if (rootChoices[index] === "")
|
|
893
|
+
rootChoices.splice(index, 1);
|
|
894
|
+
}
|
|
895
|
+
const delegatedRoot = !platform.isNativeElement(rootName);
|
|
896
|
+
if (delegatedRoot && attr(root, "as") !== undefined) {
|
|
897
|
+
fail("HT021", "A delegated component root cannot also declare native `as` choices.", source);
|
|
898
|
+
}
|
|
899
|
+
if (!delegatedRoot &&
|
|
900
|
+
(rootChoices.length === 0 ||
|
|
901
|
+
!rootChoices.includes(rootName) ||
|
|
902
|
+
rootChoices.some((choice, index) => rootChoices.indexOf(choice) !== index) ||
|
|
903
|
+
rootChoices.some((choice) => !platform.isNativeElement(choice)))) {
|
|
904
|
+
fail("HT021", "A polymorphic root must list unique native choices including its markup root.", source);
|
|
905
|
+
}
|
|
906
|
+
const targets = collectTargets(root, source, platform);
|
|
907
|
+
const contract = readContract(wrapper, declarationGroup, rootName, delegatedRoot, targets, source, legacyProps);
|
|
908
|
+
const { declarations, scope } = readDeclarations(legacyProps ? undefined : declarationGroup, contract, source);
|
|
909
|
+
const slotState = {
|
|
910
|
+
defaults: 0,
|
|
911
|
+
names: new Set(),
|
|
912
|
+
contracts: [],
|
|
913
|
+
refs: new Set(),
|
|
914
|
+
};
|
|
915
|
+
const template = parseElement(root, contract, scope, source, slotState, platform);
|
|
916
|
+
const controller = attr(wrapper, "controller");
|
|
917
|
+
if (controller === "")
|
|
918
|
+
fail("HC022", "A controller specifier cannot be empty.", source);
|
|
919
|
+
const definition = {
|
|
920
|
+
source: { file: source },
|
|
921
|
+
contract,
|
|
922
|
+
template,
|
|
923
|
+
css: style === undefined ? "" : textContent(style).trim(),
|
|
924
|
+
declarations,
|
|
925
|
+
slots: slotState.contracts,
|
|
926
|
+
root: delegatedRoot
|
|
927
|
+
? { kind: "component", tag: rootName }
|
|
928
|
+
: {
|
|
929
|
+
kind: "native",
|
|
930
|
+
element: rootName,
|
|
931
|
+
choices: rootChoices,
|
|
932
|
+
},
|
|
933
|
+
};
|
|
934
|
+
if (controller !== undefined)
|
|
935
|
+
definition.controller = controller;
|
|
936
|
+
return deepFreeze(definition);
|
|
937
|
+
}
|
|
938
|
+
//# sourceMappingURL=parser.js.map
|