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.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as acorn from "acorn";
2
2
  import { generate } from "astring";
3
- import { simple } from "acorn-walk";
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 {Object} TextNode
14
+ * @typedef {object} TextNode
15
15
  * @property {'text'} type
16
16
  * @property {string} value
17
17
  */
18
18
 
19
19
  /**
20
- * @typedef {Object} ExpressionNode
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 {Object} Token
27
+ * @typedef {object} Token
28
28
  * @property {'openTag' | 'closeTag' | 'text' | 'expression'} type
29
29
  * @property {string} [tag]
30
- * @property {Object<string, string | boolean>} [props]
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 {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
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
- const ast = acorn.parse(code, {
50
- ecmaVersion: "latest",
51
- sourceType: "module",
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
- simple(ast, {
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
- for (const key in node) {
64
- delete node[key];
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
- return generate(ast);
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').Node} ast - The AST to modify
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
- (node) =>
89
- node.type === "ImportDeclaration" &&
90
- node.source.value === moduleName &&
91
- node.specifiers.some(
92
- (spec) => spec.type === "ImportSpecifier" && spec.imported.name === exportName,
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').Node} node - The tagged template expression node
176
+ * @param {import('acorn').TaggedTemplateExpression} node - The tagged template expression node
127
177
  * @param {string} pragma - The pragma function name
128
- * @returns {import('acorn').Node} The transformed AST node
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
- return { type: "Literal", value: null };
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
- return {
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
- children.push({ type: "text", value: text });
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
- children.push({ type: "expression", value: token.value });
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
- tokens.push({ type: "closeTag", tag: tagName });
309
+ /** @type {Token} */
310
+ const closeToken = { type: "closeTag", tag: tagName };
311
+ tokens.push(closeToken);
248
312
  } else {
249
313
  const props = parseAttributes(attrsStr);
250
- tokens.push({
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
- tokens.push({ type: "text", value: "<" });
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
- tokens.push({ type: "expression", value: exprMatch[0] });
343
+ /** @type {Token} */
344
+ const exprToken = { type: "expression", value: exprMatch[0] };
345
+ tokens.push(exprToken);
276
346
  } else if (text.trim()) {
277
- tokens.push({ type: "text", value: text });
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
- node.tag = { type: "expression", expr: placeholderMap.get(node.tag) };
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
- newProps["...spread"] = { type: "expression", expr: placeholderMap.get(key) };
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
- newProps[key] = { type: "expression", expr: placeholderMap.get(value) };
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
- quasis.push({
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
- expressions.push(placeholderMap.get(parts[i]));
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 = node.children.map(resolve);
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
- return parts
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
- return { type: "expression", expr: placeholderMap.get(p) };
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
- return { type: "text", value: p };
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').Node} The AST node
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
- return { type: "Literal", value: node.value };
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
- args.push({ type: "Literal", value: node.tag });
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 (spreadProp && propsEntries.length === 1) {
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
- return {
567
+ if (key === "...spread" && typeof value === "object" && value.type === "expression") {
568
+ /** @type {import('acorn').SpreadElement} */
569
+ const spread = {
445
570
  type: "SpreadElement",
446
- argument: value.expr,
571
+ start: 0,
572
+ end: 0,
573
+ argument: /** @type {import('acorn').Expression} */ (value.expr),
447
574
  };
575
+ return spread;
448
576
  }
449
- return {
577
+ /** @type {import('acorn').Property} */
578
+ const prop = {
450
579
  type: "Property",
580
+ start: 0,
581
+ end: 0,
451
582
  key: needsQuoting(key)
452
- ? { type: "Literal", value: key }
453
- : { type: "Identifier", name: key },
454
- value: value.type === "expression" ? value.expr : { type: "Literal", value },
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
- args.push({
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
- args.push({
469
- type: "ObjectExpression",
470
- properties: propsEntries.map(([key, value]) => ({
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
- ? { type: "Literal", value: key }
474
- : { type: "Identifier", name: key },
475
- value: value.type === "expression" ? value.expr : { type: "Literal", value },
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
- args.push({ type: "Literal", value: null });
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
- return {
678
+ /** @type {import('acorn').CallExpression} */
679
+ const callExpr = {
493
680
  type: "CallExpression",
494
- callee: { type: "Identifier", name: pragma },
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
- return { type: "Literal", value: null };
695
+ /** @type {import('acorn').Literal} */
696
+ const nullLiteral = { type: "Literal", start: 0, end: 0, value: null };
697
+ return nullLiteral;
500
698
  }