htm-transform 0.1.3 → 0.1.5
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/index.d.ts +75 -0
- package/index.js +284 -86
- package/package.json +8 -5
- package/test.js +259 -247
- package/tsconfig.json +15 -0
- package/jsconfig.json +0 -12
package/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as acorn from "acorn";
|
|
2
2
|
import { generate } from "astring";
|
|
3
|
-
import {
|
|
3
|
+
import { attachComments, makeTraveler } from "astravel";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @typedef {Object} ElementNode
|
|
@@ -11,23 +11,23 @@ import { simple } from "acorn-walk";
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* @typedef {
|
|
14
|
+
* @typedef {object} TextNode
|
|
15
15
|
* @property {'text'} type
|
|
16
16
|
* @property {string} value
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
* @typedef {
|
|
20
|
+
* @typedef {object} ExpressionNode
|
|
21
21
|
* @property {'expression'} type
|
|
22
22
|
* @property {string} value
|
|
23
23
|
* @property {import('acorn').Node} [expr]
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
* @typedef {
|
|
27
|
+
* @typedef {object} Token
|
|
28
28
|
* @property {'openTag' | 'closeTag' | 'text' | 'expression'} type
|
|
29
29
|
* @property {string} [tag]
|
|
30
|
-
* @property {
|
|
30
|
+
* @property {Record<string, string | boolean>} [props]
|
|
31
31
|
* @property {boolean} [selfClosing]
|
|
32
32
|
* @property {string} [value]
|
|
33
33
|
*/
|
|
@@ -35,10 +35,10 @@ import { simple } from "acorn-walk";
|
|
|
35
35
|
/**
|
|
36
36
|
* Transforms htm tagged templates into h function calls
|
|
37
37
|
* @param {string} code - JavaScript code containing htm tagged templates
|
|
38
|
-
* @param {
|
|
39
|
-
* @param {string} options.pragma - The h function name (default: 'h')
|
|
40
|
-
* @param {string} options.tag - The tag name to look for (default: 'html')
|
|
41
|
-
* @param {
|
|
38
|
+
* @param {object} [options] - Transform options
|
|
39
|
+
* @param {string} [options.pragma] - The h function name (default: 'h')
|
|
40
|
+
* @param {string} [options.tag] - The tag name to look for (default: 'html')
|
|
41
|
+
* @param {object} [options.import] - Import configuration
|
|
42
42
|
* @param {string} options.import.from - Module to import from (e.g., 'preact', 'react')
|
|
43
43
|
* @param {string} options.import.name - Export name to import (e.g., 'h', 'createElement')
|
|
44
44
|
* @returns {string} - Transformed code
|
|
@@ -46,75 +46,125 @@ import { simple } from "acorn-walk";
|
|
|
46
46
|
export default function transform(code, options = {}) {
|
|
47
47
|
const { pragma = "h", tag: tagName = "html", import: importConfig } = options;
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
49
|
+
/** @type {Array<import('acorn').Comment>} */
|
|
50
|
+
const comments = [];
|
|
51
|
+
|
|
52
|
+
const ast = /** @type {import('acorn').Program} */ (
|
|
53
|
+
acorn.parse(code, {
|
|
54
|
+
ecmaVersion: "latest",
|
|
55
|
+
sourceType: "module",
|
|
56
|
+
locations: true,
|
|
57
|
+
onComment: comments,
|
|
58
|
+
})
|
|
59
|
+
);
|
|
53
60
|
|
|
54
61
|
let hasTransformation = false;
|
|
55
62
|
|
|
56
|
-
|
|
57
|
-
TaggedTemplateExpression(node) {
|
|
63
|
+
const traveler = makeTraveler({
|
|
64
|
+
TaggedTemplateExpression(node, state) {
|
|
58
65
|
if (node.tag.type === "Identifier" && node.tag.name === tagName) {
|
|
59
66
|
hasTransformation = true;
|
|
60
67
|
const transformed = transformTaggedTemplate(node, pragma);
|
|
61
68
|
|
|
62
69
|
// Replace the node with the transformed version
|
|
63
|
-
|
|
64
|
-
|
|
70
|
+
const mutableNode = /** @type {Record<string, unknown>} */ (/** @type {unknown} */ (node));
|
|
71
|
+
for (const key in mutableNode) {
|
|
72
|
+
delete mutableNode[key];
|
|
65
73
|
}
|
|
66
74
|
Object.assign(node, transformed);
|
|
75
|
+
// Continue traversing into the transformed node to handle nested templates
|
|
76
|
+
this.go(node, state);
|
|
77
|
+
return;
|
|
67
78
|
}
|
|
79
|
+
// Continue traversal for non-matching nodes
|
|
80
|
+
this.super.TaggedTemplateExpression.call(this, node, state);
|
|
68
81
|
},
|
|
69
82
|
});
|
|
70
83
|
|
|
84
|
+
traveler.go(ast);
|
|
85
|
+
|
|
71
86
|
// Add import statement if specified and transformation occurred
|
|
72
87
|
if (hasTransformation && importConfig?.from && importConfig?.name) {
|
|
73
88
|
addImportDeclaration(ast, importConfig.from, importConfig.name);
|
|
74
89
|
}
|
|
75
90
|
|
|
76
|
-
|
|
91
|
+
// Attach comments to AST nodes
|
|
92
|
+
if (comments.length > 0) {
|
|
93
|
+
attachComments(ast, comments);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return generate(ast, { comments: true });
|
|
77
97
|
}
|
|
78
98
|
|
|
79
99
|
/**
|
|
80
100
|
* Adds an import declaration at the top of the AST
|
|
81
|
-
* @param {import('acorn').
|
|
101
|
+
* @param {import('acorn').Program} ast - The AST to modify
|
|
82
102
|
* @param {string} moduleName - The module to import from
|
|
83
103
|
* @param {string} exportName - The export name to import
|
|
84
104
|
* @returns {void}
|
|
85
105
|
*/
|
|
86
106
|
function addImportDeclaration(ast, moduleName, exportName) {
|
|
87
107
|
const hasImport = ast.body.some(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
)
|
|
108
|
+
/**
|
|
109
|
+
* @param {import('acorn').Statement | import('acorn').ModuleDeclaration} node
|
|
110
|
+
* @returns {boolean}
|
|
111
|
+
*/
|
|
112
|
+
(node) => {
|
|
113
|
+
if (node.type !== "ImportDeclaration") return false;
|
|
114
|
+
const importNode = /** @type {import('acorn').ImportDeclaration} */ (node);
|
|
115
|
+
return (
|
|
116
|
+
importNode.source.value === moduleName &&
|
|
117
|
+
importNode.specifiers.some(
|
|
118
|
+
/**
|
|
119
|
+
* @param {import('acorn').ImportSpecifier | import('acorn').ImportDefaultSpecifier | import('acorn').ImportNamespaceSpecifier} spec
|
|
120
|
+
* @returns {boolean}
|
|
121
|
+
*/
|
|
122
|
+
(spec) => {
|
|
123
|
+
if (spec.type !== "ImportSpecifier") return false;
|
|
124
|
+
const importSpec = /** @type {import('acorn').ImportSpecifier} */ (spec);
|
|
125
|
+
return (
|
|
126
|
+
importSpec.imported.type === "Identifier" && importSpec.imported.name === exportName
|
|
127
|
+
);
|
|
128
|
+
},
|
|
129
|
+
)
|
|
130
|
+
);
|
|
131
|
+
},
|
|
94
132
|
);
|
|
95
133
|
|
|
96
134
|
if (hasImport) return;
|
|
97
135
|
|
|
98
136
|
// Create import declaration: import { exportName } from 'moduleName';
|
|
137
|
+
/** @type {import('acorn').ImportDeclaration} */
|
|
99
138
|
const importDeclaration = {
|
|
100
139
|
type: "ImportDeclaration",
|
|
140
|
+
start: 0,
|
|
141
|
+
end: 0,
|
|
101
142
|
specifiers: [
|
|
102
143
|
{
|
|
103
144
|
type: "ImportSpecifier",
|
|
145
|
+
start: 0,
|
|
146
|
+
end: 0,
|
|
104
147
|
imported: {
|
|
105
148
|
type: "Identifier",
|
|
149
|
+
start: 0,
|
|
150
|
+
end: 0,
|
|
106
151
|
name: exportName,
|
|
107
152
|
},
|
|
108
153
|
local: {
|
|
109
154
|
type: "Identifier",
|
|
155
|
+
start: 0,
|
|
156
|
+
end: 0,
|
|
110
157
|
name: exportName,
|
|
111
158
|
},
|
|
112
159
|
},
|
|
113
160
|
],
|
|
114
161
|
source: {
|
|
115
162
|
type: "Literal",
|
|
163
|
+
start: 0,
|
|
164
|
+
end: 0,
|
|
116
165
|
value: moduleName,
|
|
117
166
|
},
|
|
167
|
+
attributes: [],
|
|
118
168
|
};
|
|
119
169
|
|
|
120
170
|
// Add to the beginning of the body
|
|
@@ -123,9 +173,9 @@ function addImportDeclaration(ast, moduleName, exportName) {
|
|
|
123
173
|
|
|
124
174
|
/**
|
|
125
175
|
* Transforms a single tagged template expression
|
|
126
|
-
* @param {import('acorn').
|
|
176
|
+
* @param {import('acorn').TaggedTemplateExpression} node - The tagged template expression node
|
|
127
177
|
* @param {string} pragma - The pragma function name
|
|
128
|
-
* @returns {import('acorn').
|
|
178
|
+
* @returns {import('acorn').Literal | import('acorn').CallExpression | import('acorn').ArrayExpression | import('acorn').Expression} The transformed AST node
|
|
129
179
|
*/
|
|
130
180
|
function transformTaggedTemplate(node, pragma) {
|
|
131
181
|
const quasi = node.quasi;
|
|
@@ -149,15 +199,21 @@ function transformTaggedTemplate(node, pragma) {
|
|
|
149
199
|
|
|
150
200
|
// Convert to h() calls
|
|
151
201
|
if (elements.length === 0) {
|
|
152
|
-
|
|
202
|
+
/** @type {import('acorn').Literal} */
|
|
203
|
+
const literal = { type: "Literal", start: 0, end: 0, value: null };
|
|
204
|
+
return literal;
|
|
153
205
|
} else if (elements.length === 1) {
|
|
154
206
|
return elementsToAST(elements[0], pragma);
|
|
155
207
|
} else {
|
|
156
208
|
// Multiple root elements - return array
|
|
157
|
-
|
|
209
|
+
/** @type {import('acorn').ArrayExpression} */
|
|
210
|
+
const arrayExpr = {
|
|
158
211
|
type: "ArrayExpression",
|
|
212
|
+
start: 0,
|
|
213
|
+
end: 0,
|
|
159
214
|
elements: elements.map((el) => elementsToAST(el, pragma)),
|
|
160
215
|
};
|
|
216
|
+
return arrayExpr;
|
|
161
217
|
}
|
|
162
218
|
}
|
|
163
219
|
|
|
@@ -185,15 +241,16 @@ function parseTemplate(template, placeholders) {
|
|
|
185
241
|
|
|
186
242
|
if (token.type === "openTag") {
|
|
187
243
|
i++;
|
|
244
|
+
/** @type {ElementNode} */
|
|
188
245
|
const element = {
|
|
189
246
|
type: "element",
|
|
190
|
-
tag: token.tag,
|
|
191
|
-
props: token.props,
|
|
247
|
+
tag: token.tag || "",
|
|
248
|
+
props: token.props || {},
|
|
192
249
|
children: [],
|
|
193
250
|
};
|
|
194
251
|
|
|
195
252
|
if (!token.selfClosing) {
|
|
196
|
-
element.children = parse(token.tag);
|
|
253
|
+
element.children = parse(token.tag || "");
|
|
197
254
|
}
|
|
198
255
|
|
|
199
256
|
children.push(element);
|
|
@@ -204,13 +261,17 @@ function parseTemplate(template, placeholders) {
|
|
|
204
261
|
}
|
|
205
262
|
i++;
|
|
206
263
|
} else if (token.type === "text") {
|
|
207
|
-
const text = token.value.trim();
|
|
264
|
+
const text = (token.value || "").trim();
|
|
208
265
|
if (text) {
|
|
209
|
-
|
|
266
|
+
/** @type {TextNode} */
|
|
267
|
+
const textNode = { type: "text", value: text };
|
|
268
|
+
children.push(textNode);
|
|
210
269
|
}
|
|
211
270
|
i++;
|
|
212
271
|
} else if (token.type === "expression") {
|
|
213
|
-
|
|
272
|
+
/** @type {ExpressionNode} */
|
|
273
|
+
const exprNode = { type: "expression", value: token.value || "" };
|
|
274
|
+
children.push(exprNode);
|
|
214
275
|
i++;
|
|
215
276
|
} else {
|
|
216
277
|
i++;
|
|
@@ -232,6 +293,7 @@ function parseTemplate(template, placeholders) {
|
|
|
232
293
|
* @returns {Array<Token>} Array of tokens
|
|
233
294
|
*/
|
|
234
295
|
function tokenize(template) {
|
|
296
|
+
/** @type {Token[]} */
|
|
235
297
|
const tokens = [];
|
|
236
298
|
let i = 0;
|
|
237
299
|
|
|
@@ -244,21 +306,27 @@ function tokenize(template) {
|
|
|
244
306
|
const [full, isClosing, tagName, attrsStr, selfClosing] = tagMatch;
|
|
245
307
|
|
|
246
308
|
if (isClosing) {
|
|
247
|
-
|
|
309
|
+
/** @type {Token} */
|
|
310
|
+
const closeToken = { type: "closeTag", tag: tagName };
|
|
311
|
+
tokens.push(closeToken);
|
|
248
312
|
} else {
|
|
249
313
|
const props = parseAttributes(attrsStr);
|
|
250
|
-
|
|
314
|
+
/** @type {Token} */
|
|
315
|
+
const openToken = {
|
|
251
316
|
type: "openTag",
|
|
252
317
|
tag: tagName,
|
|
253
318
|
props,
|
|
254
319
|
selfClosing: selfClosing === "/",
|
|
255
|
-
}
|
|
320
|
+
};
|
|
321
|
+
tokens.push(openToken);
|
|
256
322
|
}
|
|
257
323
|
|
|
258
324
|
i += full.length;
|
|
259
325
|
} else {
|
|
260
326
|
// Not a valid tag, treat as text
|
|
261
|
-
|
|
327
|
+
/** @type {Token} */
|
|
328
|
+
const textToken = { type: "text", value: "<" };
|
|
329
|
+
tokens.push(textToken);
|
|
262
330
|
i++;
|
|
263
331
|
}
|
|
264
332
|
} else {
|
|
@@ -272,9 +340,13 @@ function tokenize(template) {
|
|
|
272
340
|
// Check if this is an expression placeholder
|
|
273
341
|
const exprMatch = text.match(/^(__EXPR_\d+__)/);
|
|
274
342
|
if (exprMatch && text === exprMatch[0]) {
|
|
275
|
-
|
|
343
|
+
/** @type {Token} */
|
|
344
|
+
const exprToken = { type: "expression", value: exprMatch[0] };
|
|
345
|
+
tokens.push(exprToken);
|
|
276
346
|
} else if (text.trim()) {
|
|
277
|
-
|
|
347
|
+
/** @type {Token} */
|
|
348
|
+
const textToken = { type: "text", value: text };
|
|
349
|
+
tokens.push(textToken);
|
|
278
350
|
}
|
|
279
351
|
}
|
|
280
352
|
}
|
|
@@ -288,6 +360,7 @@ function tokenize(template) {
|
|
|
288
360
|
* @returns {Object<string, string | boolean>} Parsed attributes object
|
|
289
361
|
*/
|
|
290
362
|
function parseAttributes(attrsStr) {
|
|
363
|
+
/** @type {Record<string, string | boolean>} */
|
|
291
364
|
const props = {};
|
|
292
365
|
const attrRegex = /([a-zA-Z0-9_:-]+)(?:=(?:"([^"]*)"|'([^']*)'|([^\s>]+)))?/g;
|
|
293
366
|
let match;
|
|
@@ -319,45 +392,69 @@ function resolvePlaceholders(nodes, placeholders) {
|
|
|
319
392
|
if (node.type === "element") {
|
|
320
393
|
// Resolve tag name if it's a placeholder (component)
|
|
321
394
|
if (typeof node.tag === "string" && node.tag.startsWith("__EXPR_")) {
|
|
322
|
-
|
|
395
|
+
const expr = placeholderMap.get(node.tag);
|
|
396
|
+
if (expr) {
|
|
397
|
+
node.tag = { type: "expression", expr };
|
|
398
|
+
}
|
|
323
399
|
}
|
|
324
400
|
|
|
325
401
|
// Resolve props, preserving order
|
|
402
|
+
/** @type {Record<string, string | boolean | {type: 'expression', expr: import('acorn').Node}>} */
|
|
326
403
|
const newProps = {};
|
|
327
404
|
for (const [key, value] of Object.entries(node.props)) {
|
|
328
405
|
if (key.startsWith("__EXPR_")) {
|
|
329
406
|
// Handle spread props (key is the placeholder)
|
|
330
|
-
|
|
407
|
+
const expr = placeholderMap.get(key);
|
|
408
|
+
if (expr) {
|
|
409
|
+
newProps["...spread"] = { type: "expression", expr };
|
|
410
|
+
}
|
|
331
411
|
} else if (typeof value === "string" && placeholderMap.has(value)) {
|
|
332
412
|
// Single placeholder value
|
|
333
|
-
|
|
413
|
+
const expr = placeholderMap.get(value);
|
|
414
|
+
if (expr) {
|
|
415
|
+
newProps[key] = { type: "expression", expr };
|
|
416
|
+
}
|
|
334
417
|
} else if (typeof value === "string" && value.includes("__EXPR_")) {
|
|
335
418
|
// Template string with placeholders - create TemplateLiteral
|
|
336
419
|
const parts = value.split(/(__EXPR_\d+__)/);
|
|
420
|
+
/** @type {Array<import('acorn').TemplateElement>} */
|
|
337
421
|
const quasis = [];
|
|
422
|
+
/** @type {Array<import('acorn').Expression>} */
|
|
338
423
|
const expressions = [];
|
|
339
424
|
|
|
340
425
|
for (let i = 0; i < parts.length; i++) {
|
|
341
426
|
if (i % 2 === 0) {
|
|
342
427
|
// Static string part
|
|
343
|
-
|
|
428
|
+
/** @type {import('acorn').TemplateElement} */
|
|
429
|
+
const templateElement = {
|
|
344
430
|
type: "TemplateElement",
|
|
431
|
+
start: 0,
|
|
432
|
+
end: 0,
|
|
345
433
|
value: { raw: parts[i], cooked: parts[i] },
|
|
346
434
|
tail: i === parts.length - 1,
|
|
347
|
-
}
|
|
435
|
+
};
|
|
436
|
+
quasis.push(templateElement);
|
|
348
437
|
} else {
|
|
349
438
|
// Expression placeholder
|
|
350
|
-
|
|
439
|
+
const expr = placeholderMap.get(parts[i]);
|
|
440
|
+
if (expr) {
|
|
441
|
+
expressions.push(/** @type {import('acorn').Expression} */ (expr));
|
|
442
|
+
}
|
|
351
443
|
}
|
|
352
444
|
}
|
|
353
445
|
|
|
446
|
+
/** @type {import('acorn').TemplateLiteral} */
|
|
447
|
+
const templateLiteral = {
|
|
448
|
+
type: "TemplateLiteral",
|
|
449
|
+
start: 0,
|
|
450
|
+
end: 0,
|
|
451
|
+
quasis,
|
|
452
|
+
expressions,
|
|
453
|
+
};
|
|
454
|
+
|
|
354
455
|
newProps[key] = {
|
|
355
456
|
type: "expression",
|
|
356
|
-
expr:
|
|
357
|
-
type: "TemplateLiteral",
|
|
358
|
-
quasis,
|
|
359
|
-
expressions,
|
|
360
|
-
},
|
|
457
|
+
expr: templateLiteral,
|
|
361
458
|
};
|
|
362
459
|
} else {
|
|
363
460
|
newProps[key] = value;
|
|
@@ -366,7 +463,9 @@ function resolvePlaceholders(nodes, placeholders) {
|
|
|
366
463
|
node.props = newProps;
|
|
367
464
|
|
|
368
465
|
// Resolve children
|
|
369
|
-
node.children =
|
|
466
|
+
node.children = /** @type {Array<ElementNode | TextNode | ExpressionNode>} */ (
|
|
467
|
+
node.children.map(resolve).flat()
|
|
468
|
+
);
|
|
370
469
|
} else if (node.type === "expression") {
|
|
371
470
|
const expr = placeholderMap.get(node.value);
|
|
372
471
|
if (expr) {
|
|
@@ -376,21 +475,30 @@ function resolvePlaceholders(nodes, placeholders) {
|
|
|
376
475
|
// Check if text contains placeholders
|
|
377
476
|
const parts = node.value.split(/(__EXPR_\d+__)/);
|
|
378
477
|
if (parts.length > 1) {
|
|
379
|
-
|
|
478
|
+
/** @type {Array<ElementNode | TextNode | ExpressionNode>} */
|
|
479
|
+
const result = parts
|
|
380
480
|
.filter((p) => p)
|
|
381
481
|
.map((p) => {
|
|
382
482
|
if (p.startsWith("__EXPR_")) {
|
|
383
|
-
|
|
483
|
+
const expr = placeholderMap.get(p);
|
|
484
|
+
if (expr) {
|
|
485
|
+
/** @type {ExpressionNode} */
|
|
486
|
+
const exprNode = { type: "expression", value: p, expr };
|
|
487
|
+
return exprNode;
|
|
488
|
+
}
|
|
384
489
|
}
|
|
385
|
-
|
|
490
|
+
/** @type {TextNode} */
|
|
491
|
+
const textNode = { type: "text", value: p };
|
|
492
|
+
return textNode;
|
|
386
493
|
});
|
|
494
|
+
return result;
|
|
387
495
|
}
|
|
388
496
|
}
|
|
389
497
|
|
|
390
498
|
return node;
|
|
391
499
|
}
|
|
392
500
|
|
|
393
|
-
return nodes.map(resolve).flat();
|
|
501
|
+
return /** @type {Array<ElementNode | TextNode | ExpressionNode>} */ (nodes.map(resolve).flat());
|
|
394
502
|
}
|
|
395
503
|
|
|
396
504
|
/**
|
|
@@ -407,26 +515,36 @@ function needsQuoting(key) {
|
|
|
407
515
|
* Converts element nodes to AST nodes representing h() calls
|
|
408
516
|
* @param {ElementNode | TextNode | ExpressionNode} node - The node to convert
|
|
409
517
|
* @param {string} pragma - The pragma function name
|
|
410
|
-
* @returns {import('acorn').
|
|
518
|
+
* @returns {import('acorn').Literal | import('acorn').CallExpression | import('acorn').Expression} The AST node
|
|
411
519
|
*/
|
|
412
520
|
function elementsToAST(node, pragma) {
|
|
413
521
|
if (node.type === "text") {
|
|
414
|
-
|
|
522
|
+
/** @type {import('acorn').Literal} */
|
|
523
|
+
const literal = { type: "Literal", start: 0, end: 0, value: node.value };
|
|
524
|
+
return literal;
|
|
415
525
|
}
|
|
416
526
|
|
|
417
527
|
if (node.type === "expression") {
|
|
418
|
-
return node.expr;
|
|
528
|
+
return /** @type {import('acorn').Expression} */ (node.expr);
|
|
419
529
|
}
|
|
420
530
|
|
|
421
531
|
if (node.type === "element") {
|
|
422
532
|
// Build h(tag, props, ...children)
|
|
533
|
+
/** @type {Array<import('acorn').Expression | import('acorn').SpreadElement>} */
|
|
423
534
|
const args = [];
|
|
424
535
|
|
|
425
536
|
// Tag argument
|
|
426
|
-
if (node.tag.type === "expression") {
|
|
427
|
-
args.push(node.tag.expr);
|
|
537
|
+
if (typeof node.tag === "object" && node.tag.type === "expression") {
|
|
538
|
+
args.push(/** @type {import('acorn').Expression} */ (node.tag.expr));
|
|
428
539
|
} else {
|
|
429
|
-
|
|
540
|
+
/** @type {import('acorn').Literal} */
|
|
541
|
+
const tagLiteral = {
|
|
542
|
+
type: "Literal",
|
|
543
|
+
start: 0,
|
|
544
|
+
end: 0,
|
|
545
|
+
value: /** @type {string} */ (node.tag),
|
|
546
|
+
};
|
|
547
|
+
args.push(tagLiteral);
|
|
430
548
|
}
|
|
431
549
|
|
|
432
550
|
// Props argument
|
|
@@ -434,54 +552,122 @@ function elementsToAST(node, pragma) {
|
|
|
434
552
|
if (propsEntries.length > 0) {
|
|
435
553
|
const spreadProp = node.props["...spread"];
|
|
436
554
|
|
|
437
|
-
if (
|
|
555
|
+
if (
|
|
556
|
+
spreadProp &&
|
|
557
|
+
typeof spreadProp === "object" &&
|
|
558
|
+
spreadProp.type === "expression" &&
|
|
559
|
+
propsEntries.length === 1
|
|
560
|
+
) {
|
|
438
561
|
// Just spread props
|
|
439
|
-
args.push(spreadProp.expr);
|
|
440
|
-
} else if (spreadProp) {
|
|
562
|
+
args.push(/** @type {import('acorn').Expression} */ (spreadProp.expr));
|
|
563
|
+
} else if (spreadProp && typeof spreadProp === "object" && spreadProp.type === "expression") {
|
|
441
564
|
// Merge spread with other props, preserving order
|
|
565
|
+
/** @type {Array<import('acorn').Property | import('acorn').SpreadElement>} */
|
|
442
566
|
const properties = propsEntries.map(([key, value]) => {
|
|
443
|
-
if (key === "...spread") {
|
|
444
|
-
|
|
567
|
+
if (key === "...spread" && typeof value === "object" && value.type === "expression") {
|
|
568
|
+
/** @type {import('acorn').SpreadElement} */
|
|
569
|
+
const spread = {
|
|
445
570
|
type: "SpreadElement",
|
|
446
|
-
|
|
571
|
+
start: 0,
|
|
572
|
+
end: 0,
|
|
573
|
+
argument: /** @type {import('acorn').Expression} */ (value.expr),
|
|
447
574
|
};
|
|
575
|
+
return spread;
|
|
448
576
|
}
|
|
449
|
-
|
|
577
|
+
/** @type {import('acorn').Property} */
|
|
578
|
+
const prop = {
|
|
450
579
|
type: "Property",
|
|
580
|
+
start: 0,
|
|
581
|
+
end: 0,
|
|
451
582
|
key: needsQuoting(key)
|
|
452
|
-
?
|
|
453
|
-
|
|
454
|
-
|
|
583
|
+
? /** @type {import('acorn').Literal} */ ({
|
|
584
|
+
type: "Literal",
|
|
585
|
+
start: 0,
|
|
586
|
+
end: 0,
|
|
587
|
+
value: key,
|
|
588
|
+
})
|
|
589
|
+
: /** @type {import('acorn').Identifier} */ ({
|
|
590
|
+
type: "Identifier",
|
|
591
|
+
start: 0,
|
|
592
|
+
end: 0,
|
|
593
|
+
name: key,
|
|
594
|
+
}),
|
|
595
|
+
value:
|
|
596
|
+
typeof value === "object" && value.type === "expression"
|
|
597
|
+
? /** @type {import('acorn').Expression} */ (value.expr)
|
|
598
|
+
: /** @type {import('acorn').Literal} */ ({
|
|
599
|
+
type: "Literal",
|
|
600
|
+
start: 0,
|
|
601
|
+
end: 0,
|
|
602
|
+
value: /** @type {string | boolean} */ (value),
|
|
603
|
+
}),
|
|
455
604
|
kind: "init",
|
|
456
605
|
method: false,
|
|
457
606
|
shorthand: false,
|
|
458
607
|
computed: false,
|
|
459
608
|
};
|
|
609
|
+
return prop;
|
|
460
610
|
});
|
|
461
611
|
|
|
462
|
-
|
|
612
|
+
/** @type {import('acorn').ObjectExpression} */
|
|
613
|
+
const objExpr = {
|
|
463
614
|
type: "ObjectExpression",
|
|
615
|
+
start: 0,
|
|
616
|
+
end: 0,
|
|
464
617
|
properties,
|
|
465
|
-
}
|
|
618
|
+
};
|
|
619
|
+
args.push(objExpr);
|
|
466
620
|
} else {
|
|
467
621
|
// Normal props
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
622
|
+
/** @type {Array<import('acorn').Property | import('acorn').SpreadElement>} */
|
|
623
|
+
const properties = propsEntries.map(([key, value]) => {
|
|
624
|
+
/** @type {import('acorn').Property} */
|
|
625
|
+
const prop = {
|
|
471
626
|
type: "Property",
|
|
627
|
+
start: 0,
|
|
628
|
+
end: 0,
|
|
472
629
|
key: needsQuoting(key)
|
|
473
|
-
?
|
|
474
|
-
|
|
475
|
-
|
|
630
|
+
? /** @type {import('acorn').Literal} */ ({
|
|
631
|
+
type: "Literal",
|
|
632
|
+
start: 0,
|
|
633
|
+
end: 0,
|
|
634
|
+
value: key,
|
|
635
|
+
})
|
|
636
|
+
: /** @type {import('acorn').Identifier} */ ({
|
|
637
|
+
type: "Identifier",
|
|
638
|
+
start: 0,
|
|
639
|
+
end: 0,
|
|
640
|
+
name: key,
|
|
641
|
+
}),
|
|
642
|
+
value:
|
|
643
|
+
typeof value === "object" && value.type === "expression"
|
|
644
|
+
? /** @type {import('acorn').Expression} */ (value.expr)
|
|
645
|
+
: /** @type {import('acorn').Literal} */ ({
|
|
646
|
+
type: "Literal",
|
|
647
|
+
start: 0,
|
|
648
|
+
end: 0,
|
|
649
|
+
value: /** @type {string | boolean} */ (value),
|
|
650
|
+
}),
|
|
476
651
|
kind: "init",
|
|
477
652
|
method: false,
|
|
478
653
|
shorthand: false,
|
|
479
654
|
computed: false,
|
|
480
|
-
}
|
|
655
|
+
};
|
|
656
|
+
return prop;
|
|
481
657
|
});
|
|
658
|
+
/** @type {import('acorn').ObjectExpression} */
|
|
659
|
+
const objExpr = {
|
|
660
|
+
type: "ObjectExpression",
|
|
661
|
+
start: 0,
|
|
662
|
+
end: 0,
|
|
663
|
+
properties,
|
|
664
|
+
};
|
|
665
|
+
args.push(objExpr);
|
|
482
666
|
}
|
|
483
667
|
} else {
|
|
484
|
-
|
|
668
|
+
/** @type {import('acorn').Literal} */
|
|
669
|
+
const nullLiteral = { type: "Literal", start: 0, end: 0, value: null };
|
|
670
|
+
args.push(nullLiteral);
|
|
485
671
|
}
|
|
486
672
|
|
|
487
673
|
// Children arguments
|
|
@@ -489,12 +675,24 @@ function elementsToAST(node, pragma) {
|
|
|
489
675
|
args.push(elementsToAST(child, pragma));
|
|
490
676
|
}
|
|
491
677
|
|
|
492
|
-
|
|
678
|
+
/** @type {import('acorn').CallExpression} */
|
|
679
|
+
const callExpr = {
|
|
493
680
|
type: "CallExpression",
|
|
494
|
-
|
|
681
|
+
start: 0,
|
|
682
|
+
end: 0,
|
|
683
|
+
callee: /** @type {import('acorn').Identifier} */ ({
|
|
684
|
+
type: "Identifier",
|
|
685
|
+
start: 0,
|
|
686
|
+
end: 0,
|
|
687
|
+
name: pragma,
|
|
688
|
+
}),
|
|
495
689
|
arguments: args,
|
|
690
|
+
optional: false,
|
|
496
691
|
};
|
|
692
|
+
return callExpr;
|
|
497
693
|
}
|
|
498
694
|
|
|
499
|
-
|
|
695
|
+
/** @type {import('acorn').Literal} */
|
|
696
|
+
const nullLiteral = { type: "Literal", start: 0, end: 0, value: null };
|
|
697
|
+
return nullLiteral;
|
|
500
698
|
}
|