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