@tsrx/core 0.1.20 → 0.1.22
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/package.json +3 -3
- package/src/analyze/prune.js +119 -61
- package/src/analyze/validation.js +122 -5
- package/src/index.js +11 -3
- package/src/parse/index.js +157 -99
- package/src/plugin.js +2417 -844
- package/src/runtime/iterable.js +15 -13
- package/src/scope.js +25 -10
- package/src/source-map-utils.js +1 -1
- package/src/transform/await.js +5 -1
- package/src/transform/jsx/ast-builders.js +44 -59
- package/src/transform/jsx/helpers.js +8 -5
- package/src/transform/jsx/index.js +755 -344
- package/src/transform/jsx-interleave.js +1 -2
- package/src/transform/scoping.js +26 -63
- package/src/transform/segments.js +53 -5
- package/src/transform/style-ref.js +3 -11
- package/src/utils/builders.js +2 -3
- package/types/index.d.ts +179 -110
- package/types/jsx-platform.d.ts +6 -11
- package/types/parse.d.ts +36 -10
- package/types/runtime/ref.d.ts +1 -0
package/src/parse/index.js
CHANGED
|
@@ -38,32 +38,6 @@ export function DestructuringErrors() {
|
|
|
38
38
|
return this;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
/**
|
|
42
|
-
* Convert JSX node types to regular JavaScript node types
|
|
43
|
-
* @param {ESTreeJSX.JSXIdentifier | ESTreeJSX.JSXMemberExpression | AST.Node} node - The JSX node to convert
|
|
44
|
-
* @returns {AST.Identifier | AST.MemberExpression | AST.Node} The converted node
|
|
45
|
-
*/
|
|
46
|
-
export function convert_from_jsx(node) {
|
|
47
|
-
/** @type {AST.Identifier | AST.MemberExpression | AST.Node} */
|
|
48
|
-
let converted_node;
|
|
49
|
-
if (node.type === 'JSXIdentifier') {
|
|
50
|
-
converted_node = /** @type {AST.Identifier} */ (/** @type {unknown} */ (node));
|
|
51
|
-
converted_node.type = 'Identifier';
|
|
52
|
-
} else if (node.type === 'JSXMemberExpression') {
|
|
53
|
-
converted_node = /** @type {AST.MemberExpression} */ (/** @type {unknown} */ (node));
|
|
54
|
-
converted_node.type = 'MemberExpression';
|
|
55
|
-
converted_node.object = /** @type {AST.Identifier | AST.MemberExpression} */ (
|
|
56
|
-
convert_from_jsx(converted_node.object)
|
|
57
|
-
);
|
|
58
|
-
converted_node.property = /** @type {AST.Identifier} */ (
|
|
59
|
-
convert_from_jsx(converted_node.property)
|
|
60
|
-
);
|
|
61
|
-
} else {
|
|
62
|
-
converted_node = node;
|
|
63
|
-
}
|
|
64
|
-
return converted_node;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
41
|
const regex_whitespace_only = /\s/;
|
|
68
42
|
|
|
69
43
|
/**
|
|
@@ -95,18 +69,18 @@ export function skipWhitespace(parser) {
|
|
|
95
69
|
}
|
|
96
70
|
|
|
97
71
|
/**
|
|
98
|
-
* @param {AST.Node | null | undefined} node
|
|
72
|
+
* @param {AST.Node | ESTreeJSX.JSXText | null | undefined} node
|
|
99
73
|
* @returns {boolean}
|
|
100
74
|
*/
|
|
101
75
|
export function isWhitespaceTextNode(node) {
|
|
102
|
-
if (!node
|
|
76
|
+
if (!node) {
|
|
103
77
|
return false;
|
|
104
78
|
}
|
|
105
79
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
return /^\s*$/.test(expr.value);
|
|
80
|
+
if (node.type === 'JSXText') {
|
|
81
|
+
return /^\s*$/.test(node.value);
|
|
109
82
|
}
|
|
83
|
+
|
|
110
84
|
return false;
|
|
111
85
|
}
|
|
112
86
|
|
|
@@ -298,6 +272,87 @@ export function get_comment_handlers(source, comments, index = 0) {
|
|
|
298
272
|
return null;
|
|
299
273
|
}
|
|
300
274
|
|
|
275
|
+
/**
|
|
276
|
+
* @param {any} node
|
|
277
|
+
* @returns {node is (ESTreeJSX.JSXElement | ESTreeJSX.JSXFragment) & AST.NodeWithLocation}
|
|
278
|
+
*/
|
|
279
|
+
function isNativeTemplateNode(node) {
|
|
280
|
+
return (
|
|
281
|
+
(node?.type === 'JSXElement' ||
|
|
282
|
+
node?.type === 'JSXFragment' ||
|
|
283
|
+
node?.type === 'JSXStyleElement') &&
|
|
284
|
+
node.metadata?.native_tsrx
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* @param {any} node
|
|
290
|
+
* @returns {node is (ESTreeJSX.JSXElement | AST.JSXStyleElement) & AST.NodeWithLocation}
|
|
291
|
+
*/
|
|
292
|
+
function isNativeTemplateElement(node) {
|
|
293
|
+
return (
|
|
294
|
+
(node?.type === 'JSXElement' || node?.type === 'JSXStyleElement') &&
|
|
295
|
+
node.metadata?.native_tsrx
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* @param {any} node
|
|
301
|
+
* @returns {AST.Node[]}
|
|
302
|
+
*/
|
|
303
|
+
function getTemplateChildren(node) {
|
|
304
|
+
return Array.isArray(node?.children)
|
|
305
|
+
? /** @type {AST.Node[]} */ (/** @type {unknown} */ (node.children))
|
|
306
|
+
: [];
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* @param {any} node
|
|
311
|
+
* @returns {node is (ESTreeJSX.JSXElement | ESTreeJSX.JSXFragment) & AST.NodeWithLocation}
|
|
312
|
+
*/
|
|
313
|
+
function isEmptyTemplateNode(node) {
|
|
314
|
+
return isNativeTemplateNode(node) && getTemplateChildren(node).length === 0;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* @param {any} node
|
|
319
|
+
* @returns {any}
|
|
320
|
+
*/
|
|
321
|
+
function getNodeMetadata(node) {
|
|
322
|
+
const target = /** @type {AST.Node} */ (/** @type {unknown} */ (node));
|
|
323
|
+
target.metadata ??= { path: [] };
|
|
324
|
+
return target.metadata;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* @param {any} node
|
|
329
|
+
* @param {AST.CommentWithLocation} comment
|
|
330
|
+
*/
|
|
331
|
+
function pushInnerComment(node, comment) {
|
|
332
|
+
const target = /** @type {any} */ (node);
|
|
333
|
+
(target.innerComments ||= []).push(comment);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* @param {any} node
|
|
338
|
+
* @returns {boolean}
|
|
339
|
+
*/
|
|
340
|
+
function hasInnerComments(node) {
|
|
341
|
+
return !!(/** @type {any} */ (node).innerComments?.length);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* @param {ESTreeJSX.JSXElement | AST.JSXStyleElement} node
|
|
346
|
+
* @returns {string | null}
|
|
347
|
+
*/
|
|
348
|
+
function getJSXElementName(node) {
|
|
349
|
+
const name = node.openingElement?.name;
|
|
350
|
+
if (!name) return null;
|
|
351
|
+
if (name.type === 'JSXIdentifier') return name.name;
|
|
352
|
+
if (name.type === 'JSXNamespacedName') return `${name.namespace.name}:${name.name.name}`;
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
|
|
301
356
|
return {
|
|
302
357
|
/**
|
|
303
358
|
* @type {Parse.Options['onComment']}
|
|
@@ -348,19 +403,13 @@ export function get_comment_handlers(source, comments, index = 0) {
|
|
|
348
403
|
_(node, { next, path }) {
|
|
349
404
|
const metadata = /** @type {AST.Node} */ (node)?.metadata;
|
|
350
405
|
|
|
351
|
-
/**
|
|
352
|
-
* Check if a comment is inside an attribute expression
|
|
353
|
-
* of any ancestor Elements.
|
|
354
|
-
* @returns {boolean}
|
|
355
|
-
*/
|
|
406
|
+
/** @returns {boolean} */
|
|
356
407
|
function isCommentInsideAttributeExpression() {
|
|
357
408
|
for (let i = path.length - 1; i >= 0; i--) {
|
|
358
409
|
const ancestor = path[i];
|
|
359
410
|
if (
|
|
360
411
|
ancestor &&
|
|
361
|
-
(ancestor.type === 'JSXAttribute' ||
|
|
362
|
-
ancestor.type === 'Attribute' ||
|
|
363
|
-
ancestor.type === 'JSXExpressionContainer')
|
|
412
|
+
(ancestor.type === 'JSXAttribute' || ancestor.type === 'JSXExpressionContainer')
|
|
364
413
|
) {
|
|
365
414
|
return true;
|
|
366
415
|
}
|
|
@@ -369,8 +418,6 @@ export function get_comment_handlers(source, comments, index = 0) {
|
|
|
369
418
|
}
|
|
370
419
|
|
|
371
420
|
/**
|
|
372
|
-
* Check if a comment is inside any attribute of ancestor Elements,
|
|
373
|
-
* but NOT if we're currently traversing inside that attribute.
|
|
374
421
|
* @param {AST.CommentWithLocation} comment
|
|
375
422
|
* @returns {boolean}
|
|
376
423
|
*/
|
|
@@ -378,14 +425,17 @@ export function get_comment_handlers(source, comments, index = 0) {
|
|
|
378
425
|
for (let i = path.length - 1; i >= 0; i--) {
|
|
379
426
|
const ancestor = path[i];
|
|
380
427
|
// we would definitely reach the attribute first before getting to the element
|
|
381
|
-
if (ancestor.type === 'JSXAttribute'
|
|
428
|
+
if (ancestor.type === 'JSXAttribute') {
|
|
382
429
|
return false;
|
|
383
430
|
}
|
|
384
|
-
if (ancestor
|
|
385
|
-
for (const attr of
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
431
|
+
if (isNativeTemplateElement(ancestor)) {
|
|
432
|
+
for (const attr of ancestor.openingElement.attributes) {
|
|
433
|
+
if (
|
|
434
|
+
attr.start !== undefined &&
|
|
435
|
+
attr.end !== undefined &&
|
|
436
|
+
comment.start >= attr.start &&
|
|
437
|
+
comment.end <= attr.end
|
|
438
|
+
) {
|
|
389
439
|
return true;
|
|
390
440
|
}
|
|
391
441
|
}
|
|
@@ -395,23 +445,20 @@ export function get_comment_handlers(source, comments, index = 0) {
|
|
|
395
445
|
}
|
|
396
446
|
|
|
397
447
|
/**
|
|
398
|
-
* If a comment is located between an empty Element's opening and closing tags,
|
|
399
|
-
* attach it to the Element as `innerComments`.
|
|
400
448
|
* @param {AST.CommentWithLocation} comment
|
|
401
|
-
* @returns {AST.
|
|
449
|
+
* @returns {((ESTreeJSX.JSXElement | ESTreeJSX.JSXFragment) & AST.NodeWithLocation) | null}
|
|
402
450
|
*/
|
|
403
451
|
function getEmptyElementInnerCommentTarget(comment) {
|
|
404
|
-
const element =
|
|
405
|
-
|
|
406
|
-
|
|
452
|
+
const element = path.findLast((ancestor) => isNativeTemplateNode(ancestor));
|
|
453
|
+
const openingEnd =
|
|
454
|
+
element?.type === 'JSXFragment'
|
|
455
|
+
? element.openingFragment?.end
|
|
456
|
+
: element?.openingElement?.end;
|
|
407
457
|
if (
|
|
408
458
|
!element ||
|
|
409
|
-
element
|
|
410
|
-
|
|
411
|
-
!(
|
|
412
|
-
comment.start >= /** @type {AST.NodeWithLocation} */ (element.openingElement).end &&
|
|
413
|
-
comment.end <= /** @type {AST.NodeWithLocation} */ (element).end
|
|
414
|
-
)
|
|
459
|
+
!isEmptyTemplateNode(element) ||
|
|
460
|
+
openingEnd === undefined ||
|
|
461
|
+
!(comment.start >= openingEnd && comment.end <= element.end)
|
|
415
462
|
) {
|
|
416
463
|
return null;
|
|
417
464
|
}
|
|
@@ -426,22 +473,16 @@ export function get_comment_handlers(source, comments, index = 0) {
|
|
|
426
473
|
// parent <style> element's content range so they don't leak to
|
|
427
474
|
// subsequent JS nodes.
|
|
428
475
|
if (node.type === 'StyleSheet') {
|
|
429
|
-
const styleElement =
|
|
430
|
-
|
|
431
|
-
(
|
|
432
|
-
ancestor
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
)
|
|
437
|
-
);
|
|
476
|
+
const styleElement =
|
|
477
|
+
/** @type {(ESTreeJSX.JSXElement & AST.NodeWithLocation) | undefined} */ (
|
|
478
|
+
path.findLast(
|
|
479
|
+
(ancestor) =>
|
|
480
|
+
isNativeTemplateElement(ancestor) && getJSXElementName(ancestor) === 'style',
|
|
481
|
+
)
|
|
482
|
+
);
|
|
438
483
|
if (styleElement) {
|
|
439
|
-
const cssStart =
|
|
440
|
-
|
|
441
|
-
styleElement.start;
|
|
442
|
-
const cssEnd =
|
|
443
|
-
/** @type {AST.NodeWithLocation} */ (styleElement.closingElement)?.start ??
|
|
444
|
-
styleElement.end;
|
|
484
|
+
const cssStart = styleElement.openingElement?.end ?? styleElement.start;
|
|
485
|
+
const cssEnd = styleElement.closingElement?.start ?? styleElement.end;
|
|
445
486
|
while (comments[0] && comments[0].start >= cssStart && comments[0].end <= cssEnd) {
|
|
446
487
|
comments.shift();
|
|
447
488
|
}
|
|
@@ -453,8 +494,7 @@ export function get_comment_handlers(source, comments, index = 0) {
|
|
|
453
494
|
// For empty template elements, keep comments as `innerComments`.
|
|
454
495
|
// The Prettier plugin uses `innerComments` to preserve them and
|
|
455
496
|
// to avoid collapsing the element into self-closing syntax.
|
|
456
|
-
const isEmptyElement =
|
|
457
|
-
node.type === 'Element' && (!node.children || node.children.length === 0);
|
|
497
|
+
const isEmptyElement = isEmptyTemplateNode(node);
|
|
458
498
|
if (!isEmptyElement) {
|
|
459
499
|
while (
|
|
460
500
|
comments[0] &&
|
|
@@ -468,9 +508,7 @@ export function get_comment_handlers(source, comments, index = 0) {
|
|
|
468
508
|
// before the child element is pushed to the parser's #path, causing
|
|
469
509
|
// comments inside the child to get the parent's containerId.
|
|
470
510
|
const commentStart = comments[0].start;
|
|
471
|
-
const isInsideChildElement =
|
|
472
|
-
node
|
|
473
|
-
).children?.some(
|
|
511
|
+
const isInsideChildElement = getTemplateChildren(node).some(
|
|
474
512
|
(child) =>
|
|
475
513
|
child &&
|
|
476
514
|
child.start !== undefined &&
|
|
@@ -491,7 +529,7 @@ export function get_comment_handlers(source, comments, index = 0) {
|
|
|
491
529
|
comments[0] &&
|
|
492
530
|
comments[0].start < /** @type {AST.NodeWithLocation} */ (node).start
|
|
493
531
|
) {
|
|
494
|
-
// Skip comments that are inside an attribute of an ancestor
|
|
532
|
+
// Skip comments that are inside an attribute of an ancestor JSX element.
|
|
495
533
|
// Since zimmerframe visits children before attributes, we need to leave
|
|
496
534
|
// these comments for when the attribute nodes are visited.
|
|
497
535
|
if (
|
|
@@ -506,7 +544,8 @@ export function get_comment_handlers(source, comments, index = 0) {
|
|
|
506
544
|
/** @type {AST.CommentWithLocation} */ (comments[0]),
|
|
507
545
|
);
|
|
508
546
|
if (maybeInner) {
|
|
509
|
-
(
|
|
547
|
+
pushInnerComment(
|
|
548
|
+
maybeInner,
|
|
510
549
|
/** @type {AST.CommentWithLocation} */ (comments.shift()),
|
|
511
550
|
);
|
|
512
551
|
continue;
|
|
@@ -536,17 +575,18 @@ export function get_comment_handlers(source, comments, index = 0) {
|
|
|
536
575
|
continue;
|
|
537
576
|
}
|
|
538
577
|
|
|
539
|
-
const ancestorElements =
|
|
540
|
-
|
|
541
|
-
|
|
578
|
+
const ancestorElements = path
|
|
579
|
+
.filter((ancestor) => isNativeTemplateNode(ancestor) && ancestor.loc)
|
|
580
|
+
.map((ancestor) => /** @type {AST.NodeWithLocation} */ (ancestor))
|
|
581
|
+
.sort((a, b) => a.loc.start.line - b.loc.start.line);
|
|
542
582
|
|
|
543
583
|
const targetAncestor = ancestorElements.find(
|
|
544
584
|
(ancestor) => comment.loc.start.line < ancestor.loc.start.line,
|
|
545
585
|
);
|
|
546
586
|
|
|
547
587
|
if (targetAncestor) {
|
|
548
|
-
|
|
549
|
-
(
|
|
588
|
+
const targetMetadata = getNodeMetadata(targetAncestor);
|
|
589
|
+
(targetMetadata.elementLeadingComments ||= []).push(comment);
|
|
550
590
|
continue;
|
|
551
591
|
}
|
|
552
592
|
|
|
@@ -595,8 +635,8 @@ export function get_comment_handlers(source, comments, index = 0) {
|
|
|
595
635
|
return;
|
|
596
636
|
}
|
|
597
637
|
}
|
|
598
|
-
// Handle empty
|
|
599
|
-
if (
|
|
638
|
+
// Handle empty template nodes the same way as empty BlockStatements
|
|
639
|
+
if (isEmptyTemplateNode(node)) {
|
|
600
640
|
// Collect all comments that fall within this empty element
|
|
601
641
|
while (
|
|
602
642
|
comments[0] &&
|
|
@@ -604,9 +644,26 @@ export function get_comment_handlers(source, comments, index = 0) {
|
|
|
604
644
|
comments[0].end < /** @type {AST.NodeWithLocation} */ (node).end
|
|
605
645
|
) {
|
|
606
646
|
const comment = /** @type {AST.CommentWithLocation} */ (comments.shift());
|
|
607
|
-
(node
|
|
647
|
+
pushInnerComment(node, comment);
|
|
608
648
|
}
|
|
609
|
-
if (node
|
|
649
|
+
if (hasInnerComments(node)) {
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// Trailing comments after the last statement/render inside a `@{ … }`
|
|
655
|
+
// code block (before its `}`) have no following node to attach to and
|
|
656
|
+
// would otherwise be claimed by the enclosing element's closing tag.
|
|
657
|
+
// Claim them here as the block's inner comments.
|
|
658
|
+
if (node.type === 'JSXCodeBlock') {
|
|
659
|
+
while (
|
|
660
|
+
comments[0] &&
|
|
661
|
+
comments[0].start >= /** @type {AST.NodeWithLocation} */ (node).start &&
|
|
662
|
+
comments[0].start < /** @type {AST.NodeWithLocation} */ (node).end
|
|
663
|
+
) {
|
|
664
|
+
pushInnerComment(node, /** @type {AST.CommentWithLocation} */ (comments.shift()));
|
|
665
|
+
}
|
|
666
|
+
if (comments.length === 0) {
|
|
610
667
|
return;
|
|
611
668
|
}
|
|
612
669
|
}
|
|
@@ -682,7 +739,8 @@ export function get_comment_handlers(source, comments, index = 0) {
|
|
|
682
739
|
|
|
683
740
|
const maybeInner = getEmptyElementInnerCommentTarget(potentialComment);
|
|
684
741
|
if (maybeInner) {
|
|
685
|
-
(
|
|
742
|
+
pushInnerComment(
|
|
743
|
+
maybeInner,
|
|
686
744
|
/** @type {AST.CommentWithLocation} */ (comments.shift()),
|
|
687
745
|
);
|
|
688
746
|
continue;
|
|
@@ -712,7 +770,8 @@ export function get_comment_handlers(source, comments, index = 0) {
|
|
|
712
770
|
|
|
713
771
|
const maybeInner = getEmptyElementInnerCommentTarget(comment);
|
|
714
772
|
if (maybeInner) {
|
|
715
|
-
(
|
|
773
|
+
pushInnerComment(
|
|
774
|
+
maybeInner,
|
|
716
775
|
/** @type {AST.CommentWithLocation} */ (comments.shift()),
|
|
717
776
|
);
|
|
718
777
|
continue;
|
|
@@ -727,7 +786,8 @@ export function get_comment_handlers(source, comments, index = 0) {
|
|
|
727
786
|
/** @type {AST.CommentWithLocation} */ (comments[0]),
|
|
728
787
|
);
|
|
729
788
|
if (maybeInner) {
|
|
730
|
-
(
|
|
789
|
+
pushInnerComment(
|
|
790
|
+
maybeInner,
|
|
731
791
|
/** @type {AST.CommentWithLocation} */ (comments.shift()),
|
|
732
792
|
);
|
|
733
793
|
return;
|
|
@@ -814,7 +874,7 @@ export function get_comment_handlers(source, comments, index = 0) {
|
|
|
814
874
|
// check if there's also a blank line after the comment(s) before the next node
|
|
815
875
|
// If so, attach comments as trailing to preserve the grouping
|
|
816
876
|
// Only do this for statement-level contexts (BlockStatement, Program),
|
|
817
|
-
// not for
|
|
877
|
+
// not for JSX element children or other contexts
|
|
818
878
|
const isStatementContext =
|
|
819
879
|
parent.type === 'BlockStatement' || parent.type === 'Program';
|
|
820
880
|
|
|
@@ -850,16 +910,14 @@ export function get_comment_handlers(source, comments, index = 0) {
|
|
|
850
910
|
const hasBlankLineAfter = /\n\s*\n/.test(sliceAfterComments);
|
|
851
911
|
|
|
852
912
|
if (hasBlankLineAfter) {
|
|
853
|
-
// Don't attach comments as trailing if
|
|
854
|
-
|
|
855
|
-
// This means the comments are inside the Element (between opening and closing tags)
|
|
856
|
-
const nextIsElement = nextSibling.type === 'Element';
|
|
913
|
+
// Don't attach comments as trailing if they are inside the next template node.
|
|
914
|
+
const nextIsElement = isNativeTemplateNode(nextSibling);
|
|
857
915
|
const commentsInsideElement =
|
|
858
916
|
nextIsElement &&
|
|
859
917
|
nextSibling.loc &&
|
|
860
918
|
comments.some((c) => {
|
|
861
919
|
if (!c.loc) return false;
|
|
862
|
-
// Check if comment is on a line between
|
|
920
|
+
// Check if comment is on a line between the JSX element's start and end lines
|
|
863
921
|
return (
|
|
864
922
|
c.loc.start.line >= nextSibling.loc.start.line &&
|
|
865
923
|
c.loc.end.line <= nextSibling.loc.end.line
|