@b9g/crank 0.5.7 → 0.6.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/jsx-tag.js CHANGED
@@ -1,428 +1,430 @@
1
1
  /// <reference types="jsx-tag.d.ts" />
2
2
  import { createElement } from './crank.js';
3
3
 
4
- const cache = new Map();
5
- function jsx(spans, ...expressions) {
6
- const key = JSON.stringify(spans.raw);
7
- let parseResult = cache.get(key);
8
- if (parseResult == null) {
9
- parseResult = parse(spans.raw);
10
- cache.set(key, parseResult);
11
- }
12
- const { element, targets } = parseResult;
13
- for (let i = 0; i < expressions.length; i++) {
14
- const exp = expressions[i];
15
- const target = targets[i];
16
- if (target) {
17
- if (target.type === "error") {
18
- throw new SyntaxError(target.message.replace("${}", formatTagForError(exp)));
19
- }
20
- target.value = exp;
21
- }
22
- }
23
- return build(element);
24
- }
25
- /**
26
- * Matches first significant character in children mode.
27
- *
28
- * Group 1: newline
29
- * Group 2: comment
30
- * Group 3: tag
31
- * Group 4: closing slash
32
- * Group 5: tag name
33
- *
34
- * The comment group must appear first because the tag group can potentially
35
- * match a comment, so that we can handle tag expressions where we’ve reached
36
- * the end of a span.
37
- */
38
- const CHILDREN_RE = /((?:\r|\n|\r\n)\s*)|(<!--[\S\s]*?(?:-->|$))|(<\s*(\/{0,2})\s*([-_$\w]*))/g;
39
- /**
40
- * Matches props after element tags.
41
- *
42
- * Group 1: tag end
43
- * Group 2: spread props
44
- * Group 3: prop name
45
- * Group 4: equals
46
- * Group 5: prop value string
47
- */
48
- const PROPS_RE = /\s*(?:(\/?\s*>)|(\.\.\.\s*)|(?:([-_$\w]+)\s*(=)?\s*(?:("(\\"|[\S\s])*?(?:"|$)|'(?:\\'|[\S\s])*?(?:'|$)))?))/g;
49
- const CLOSING_BRACKET_RE = />/g;
50
- const CLOSING_SINGLE_QUOTE_RE = /[^\\]?'/g;
51
- const CLOSING_DOUBLE_QUOTE_RE = /[^\\]?"/g;
52
- const CLOSING_COMMENT_RE = /-->/g;
53
- function parse(spans) {
54
- let matcher = CHILDREN_RE;
55
- const stack = [];
56
- let element = {
57
- type: "element",
58
- open: { type: "tag", slash: "", value: "" },
59
- close: null,
60
- props: [],
61
- children: [],
62
- };
63
- const targets = [];
64
- let lineStart = true;
65
- for (let s = 0; s < spans.length; s++) {
66
- const span = spans[s];
67
- // Whether or not an expression is upcoming. Used to provide better errors.
68
- const expressing = s < spans.length - 1;
69
- let expressionTarget = null;
70
- for (let i = 0, end = i; i < span.length; i = end) {
71
- matcher.lastIndex = i;
72
- const match = matcher.exec(span);
73
- end = match ? match.index + match[0].length : span.length;
74
- switch (matcher) {
75
- case CHILDREN_RE: {
76
- if (match) {
77
- const [, newline, comment, tag, closingSlash, tagName] = match;
78
- if (i < match.index) {
79
- let before = span.slice(i, match.index);
80
- if (lineStart) {
81
- before = before.replace(/^\s*/, "");
82
- }
83
- if (newline) {
84
- if (span[Math.max(0, match.index - 1)] === "\\") {
85
- // We preserve whitespace before escaped newlines and have to
86
- // remove the backslash.
87
- // jsx` \
88
- // `
89
- before = before.slice(0, -1);
90
- }
91
- else {
92
- before = before.replace(/\s*$/, "");
93
- }
94
- }
95
- if (before) {
96
- element.children.push({ type: "value", value: before });
97
- }
98
- }
99
- lineStart = !!newline;
100
- if (comment) {
101
- if (end === span.length) {
102
- // Expression in a comment:
103
- // jsx`<!-- ${exp} -->`
104
- matcher = CLOSING_COMMENT_RE;
105
- }
106
- }
107
- else if (tag) {
108
- if (closingSlash) {
109
- element.close = {
110
- type: "tag",
111
- slash: closingSlash,
112
- value: tagName,
113
- };
114
- if (!stack.length) {
115
- if (end !== span.length) {
116
- throw new SyntaxError(`Unmatched closing tag "${tagName}"`);
117
- }
118
- // ERROR EXPRESSION
119
- expressionTarget = {
120
- type: "error",
121
- message: "Unmatched closing tag ${}",
122
- value: null,
123
- };
124
- }
125
- else {
126
- if (end === span.length) {
127
- // TAG EXPRESSION
128
- expressionTarget = element.close;
129
- }
130
- element = stack.pop();
131
- matcher = CLOSING_BRACKET_RE;
132
- }
133
- }
134
- else {
135
- const next = {
136
- type: "element",
137
- open: {
138
- type: "tag",
139
- slash: "",
140
- value: tagName,
141
- },
142
- close: null,
143
- props: [],
144
- children: [],
145
- };
146
- element.children.push(next);
147
- stack.push(element);
148
- element = next;
149
- matcher = PROPS_RE;
150
- if (end === span.length) {
151
- // TAG EXPRESSION
152
- expressionTarget = element.open;
153
- }
154
- }
155
- }
156
- }
157
- else {
158
- if (i < span.length) {
159
- let after = span.slice(i);
160
- if (!expressing) {
161
- // trim trailing whitespace
162
- after = after.replace(/\s*$/, "");
163
- }
164
- if (after) {
165
- element.children.push({ type: "value", value: after });
166
- }
167
- }
168
- }
169
- break;
170
- }
171
- case PROPS_RE: {
172
- if (match) {
173
- const [, tagEnd, spread, name, equals, string] = match;
174
- if (i < match.index) {
175
- throw new SyntaxError(`Unexpected text \`${span.slice(i, match.index).trim()}\``);
176
- }
177
- if (tagEnd) {
178
- if (tagEnd[0] === "/") {
179
- // This is a self-closing element, so there will always be a
180
- // result on the stack.
181
- element = stack.pop();
182
- }
183
- matcher = CHILDREN_RE;
184
- }
185
- else if (spread) {
186
- const value = {
187
- type: "value",
188
- value: null,
189
- };
190
- element.props.push(value);
191
- // SPREAD PROP EXPRESSION
192
- expressionTarget = value;
193
- if (!(expressing && end === span.length)) {
194
- throw new SyntaxError('Expression expected after "..."');
195
- }
196
- }
197
- else if (name) {
198
- let value;
199
- if (string == null) {
200
- if (!equals) {
201
- value = { type: "value", value: true };
202
- }
203
- else if (end < span.length) {
204
- throw new SyntaxError(`Unexpected text \`${span.slice(end, end + 20)}\``);
205
- }
206
- else {
207
- value = { type: "value", value: null };
208
- // PROP EXPRESSION
209
- expressionTarget = value;
210
- if (!(expressing && end === span.length)) {
211
- throw new SyntaxError(`Expression expected for prop "${name}"`);
212
- }
213
- }
214
- }
215
- else {
216
- const quote = string[0];
217
- value = { type: "propString", parts: [] };
218
- value.parts.push(string);
219
- if (end === span.length) {
220
- matcher =
221
- quote === "'"
222
- ? CLOSING_SINGLE_QUOTE_RE
223
- : CLOSING_DOUBLE_QUOTE_RE;
224
- }
225
- }
226
- const prop = {
227
- type: "prop",
228
- name,
229
- value,
230
- };
231
- element.props.push(prop);
232
- }
233
- }
234
- else {
235
- if (!expressing) {
236
- if (i === span.length) {
237
- throw new SyntaxError(`Expected props but reached end of document`);
238
- }
239
- else {
240
- throw new SyntaxError(`Unexpected text \`${span.slice(i, i + 20).trim()}\``);
241
- }
242
- }
243
- // Unexpected expression errors are handled in the outer loop.
244
- //
245
- // This would most likely be the starting point for the logic of
246
- // prop name expressions.
247
- // jsx`<p ${name}=${value}>`
248
- }
249
- break;
250
- }
251
- case CLOSING_BRACKET_RE: {
252
- // We’re in a closing tag and looking for the >.
253
- if (match) {
254
- if (i < match.index) {
255
- throw new SyntaxError(`Unexpected text \`${span.slice(i, match.index).trim()}\``);
256
- }
257
- matcher = CHILDREN_RE;
258
- }
259
- else {
260
- if (!expressing) {
261
- throw new SyntaxError(`Unexpected text \`${span.slice(i, i + 20).trim()}\``);
262
- }
263
- }
264
- break;
265
- }
266
- case CLOSING_SINGLE_QUOTE_RE:
267
- case CLOSING_DOUBLE_QUOTE_RE: {
268
- const string = span.slice(i, end);
269
- const prop = element.props[element.props.length - 1];
270
- const propString = prop.value;
271
- propString.parts.push(string);
272
- if (match) {
273
- matcher = PROPS_RE;
274
- }
275
- else {
276
- if (!expressing) {
277
- throw new SyntaxError(`Missing \`${matcher === CLOSING_SINGLE_QUOTE_RE ? "'" : '"'}\``);
278
- }
279
- }
280
- break;
281
- }
282
- case CLOSING_COMMENT_RE: {
283
- if (match) {
284
- matcher = CHILDREN_RE;
285
- }
286
- else {
287
- if (!expressing) {
288
- throw new SyntaxError("Expected `-->` but reached end of template");
289
- }
290
- }
291
- break;
292
- }
293
- }
294
- }
295
- if (expressing) {
296
- if (expressionTarget) {
297
- targets.push(expressionTarget);
298
- if (expressionTarget.type === "error") {
299
- break;
300
- }
301
- continue;
302
- }
303
- switch (matcher) {
304
- case CHILDREN_RE: {
305
- const target = { type: "value", value: null };
306
- element.children.push(target);
307
- targets.push(target);
308
- break;
309
- }
310
- case CLOSING_SINGLE_QUOTE_RE:
311
- case CLOSING_DOUBLE_QUOTE_RE: {
312
- const prop = element.props[element.props.length - 1];
313
- const target = { type: "value", value: null };
314
- prop.value.parts.push(target);
315
- targets.push(target);
316
- break;
317
- }
318
- case CLOSING_COMMENT_RE:
319
- targets.push(null);
320
- break;
321
- default:
322
- throw new SyntaxError("Unexpected expression");
323
- }
324
- }
325
- else if (expressionTarget) {
326
- throw new SyntaxError("Expression expected");
327
- }
328
- lineStart = false;
329
- }
330
- if (stack.length) {
331
- const ti = targets.indexOf(element.open);
332
- if (ti === -1) {
333
- throw new SyntaxError(`Unmatched opening tag "${element.open.value}"`);
334
- }
335
- targets[ti] = {
336
- type: "error",
337
- message: "Unmatched opening tag ${}",
338
- value: null,
339
- };
340
- }
341
- if (element.children.length === 1 && element.children[0].type === "element") {
342
- element = element.children[0];
343
- }
344
- return { element, targets };
345
- }
346
- function build(parsed) {
347
- if (parsed.close !== null &&
348
- parsed.close.slash !== "//" &&
349
- parsed.open.value !== parsed.close.value) {
350
- throw new SyntaxError(`Unmatched closing tag ${formatTagForError(parsed.close.value)}, expected ${formatTagForError(parsed.open.value)}`);
351
- }
352
- const children = [];
353
- for (let i = 0; i < parsed.children.length; i++) {
354
- const child = parsed.children[i];
355
- children.push(child.type === "element" ? build(child) : child.value);
356
- }
357
- let props = parsed.props.length ? {} : null;
358
- for (let i = 0; i < parsed.props.length; i++) {
359
- const prop = parsed.props[i];
360
- if (prop.type === "prop") {
361
- let value;
362
- if (prop.value.type === "value") {
363
- value = prop.value.value;
364
- }
365
- else {
366
- let string = "";
367
- for (let i = 0; i < prop.value.parts.length; i++) {
368
- const part = prop.value.parts[i];
369
- if (typeof part === "string") {
370
- string += part;
371
- }
372
- else if (typeof part.value !== "boolean" && part.value != null) {
373
- string +=
374
- typeof part.value === "string" ? part.value : String(part.value);
375
- }
376
- }
377
- value = string
378
- // remove quotes
379
- .slice(1, -1)
380
- // unescape things
381
- // adapted from https://stackoverflow.com/a/57330383/1825413
382
- .replace(/\\x[0-9a-f]{2}|\\u[0-9a-f]{4}|\\u\{[0-9a-f]+\}|\\./gi, (match) => {
383
- switch (match[1]) {
384
- case "b":
385
- return "\b";
386
- case "f":
387
- return "\f";
388
- case "n":
389
- return "\n";
390
- case "r":
391
- return "\r";
392
- case "t":
393
- return "\t";
394
- case "v":
395
- return "\v";
396
- case "x":
397
- return String.fromCharCode(parseInt(match.slice(2), 16));
398
- case "u":
399
- if (match[2] === "{") {
400
- return String.fromCodePoint(parseInt(match.slice(3, -1), 16));
401
- }
402
- return String.fromCharCode(parseInt(match.slice(2), 16));
403
- case "0":
404
- return "\0";
405
- default:
406
- return match.slice(1);
407
- }
408
- });
409
- }
410
- props[prop.name] = value;
411
- }
412
- else {
413
- // spread prop
414
- props = { ...props, ...prop.value };
415
- }
416
- }
417
- return createElement(parsed.open.value, props, ...children);
418
- }
419
- function formatTagForError(tag) {
420
- return typeof tag === "function"
421
- ? tag.name + "()"
422
- : typeof tag === "string"
423
- ? `"${tag}"`
424
- : JSON.stringify(tag);
4
+ const cache = new Map();
5
+ function jsx(spans, ...expressions) {
6
+ const key = JSON.stringify(spans.raw);
7
+ let parseResult = cache.get(key);
8
+ if (parseResult == null) {
9
+ parseResult = parse(spans.raw);
10
+ cache.set(key, parseResult);
11
+ }
12
+ const { element, targets } = parseResult;
13
+ for (let i = 0; i < expressions.length; i++) {
14
+ const exp = expressions[i];
15
+ const target = targets[i];
16
+ if (target) {
17
+ if (target.type === "error") {
18
+ throw new SyntaxError(target.message.replace("${}", formatTagForError(exp)));
19
+ }
20
+ target.value = exp;
21
+ }
22
+ }
23
+ return build(element);
24
+ }
25
+ /** Alias for `jsx` template tag. */
26
+ const html = jsx;
27
+ /**
28
+ * Matches first significant character in children mode.
29
+ *
30
+ * Group 1: newline
31
+ * Group 2: comment
32
+ * Group 3: tag
33
+ * Group 4: closing slash
34
+ * Group 5: tag name
35
+ *
36
+ * The comment group must appear first because the tag group can potentially
37
+ * match a comment, so that we can handle tag expressions where we’ve reached
38
+ * the end of a span.
39
+ */
40
+ const CHILDREN_RE = /((?:\r|\n|\r\n)\s*)|(<!--[\S\s]*?(?:-->|$))|(<\s*(\/{0,2})\s*([-_$\w]*))/g;
41
+ /**
42
+ * Matches props after element tags.
43
+ *
44
+ * Group 1: tag end
45
+ * Group 2: spread props
46
+ * Group 3: prop name
47
+ * Group 4: equals
48
+ * Group 5: prop value string
49
+ */
50
+ const PROPS_RE = /\s*(?:(\/?\s*>)|(\.\.\.\s*)|(?:([-_$\w]+)\s*(=)?\s*(?:("(\\"|[\S\s])*?(?:"|$)|'(?:\\'|[\S\s])*?(?:'|$)))?))/g;
51
+ const CLOSING_BRACKET_RE = />/g;
52
+ const CLOSING_SINGLE_QUOTE_RE = /[^\\]?'/g;
53
+ const CLOSING_DOUBLE_QUOTE_RE = /[^\\]?"/g;
54
+ const CLOSING_COMMENT_RE = /-->/g;
55
+ function parse(spans) {
56
+ let matcher = CHILDREN_RE;
57
+ const stack = [];
58
+ let element = {
59
+ type: "element",
60
+ open: { type: "tag", slash: "", value: "" },
61
+ close: null,
62
+ props: [],
63
+ children: [],
64
+ };
65
+ const targets = [];
66
+ let lineStart = true;
67
+ for (let s = 0; s < spans.length; s++) {
68
+ const span = spans[s];
69
+ // Whether or not an expression is upcoming. Used to provide better errors.
70
+ const expressing = s < spans.length - 1;
71
+ let expressionTarget = null;
72
+ for (let i = 0, end = i; i < span.length; i = end) {
73
+ matcher.lastIndex = i;
74
+ const match = matcher.exec(span);
75
+ end = match ? match.index + match[0].length : span.length;
76
+ switch (matcher) {
77
+ case CHILDREN_RE: {
78
+ if (match) {
79
+ const [, newline, comment, tag, closingSlash, tagName] = match;
80
+ if (i < match.index) {
81
+ let before = span.slice(i, match.index);
82
+ if (lineStart) {
83
+ before = before.replace(/^\s*/, "");
84
+ }
85
+ if (newline) {
86
+ if (span[Math.max(0, match.index - 1)] === "\\") {
87
+ // We preserve whitespace before escaped newlines and have to
88
+ // remove the backslash.
89
+ // jsx` \
90
+ // `
91
+ before = before.slice(0, -1);
92
+ }
93
+ else {
94
+ before = before.replace(/\s*$/, "");
95
+ }
96
+ }
97
+ if (before) {
98
+ element.children.push({ type: "value", value: before });
99
+ }
100
+ }
101
+ lineStart = !!newline;
102
+ if (comment) {
103
+ if (end === span.length) {
104
+ // Expression in a comment:
105
+ // jsx`<!-- ${exp} -->`
106
+ matcher = CLOSING_COMMENT_RE;
107
+ }
108
+ }
109
+ else if (tag) {
110
+ if (closingSlash) {
111
+ element.close = {
112
+ type: "tag",
113
+ slash: closingSlash,
114
+ value: tagName,
115
+ };
116
+ if (!stack.length) {
117
+ if (end !== span.length) {
118
+ throw new SyntaxError(`Unmatched closing tag "${tagName}"`);
119
+ }
120
+ // ERROR EXPRESSION
121
+ expressionTarget = {
122
+ type: "error",
123
+ message: "Unmatched closing tag ${}",
124
+ value: null,
125
+ };
126
+ }
127
+ else {
128
+ if (end === span.length) {
129
+ // TAG EXPRESSION
130
+ expressionTarget = element.close;
131
+ }
132
+ element = stack.pop();
133
+ matcher = CLOSING_BRACKET_RE;
134
+ }
135
+ }
136
+ else {
137
+ const next = {
138
+ type: "element",
139
+ open: {
140
+ type: "tag",
141
+ slash: "",
142
+ value: tagName,
143
+ },
144
+ close: null,
145
+ props: [],
146
+ children: [],
147
+ };
148
+ element.children.push(next);
149
+ stack.push(element);
150
+ element = next;
151
+ matcher = PROPS_RE;
152
+ if (end === span.length) {
153
+ // TAG EXPRESSION
154
+ expressionTarget = element.open;
155
+ }
156
+ }
157
+ }
158
+ }
159
+ else {
160
+ if (i < span.length) {
161
+ let after = span.slice(i);
162
+ if (!expressing) {
163
+ // trim trailing whitespace
164
+ after = after.replace(/\s*$/, "");
165
+ }
166
+ if (after) {
167
+ element.children.push({ type: "value", value: after });
168
+ }
169
+ }
170
+ }
171
+ break;
172
+ }
173
+ case PROPS_RE: {
174
+ if (match) {
175
+ const [, tagEnd, spread, name, equals, string] = match;
176
+ if (i < match.index) {
177
+ throw new SyntaxError(`Unexpected text \`${span.slice(i, match.index).trim()}\``);
178
+ }
179
+ if (tagEnd) {
180
+ if (tagEnd[0] === "/") {
181
+ // This is a self-closing element, so there will always be a
182
+ // result on the stack.
183
+ element = stack.pop();
184
+ }
185
+ matcher = CHILDREN_RE;
186
+ }
187
+ else if (spread) {
188
+ const value = {
189
+ type: "value",
190
+ value: null,
191
+ };
192
+ element.props.push(value);
193
+ // SPREAD PROP EXPRESSION
194
+ expressionTarget = value;
195
+ if (!(expressing && end === span.length)) {
196
+ throw new SyntaxError('Expression expected after "..."');
197
+ }
198
+ }
199
+ else if (name) {
200
+ let value;
201
+ if (string == null) {
202
+ if (!equals) {
203
+ value = { type: "value", value: true };
204
+ }
205
+ else if (end < span.length) {
206
+ throw new SyntaxError(`Unexpected text \`${span.slice(end, end + 20)}\``);
207
+ }
208
+ else {
209
+ value = { type: "value", value: null };
210
+ // PROP EXPRESSION
211
+ expressionTarget = value;
212
+ if (!(expressing && end === span.length)) {
213
+ throw new SyntaxError(`Expression expected for prop "${name}"`);
214
+ }
215
+ }
216
+ }
217
+ else {
218
+ const quote = string[0];
219
+ value = { type: "propString", parts: [] };
220
+ value.parts.push(string);
221
+ if (end === span.length) {
222
+ matcher =
223
+ quote === "'"
224
+ ? CLOSING_SINGLE_QUOTE_RE
225
+ : CLOSING_DOUBLE_QUOTE_RE;
226
+ }
227
+ }
228
+ const prop = {
229
+ type: "prop",
230
+ name,
231
+ value,
232
+ };
233
+ element.props.push(prop);
234
+ }
235
+ }
236
+ else {
237
+ if (!expressing) {
238
+ if (i === span.length) {
239
+ throw new SyntaxError(`Expected props but reached end of document`);
240
+ }
241
+ else {
242
+ throw new SyntaxError(`Unexpected text \`${span.slice(i, i + 20).trim()}\``);
243
+ }
244
+ }
245
+ // Unexpected expression errors are handled in the outer loop.
246
+ //
247
+ // This would most likely be the starting point for the logic of
248
+ // prop name expressions.
249
+ // jsx`<p ${name}=${value}>`
250
+ }
251
+ break;
252
+ }
253
+ case CLOSING_BRACKET_RE: {
254
+ // We’re in a closing tag and looking for the >.
255
+ if (match) {
256
+ if (i < match.index) {
257
+ throw new SyntaxError(`Unexpected text \`${span.slice(i, match.index).trim()}\``);
258
+ }
259
+ matcher = CHILDREN_RE;
260
+ }
261
+ else {
262
+ if (!expressing) {
263
+ throw new SyntaxError(`Unexpected text \`${span.slice(i, i + 20).trim()}\``);
264
+ }
265
+ }
266
+ break;
267
+ }
268
+ case CLOSING_SINGLE_QUOTE_RE:
269
+ case CLOSING_DOUBLE_QUOTE_RE: {
270
+ const string = span.slice(i, end);
271
+ const prop = element.props[element.props.length - 1];
272
+ const propString = prop.value;
273
+ propString.parts.push(string);
274
+ if (match) {
275
+ matcher = PROPS_RE;
276
+ }
277
+ else {
278
+ if (!expressing) {
279
+ throw new SyntaxError(`Missing \`${matcher === CLOSING_SINGLE_QUOTE_RE ? "'" : '"'}\``);
280
+ }
281
+ }
282
+ break;
283
+ }
284
+ case CLOSING_COMMENT_RE: {
285
+ if (match) {
286
+ matcher = CHILDREN_RE;
287
+ }
288
+ else {
289
+ if (!expressing) {
290
+ throw new SyntaxError("Expected `-->` but reached end of template");
291
+ }
292
+ }
293
+ break;
294
+ }
295
+ }
296
+ }
297
+ if (expressing) {
298
+ if (expressionTarget) {
299
+ targets.push(expressionTarget);
300
+ if (expressionTarget.type === "error") {
301
+ break;
302
+ }
303
+ continue;
304
+ }
305
+ switch (matcher) {
306
+ case CHILDREN_RE: {
307
+ const target = { type: "value", value: null };
308
+ element.children.push(target);
309
+ targets.push(target);
310
+ break;
311
+ }
312
+ case CLOSING_SINGLE_QUOTE_RE:
313
+ case CLOSING_DOUBLE_QUOTE_RE: {
314
+ const prop = element.props[element.props.length - 1];
315
+ const target = { type: "value", value: null };
316
+ prop.value.parts.push(target);
317
+ targets.push(target);
318
+ break;
319
+ }
320
+ case CLOSING_COMMENT_RE:
321
+ targets.push(null);
322
+ break;
323
+ default:
324
+ throw new SyntaxError("Unexpected expression");
325
+ }
326
+ }
327
+ else if (expressionTarget) {
328
+ throw new SyntaxError("Expression expected");
329
+ }
330
+ lineStart = false;
331
+ }
332
+ if (stack.length) {
333
+ const ti = targets.indexOf(element.open);
334
+ if (ti === -1) {
335
+ throw new SyntaxError(`Unmatched opening tag "${element.open.value}"`);
336
+ }
337
+ targets[ti] = {
338
+ type: "error",
339
+ message: "Unmatched opening tag ${}",
340
+ value: null,
341
+ };
342
+ }
343
+ if (element.children.length === 1 && element.children[0].type === "element") {
344
+ element = element.children[0];
345
+ }
346
+ return { element, targets };
347
+ }
348
+ function build(parsed) {
349
+ if (parsed.close !== null &&
350
+ parsed.close.slash !== "//" &&
351
+ parsed.open.value !== parsed.close.value) {
352
+ throw new SyntaxError(`Unmatched closing tag ${formatTagForError(parsed.close.value)}, expected ${formatTagForError(parsed.open.value)}`);
353
+ }
354
+ const children = [];
355
+ for (let i = 0; i < parsed.children.length; i++) {
356
+ const child = parsed.children[i];
357
+ children.push(child.type === "element" ? build(child) : child.value);
358
+ }
359
+ let props = parsed.props.length ? {} : null;
360
+ for (let i = 0; i < parsed.props.length; i++) {
361
+ const prop = parsed.props[i];
362
+ if (prop.type === "prop") {
363
+ let value;
364
+ if (prop.value.type === "value") {
365
+ value = prop.value.value;
366
+ }
367
+ else {
368
+ let string = "";
369
+ for (let i = 0; i < prop.value.parts.length; i++) {
370
+ const part = prop.value.parts[i];
371
+ if (typeof part === "string") {
372
+ string += part;
373
+ }
374
+ else if (typeof part.value !== "boolean" && part.value != null) {
375
+ string +=
376
+ typeof part.value === "string" ? part.value : String(part.value);
377
+ }
378
+ }
379
+ value = string
380
+ // remove quotes
381
+ .slice(1, -1)
382
+ // unescape things
383
+ // adapted from https://stackoverflow.com/a/57330383/1825413
384
+ .replace(/\\x[0-9a-f]{2}|\\u[0-9a-f]{4}|\\u\{[0-9a-f]+\}|\\./gi, (match) => {
385
+ switch (match[1]) {
386
+ case "b":
387
+ return "\b";
388
+ case "f":
389
+ return "\f";
390
+ case "n":
391
+ return "\n";
392
+ case "r":
393
+ return "\r";
394
+ case "t":
395
+ return "\t";
396
+ case "v":
397
+ return "\v";
398
+ case "x":
399
+ return String.fromCharCode(parseInt(match.slice(2), 16));
400
+ case "u":
401
+ if (match[2] === "{") {
402
+ return String.fromCodePoint(parseInt(match.slice(3, -1), 16));
403
+ }
404
+ return String.fromCharCode(parseInt(match.slice(2), 16));
405
+ case "0":
406
+ return "\0";
407
+ default:
408
+ return match.slice(1);
409
+ }
410
+ });
411
+ }
412
+ props[prop.name] = value;
413
+ }
414
+ else {
415
+ // spread prop
416
+ props = { ...props, ...prop.value };
417
+ }
418
+ }
419
+ return createElement(parsed.open.value, props, ...children);
420
+ }
421
+ function formatTagForError(tag) {
422
+ return typeof tag === "function"
423
+ ? tag.name + "()"
424
+ : typeof tag === "string"
425
+ ? `"${tag}"`
426
+ : JSON.stringify(tag);
425
427
  }
426
428
 
427
- export { jsx };
429
+ export { html, jsx };
428
430
  //# sourceMappingURL=jsx-tag.js.map