@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/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Core compiler infrastructure for TSRX syntax",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Dominic Gannaway",
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.22",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
@@ -60,10 +60,10 @@
|
|
|
60
60
|
"dependencies": {
|
|
61
61
|
"@jridgewell/sourcemap-codec": "^1.5.5",
|
|
62
62
|
"@noble/hashes": "^2.2.0",
|
|
63
|
-
"@sveltejs/acorn-typescript": "^1.0.
|
|
63
|
+
"@sveltejs/acorn-typescript": "^1.0.10",
|
|
64
64
|
"@types/estree-jsx": "^1.0.5",
|
|
65
65
|
"@types/estree": "^1.0.8",
|
|
66
|
-
"acorn": "^8.
|
|
66
|
+
"acorn": "^8.16.0",
|
|
67
67
|
"esrap": "^2.2.8",
|
|
68
68
|
"is-reference": "^3.0.3",
|
|
69
69
|
"magic-string": "^0.30.18",
|
package/src/analyze/prune.js
CHANGED
|
@@ -19,28 +19,75 @@ let style_identifier_classes;
|
|
|
19
19
|
/** @type {TopScopedClasses} */
|
|
20
20
|
let top_scoped_classes;
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* @param {any} node
|
|
24
|
+
* @returns {boolean}
|
|
25
|
+
*/
|
|
26
|
+
function is_native_jsx_element(node) {
|
|
27
|
+
return node?.type === 'JSXElement' && node.metadata?.native_tsrx;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {any} node
|
|
32
|
+
* @returns {any}
|
|
33
|
+
*/
|
|
34
|
+
function get_element_name(node) {
|
|
35
|
+
return node.openingElement?.name ?? node.id;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {any} node
|
|
40
|
+
* @returns {any[]}
|
|
41
|
+
*/
|
|
42
|
+
function get_element_attributes(node) {
|
|
43
|
+
return node.openingElement?.attributes ?? node.attributes ?? [];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @param {any} attribute
|
|
48
|
+
* @returns {string | null}
|
|
49
|
+
*/
|
|
50
|
+
function get_attribute_name(attribute) {
|
|
51
|
+
const name = attribute.name;
|
|
52
|
+
if (name?.type === 'JSXIdentifier' || name?.type === 'Identifier') return name.name;
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {any} attribute
|
|
58
|
+
* @returns {any}
|
|
59
|
+
*/
|
|
60
|
+
function get_attribute_value(attribute) {
|
|
61
|
+
const value = attribute.value;
|
|
62
|
+
return value?.type === 'JSXExpressionContainer' ? value.expression : value;
|
|
63
|
+
}
|
|
64
|
+
|
|
22
65
|
/**
|
|
23
66
|
* Returns true if node is a DOM element (not a component).
|
|
24
67
|
* @param {AST.Node} node
|
|
25
68
|
* @returns {boolean}
|
|
26
69
|
*/
|
|
27
70
|
function is_element_dom_element(node) {
|
|
28
|
-
const id =
|
|
71
|
+
const id = get_element_name(node);
|
|
29
72
|
return (
|
|
30
|
-
id.type === 'Identifier' &&
|
|
73
|
+
(id.type === 'Identifier' || id.type === 'JSXIdentifier') &&
|
|
31
74
|
id.name[0].toLowerCase() === id.name[0] &&
|
|
32
75
|
id.name !== 'children' &&
|
|
33
|
-
!id.tracked
|
|
76
|
+
!id.tracked &&
|
|
77
|
+
!id.dynamic
|
|
34
78
|
);
|
|
35
79
|
}
|
|
36
80
|
|
|
37
81
|
/**
|
|
38
82
|
* Returns true if element is dynamic.
|
|
39
|
-
* @param {
|
|
83
|
+
* @param {any} node
|
|
40
84
|
* @returns {boolean}
|
|
41
85
|
*/
|
|
42
86
|
function is_element_dynamic(node) {
|
|
43
|
-
|
|
87
|
+
const id = get_element_name(node);
|
|
88
|
+
return id?.type === 'Identifier' || id?.type === 'JSXIdentifier'
|
|
89
|
+
? !!(id.tracked || id.dynamic)
|
|
90
|
+
: false;
|
|
44
91
|
}
|
|
45
92
|
|
|
46
93
|
// CSS selector constants
|
|
@@ -220,7 +267,7 @@ function truncate(node) {
|
|
|
220
267
|
/**
|
|
221
268
|
* @param {AST.CSS.RelativeSelector[]} relative_selectors
|
|
222
269
|
* @param {AST.CSS.Rule} rule
|
|
223
|
-
* @param {
|
|
270
|
+
* @param {any} element
|
|
224
271
|
* @param {Direction} direction
|
|
225
272
|
* @returns {boolean}
|
|
226
273
|
*/
|
|
@@ -271,12 +318,12 @@ function apply_selector(relative_selectors, rule, element, direction) {
|
|
|
271
318
|
}
|
|
272
319
|
|
|
273
320
|
/**
|
|
274
|
-
* @param {
|
|
321
|
+
* @param {any} node
|
|
275
322
|
* @param {boolean} adjacent_only
|
|
276
|
-
* @returns {
|
|
323
|
+
* @returns {any[]}
|
|
277
324
|
*/
|
|
278
325
|
function get_ancestor_elements(node, adjacent_only) {
|
|
279
|
-
/** @type {
|
|
326
|
+
/** @type {any[]} */
|
|
280
327
|
const ancestors = [];
|
|
281
328
|
|
|
282
329
|
const path = node.metadata.path;
|
|
@@ -285,7 +332,7 @@ function get_ancestor_elements(node, adjacent_only) {
|
|
|
285
332
|
while (i--) {
|
|
286
333
|
const parent = path[i];
|
|
287
334
|
|
|
288
|
-
if (parent
|
|
335
|
+
if (is_native_jsx_element(parent)) {
|
|
289
336
|
ancestors.push(parent);
|
|
290
337
|
if (adjacent_only) {
|
|
291
338
|
break;
|
|
@@ -297,12 +344,12 @@ function get_ancestor_elements(node, adjacent_only) {
|
|
|
297
344
|
}
|
|
298
345
|
|
|
299
346
|
/**
|
|
300
|
-
* @param {
|
|
347
|
+
* @param {any} node
|
|
301
348
|
* @param {boolean} adjacent_only
|
|
302
|
-
* @returns {
|
|
349
|
+
* @returns {any[]}
|
|
303
350
|
*/
|
|
304
351
|
function get_descendant_elements(node, adjacent_only) {
|
|
305
|
-
/** @type {
|
|
352
|
+
/** @type {any[]} */
|
|
306
353
|
const descendants = [];
|
|
307
354
|
|
|
308
355
|
/**
|
|
@@ -311,26 +358,23 @@ function get_descendant_elements(node, adjacent_only) {
|
|
|
311
358
|
* @returns {void}
|
|
312
359
|
*/
|
|
313
360
|
function visit(current_node, depth = 0) {
|
|
314
|
-
if (current_node
|
|
361
|
+
if (is_native_jsx_element(current_node) && current_node !== node) {
|
|
315
362
|
descendants.push(current_node);
|
|
316
363
|
if (adjacent_only) return; // Only direct children for '>' combinator
|
|
317
364
|
}
|
|
318
365
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
for (const child of /** @type {AST.Element} */ (current_node).children) {
|
|
366
|
+
if (Array.isArray(/** @type {any} */ (current_node).children)) {
|
|
367
|
+
for (const child of /** @type {any} */ (current_node).children) {
|
|
322
368
|
visit(child, depth + 1);
|
|
323
369
|
}
|
|
324
370
|
}
|
|
325
371
|
|
|
326
|
-
// For template nodes and interpolation expressions
|
|
327
372
|
if (
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
typeof
|
|
331
|
-
'object'
|
|
373
|
+
current_node.type === 'JSXExpressionContainer' &&
|
|
374
|
+
current_node.expression &&
|
|
375
|
+
typeof current_node.expression === 'object'
|
|
332
376
|
) {
|
|
333
|
-
visit(
|
|
377
|
+
visit(current_node.expression, depth + 1);
|
|
334
378
|
}
|
|
335
379
|
}
|
|
336
380
|
|
|
@@ -357,18 +401,19 @@ function can_render_dynamic_content(element, check_classes = false) {
|
|
|
357
401
|
|
|
358
402
|
// Either a dynamic element or component (only can tell at runtime)
|
|
359
403
|
// But dynamic elements should return false ideally
|
|
360
|
-
if (is_element_dynamic(
|
|
404
|
+
if (is_element_dynamic(element)) {
|
|
361
405
|
return true;
|
|
362
406
|
}
|
|
363
407
|
|
|
364
408
|
// Check for dynamic class attributes if requested (for class-based selectors)
|
|
365
|
-
if (check_classes
|
|
366
|
-
for (const attr of
|
|
367
|
-
if (attr.type === '
|
|
409
|
+
if (check_classes) {
|
|
410
|
+
for (const attr of get_element_attributes(element)) {
|
|
411
|
+
if (attr.type === 'JSXAttribute' && get_attribute_name(attr) === 'class') {
|
|
412
|
+
const value = get_attribute_value(attr);
|
|
368
413
|
// Check if class value is an expression (not a static string)
|
|
369
|
-
if (
|
|
414
|
+
if (value && typeof value === 'object') {
|
|
370
415
|
// If it's a CallExpression or other dynamic value, it's dynamic
|
|
371
|
-
if (
|
|
416
|
+
if (value.type !== 'Literal') {
|
|
372
417
|
return true;
|
|
373
418
|
}
|
|
374
419
|
}
|
|
@@ -383,7 +428,7 @@ function can_render_dynamic_content(element, check_classes = false) {
|
|
|
383
428
|
* @param {AST.Node} node
|
|
384
429
|
* @param {Direction} direction
|
|
385
430
|
* @param {boolean} adjacent_only
|
|
386
|
-
* @returns {Map<
|
|
431
|
+
* @returns {Map<any, boolean>}
|
|
387
432
|
*/
|
|
388
433
|
function get_possible_element_siblings(node, direction, adjacent_only) {
|
|
389
434
|
const siblings = new Map();
|
|
@@ -415,7 +460,7 @@ function get_possible_element_siblings(node, direction, adjacent_only) {
|
|
|
415
460
|
for (let i = start; i !== end; i += step) {
|
|
416
461
|
const sibling = container[i];
|
|
417
462
|
|
|
418
|
-
if (sibling
|
|
463
|
+
if (is_native_jsx_element(sibling)) {
|
|
419
464
|
siblings.set(sibling, true);
|
|
420
465
|
// Don't break for dynamic elements (children and dynamic components)
|
|
421
466
|
// as they can render dynamic content or might render nothing
|
|
@@ -425,13 +470,7 @@ function get_possible_element_siblings(node, direction, adjacent_only) {
|
|
|
425
470
|
}
|
|
426
471
|
}
|
|
427
472
|
// Stop at non-whitespace text nodes for adjacent selectors
|
|
428
|
-
else if (
|
|
429
|
-
adjacent_only &&
|
|
430
|
-
(sibling.type === 'TSRXExpression' || sibling.type === 'Text') &&
|
|
431
|
-
sibling.expression.type === 'Literal' &&
|
|
432
|
-
typeof sibling.expression.value === 'string' &&
|
|
433
|
-
sibling.expression.value.trim()
|
|
434
|
-
) {
|
|
473
|
+
else if (adjacent_only && sibling.type === 'JSXText' && sibling.value.trim()) {
|
|
435
474
|
break;
|
|
436
475
|
}
|
|
437
476
|
}
|
|
@@ -443,7 +482,7 @@ function get_possible_element_siblings(node, direction, adjacent_only) {
|
|
|
443
482
|
* @param {AST.CSS.RelativeSelector} relative_selector
|
|
444
483
|
* @param {AST.CSS.RelativeSelector[]} rest_selectors
|
|
445
484
|
* @param {AST.CSS.Rule} rule
|
|
446
|
-
* @param {
|
|
485
|
+
* @param {any} node
|
|
447
486
|
* @param {Direction} direction
|
|
448
487
|
* @returns {boolean}
|
|
449
488
|
*/
|
|
@@ -515,7 +554,7 @@ function apply_combinator(relative_selector, rest_selectors, rule, node, directi
|
|
|
515
554
|
|
|
516
555
|
for (let i = search_start; i < search_end; i++) {
|
|
517
556
|
const subsequent = container[i];
|
|
518
|
-
if (subsequent
|
|
557
|
+
if (is_native_jsx_element(subsequent)) {
|
|
519
558
|
if (apply_selector(remaining, rule, subsequent, direction)) {
|
|
520
559
|
sibling_matched = true;
|
|
521
560
|
break;
|
|
@@ -529,7 +568,7 @@ function apply_combinator(relative_selector, rest_selectors, rule, node, directi
|
|
|
529
568
|
}
|
|
530
569
|
// Don't apply_selector for dynamic elements - they won't match regular element selectors
|
|
531
570
|
} else if (
|
|
532
|
-
possible_sibling
|
|
571
|
+
is_native_jsx_element(possible_sibling) &&
|
|
533
572
|
apply_selector(rest_selectors, rule, possible_sibling, direction)
|
|
534
573
|
) {
|
|
535
574
|
sibling_matched = true;
|
|
@@ -551,7 +590,7 @@ function apply_combinator(relative_selector, rest_selectors, rule, node, directi
|
|
|
551
590
|
}
|
|
552
591
|
/**
|
|
553
592
|
* @param {AST.Node} node
|
|
554
|
-
* @returns {
|
|
593
|
+
* @returns {any | null}
|
|
555
594
|
*/
|
|
556
595
|
function get_element_parent(node) {
|
|
557
596
|
// Check if metadata and path exist
|
|
@@ -565,7 +604,7 @@ function get_element_parent(node) {
|
|
|
565
604
|
while (i--) {
|
|
566
605
|
const parent = path[i];
|
|
567
606
|
|
|
568
|
-
if (parent
|
|
607
|
+
if (is_native_jsx_element(parent)) {
|
|
569
608
|
return parent;
|
|
570
609
|
}
|
|
571
610
|
}
|
|
@@ -664,11 +703,12 @@ function is_global(selector, rule) {
|
|
|
664
703
|
}
|
|
665
704
|
|
|
666
705
|
/**
|
|
667
|
-
* @param {
|
|
668
|
-
* @returns {
|
|
706
|
+
* @param {any} attribute
|
|
707
|
+
* @returns {boolean}
|
|
669
708
|
*/
|
|
670
709
|
function is_text_attribute(attribute) {
|
|
671
|
-
|
|
710
|
+
const value = get_attribute_value(attribute);
|
|
711
|
+
return value?.type === 'Literal' && typeof value.value === 'string';
|
|
672
712
|
}
|
|
673
713
|
|
|
674
714
|
/**
|
|
@@ -702,7 +742,7 @@ function test_attribute(operator, expected_value, case_insensitive, value) {
|
|
|
702
742
|
}
|
|
703
743
|
|
|
704
744
|
/**
|
|
705
|
-
* @param {
|
|
745
|
+
* @param {any} node
|
|
706
746
|
* @param {string} name
|
|
707
747
|
* @param {string | null} expected_value
|
|
708
748
|
* @param {string | null} operator
|
|
@@ -710,18 +750,29 @@ function test_attribute(operator, expected_value, case_insensitive, value) {
|
|
|
710
750
|
* @returns {boolean}
|
|
711
751
|
*/
|
|
712
752
|
function attribute_matches(node, name, expected_value, operator, case_insensitive) {
|
|
713
|
-
for (const attribute of node
|
|
714
|
-
if (attribute.type === '
|
|
753
|
+
for (const attribute of get_element_attributes(node)) {
|
|
754
|
+
if (attribute.type === 'JSXSpreadAttribute') return true;
|
|
715
755
|
|
|
716
|
-
if (attribute.type !== '
|
|
756
|
+
if (attribute.type !== 'JSXAttribute') continue;
|
|
717
757
|
|
|
718
758
|
const lowerCaseName = name.toLowerCase();
|
|
719
|
-
|
|
759
|
+
const attributeName = get_attribute_name(attribute);
|
|
760
|
+
if (
|
|
761
|
+
!attributeName ||
|
|
762
|
+
![lowerCaseName, `$${lowerCaseName}`].includes(attributeName.toLowerCase())
|
|
763
|
+
) {
|
|
764
|
+
continue;
|
|
765
|
+
}
|
|
720
766
|
|
|
721
767
|
if (expected_value === null) return true;
|
|
722
768
|
|
|
723
769
|
if (is_text_attribute(attribute)) {
|
|
724
|
-
return test_attribute(
|
|
770
|
+
return test_attribute(
|
|
771
|
+
operator,
|
|
772
|
+
expected_value,
|
|
773
|
+
case_insensitive,
|
|
774
|
+
get_attribute_value(attribute).value,
|
|
775
|
+
);
|
|
725
776
|
} else {
|
|
726
777
|
return true;
|
|
727
778
|
}
|
|
@@ -754,7 +805,7 @@ function is_outer_global(relative_selector) {
|
|
|
754
805
|
/**
|
|
755
806
|
* @param {AST.CSS.RelativeSelector} relative_selector
|
|
756
807
|
* @param {AST.CSS.Rule} rule
|
|
757
|
-
* @param {
|
|
808
|
+
* @param {any} element
|
|
758
809
|
* @param {Direction} direction
|
|
759
810
|
* @return {boolean}
|
|
760
811
|
*/
|
|
@@ -879,7 +930,7 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
|
|
|
879
930
|
selector.metadata.scoped = true;
|
|
880
931
|
}
|
|
881
932
|
|
|
882
|
-
/** @type {
|
|
933
|
+
/** @type {any | null} */
|
|
883
934
|
let el = element;
|
|
884
935
|
while (el) {
|
|
885
936
|
el.metadata.scoped = true;
|
|
@@ -929,9 +980,11 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
|
|
|
929
980
|
}
|
|
930
981
|
|
|
931
982
|
case 'AttributeSelector': {
|
|
932
|
-
const
|
|
933
|
-
|
|
934
|
-
|
|
983
|
+
const element_name = get_element_name(element);
|
|
984
|
+
const whitelisted =
|
|
985
|
+
element_name?.type === 'Identifier' || element_name?.type === 'JSXIdentifier'
|
|
986
|
+
? whitelist_attribute_selector.get(element_name.name.toLowerCase())
|
|
987
|
+
: undefined;
|
|
935
988
|
if (
|
|
936
989
|
!whitelisted?.includes(selector.name.toLowerCase()) &&
|
|
937
990
|
!attribute_matches(
|
|
@@ -968,13 +1021,14 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
|
|
|
968
1021
|
}
|
|
969
1022
|
|
|
970
1023
|
case 'TypeSelector': {
|
|
971
|
-
if (is_element_dynamic(
|
|
1024
|
+
if (is_element_dynamic(element)) {
|
|
972
1025
|
break;
|
|
973
1026
|
}
|
|
974
1027
|
|
|
1028
|
+
const element_name = get_element_name(element);
|
|
975
1029
|
if (
|
|
976
|
-
|
|
977
|
-
|
|
1030
|
+
(element_name?.type === 'Identifier' || element_name?.type === 'JSXIdentifier') &&
|
|
1031
|
+
element_name.name.toLowerCase() !== name.toLowerCase() &&
|
|
978
1032
|
name !== '*'
|
|
979
1033
|
) {
|
|
980
1034
|
return false;
|
|
@@ -1063,7 +1117,7 @@ function rule_has_animation(rule) {
|
|
|
1063
1117
|
|
|
1064
1118
|
/**
|
|
1065
1119
|
* @param {AST.CSS.StyleSheet} css
|
|
1066
|
-
* @param {
|
|
1120
|
+
* @param {any} element
|
|
1067
1121
|
* @param {StyleClasses} styleClasses
|
|
1068
1122
|
* @param {TopScopedClasses} topScopedClasses
|
|
1069
1123
|
* @return {void}
|
|
@@ -1073,6 +1127,10 @@ export function prune_css(css, element, styleClasses, topScopedClasses) {
|
|
|
1073
1127
|
style_identifier_classes = styleClasses;
|
|
1074
1128
|
top_scoped_classes = topScopedClasses;
|
|
1075
1129
|
|
|
1130
|
+
if (is_element_dynamic(element)) {
|
|
1131
|
+
element.metadata.scoped = true;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1076
1134
|
/** @type {Visitors<AST.CSS.Node, null>} */
|
|
1077
1135
|
const visitors = {
|
|
1078
1136
|
Rule(node, context) {
|
|
@@ -9,9 +9,17 @@ import { DIAGNOSTIC_CODES } from '../diagnostics.js';
|
|
|
9
9
|
export const TSRX_RETURN_STATEMENT_ERROR =
|
|
10
10
|
'Return statements are not allowed inside TSRX templates. Move the return before the TSRX return value, or use conditional rendering instead.';
|
|
11
11
|
export const TSRX_LOOP_RETURN_ERROR =
|
|
12
|
-
'Return statements are not allowed inside TSRX template for...of loops.
|
|
12
|
+
'Return statements are not allowed inside TSRX template for...of loops. Filter the iterable before rendering or use an @for empty fallback for empty lists.';
|
|
13
13
|
export const TSRX_LOOP_BREAK_ERROR =
|
|
14
14
|
'Break statements are not allowed inside TSRX template for...of loops.';
|
|
15
|
+
export const TSRX_LOOP_CONTINUE_ERROR =
|
|
16
|
+
'Continue statements are not allowed inside TSRX template for...of loops. Filter the iterable before rendering.';
|
|
17
|
+
export const TSRX_IF_RETURN_ERROR =
|
|
18
|
+
'Return statements are not allowed inside TSRX template @if blocks. Move the return before the template output or render conditionally instead.';
|
|
19
|
+
export const TSRX_IF_BREAK_ERROR =
|
|
20
|
+
'Break statements are not allowed inside TSRX template @if blocks.';
|
|
21
|
+
export const TSRX_IF_CONTINUE_ERROR =
|
|
22
|
+
'Continue statements are not allowed inside TSRX template @if blocks. Filter before rendering or use conditional output instead.';
|
|
15
23
|
export const TSRX_FOR_STATEMENT_ERROR =
|
|
16
24
|
'For loops are not supported in TSRX templates. Use for...of instead.';
|
|
17
25
|
export const TSRX_FOR_IN_STATEMENT_ERROR =
|
|
@@ -134,11 +142,12 @@ const invalid_nestings = {
|
|
|
134
142
|
};
|
|
135
143
|
|
|
136
144
|
/**
|
|
137
|
-
* @param {
|
|
145
|
+
* @param {any} element
|
|
138
146
|
* @returns {string | null}
|
|
139
147
|
*/
|
|
140
148
|
function get_element_tag(element) {
|
|
141
|
-
|
|
149
|
+
const name = element.openingElement?.name ?? element.id;
|
|
150
|
+
return name?.type === 'JSXIdentifier' || name?.type === 'Identifier' ? name.name : null;
|
|
142
151
|
}
|
|
143
152
|
|
|
144
153
|
/**
|
|
@@ -218,6 +227,64 @@ export function validate_tsrx_loop_break_statement(node, filename, errors, comme
|
|
|
218
227
|
);
|
|
219
228
|
}
|
|
220
229
|
|
|
230
|
+
/**
|
|
231
|
+
* @param {AST.ContinueStatement} node
|
|
232
|
+
* @param {string | null | undefined} filename
|
|
233
|
+
* @param {CompileError[]} [errors]
|
|
234
|
+
* @param {AST.CommentWithLocation[]} [comments]
|
|
235
|
+
*/
|
|
236
|
+
export function validate_tsrx_loop_continue_statement(node, filename, errors, comments) {
|
|
237
|
+
error(
|
|
238
|
+
TSRX_LOOP_CONTINUE_ERROR,
|
|
239
|
+
filename ?? null,
|
|
240
|
+
get_statement_keyword_node(node, 'continue'),
|
|
241
|
+
errors,
|
|
242
|
+
comments,
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* @param {AST.ReturnStatement} node
|
|
248
|
+
* @param {string | null | undefined} filename
|
|
249
|
+
* @param {CompileError[]} [errors]
|
|
250
|
+
* @param {AST.CommentWithLocation[]} [comments]
|
|
251
|
+
*/
|
|
252
|
+
export function validate_tsrx_if_return_statement(node, filename, errors, comments) {
|
|
253
|
+
error(TSRX_IF_RETURN_ERROR, filename ?? null, get_return_keyword_node(node), errors, comments);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* @param {AST.BreakStatement} node
|
|
258
|
+
* @param {string | null | undefined} filename
|
|
259
|
+
* @param {CompileError[]} [errors]
|
|
260
|
+
* @param {AST.CommentWithLocation[]} [comments]
|
|
261
|
+
*/
|
|
262
|
+
export function validate_tsrx_if_break_statement(node, filename, errors, comments) {
|
|
263
|
+
error(
|
|
264
|
+
TSRX_IF_BREAK_ERROR,
|
|
265
|
+
filename ?? null,
|
|
266
|
+
get_statement_keyword_node(node, 'break'),
|
|
267
|
+
errors,
|
|
268
|
+
comments,
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* @param {AST.ContinueStatement} node
|
|
274
|
+
* @param {string | null | undefined} filename
|
|
275
|
+
* @param {CompileError[]} [errors]
|
|
276
|
+
* @param {AST.CommentWithLocation[]} [comments]
|
|
277
|
+
*/
|
|
278
|
+
export function validate_tsrx_if_continue_statement(node, filename, errors, comments) {
|
|
279
|
+
error(
|
|
280
|
+
TSRX_IF_CONTINUE_ERROR,
|
|
281
|
+
filename ?? null,
|
|
282
|
+
get_statement_keyword_node(node, 'continue'),
|
|
283
|
+
errors,
|
|
284
|
+
comments,
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
221
288
|
/**
|
|
222
289
|
* @param {AST.ForStatement | AST.ForInStatement | AST.WhileStatement | AST.DoWhileStatement} node
|
|
223
290
|
* @param {string | null | undefined} filename
|
|
@@ -240,7 +307,57 @@ export function validate_tsrx_unsupported_loop_statement(node, filename, errors,
|
|
|
240
307
|
}
|
|
241
308
|
|
|
242
309
|
/**
|
|
243
|
-
*
|
|
310
|
+
* Returns `true` when `child` occupies a value slot of `parent` — i.e. it is
|
|
311
|
+
* being captured as a value (assigned to a binding, pushed into an array,
|
|
312
|
+
* passed as an argument, used as an operand, …) rather than rendered as a
|
|
313
|
+
* statement-position template child.
|
|
314
|
+
*
|
|
315
|
+
* Target analyzers use this to tell apart direct template output from a TSRX
|
|
316
|
+
* element that merely happens to be a value, so that a value-position element
|
|
317
|
+
* nested inside plain JavaScript control flow does not get mistaken for direct
|
|
318
|
+
* output that would require a `@for`/`@if`/`@switch`/`@try` directive.
|
|
319
|
+
* @param {AST.Node} parent
|
|
320
|
+
* @param {AST.Node} child
|
|
321
|
+
* @returns {boolean}
|
|
322
|
+
*/
|
|
323
|
+
export function is_template_value_position(parent, child) {
|
|
324
|
+
switch (parent.type) {
|
|
325
|
+
case 'VariableDeclarator':
|
|
326
|
+
return parent.init === child;
|
|
327
|
+
case 'AssignmentExpression':
|
|
328
|
+
return parent.right === child;
|
|
329
|
+
case 'Property':
|
|
330
|
+
case 'PropertyDefinition':
|
|
331
|
+
return parent.value === child;
|
|
332
|
+
case 'ArrayExpression':
|
|
333
|
+
return /** @type {any[]} */ (parent.elements).includes(child);
|
|
334
|
+
case 'CallExpression':
|
|
335
|
+
case 'NewExpression':
|
|
336
|
+
return parent.callee === child || /** @type {any[]} */ (parent.arguments).includes(child);
|
|
337
|
+
case 'ConditionalExpression':
|
|
338
|
+
return parent.test === child || parent.consequent === child || parent.alternate === child;
|
|
339
|
+
case 'LogicalExpression':
|
|
340
|
+
case 'BinaryExpression':
|
|
341
|
+
return parent.left === child || parent.right === child;
|
|
342
|
+
case 'UnaryExpression':
|
|
343
|
+
case 'AwaitExpression':
|
|
344
|
+
case 'SpreadElement':
|
|
345
|
+
case 'YieldExpression':
|
|
346
|
+
return parent.argument === child;
|
|
347
|
+
case 'TemplateLiteral':
|
|
348
|
+
case 'SequenceExpression':
|
|
349
|
+
return /** @type {any[]} */ (parent.expressions).includes(child);
|
|
350
|
+
case 'TSAsExpression':
|
|
351
|
+
case 'TSNonNullExpression':
|
|
352
|
+
case 'TSSatisfiesExpression':
|
|
353
|
+
return parent.expression === child;
|
|
354
|
+
default:
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* @param {any} element
|
|
244
361
|
* @param {AnalysisContext} context
|
|
245
362
|
* @param {CompileError[]} [errors]
|
|
246
363
|
*/
|
|
@@ -253,7 +370,7 @@ export function validate_nesting(element, context, errors) {
|
|
|
253
370
|
|
|
254
371
|
for (let i = context.path.length - 1; i >= 0; i--) {
|
|
255
372
|
const parent = context.path[i];
|
|
256
|
-
if (parent.type === '
|
|
373
|
+
if (parent.type === 'JSXElement' || parent.type === 'JSXStyleElement') {
|
|
257
374
|
const parent_tag = get_element_tag(parent);
|
|
258
375
|
if (parent_tag === null) {
|
|
259
376
|
continue;
|
package/src/index.js
CHANGED
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
export { parse_module as parseModule } from './parse/parse-module.js';
|
|
10
10
|
export {
|
|
11
11
|
get_comment_handlers as getCommentHandlers,
|
|
12
|
-
convert_from_jsx as convertFromJsx,
|
|
13
12
|
skipWhitespace,
|
|
14
13
|
isWhitespaceTextNode,
|
|
15
14
|
BINDING_TYPES,
|
|
@@ -181,7 +180,6 @@ export {
|
|
|
181
180
|
create_compile_error,
|
|
182
181
|
create_generated_identifier,
|
|
183
182
|
create_null_literal,
|
|
184
|
-
expand_switch_cases_for_fallthrough,
|
|
185
183
|
flatten_switch_consequent,
|
|
186
184
|
get_for_of_iteration_params,
|
|
187
185
|
identifier_to_jsx_name,
|
|
@@ -189,6 +187,7 @@ export {
|
|
|
189
187
|
is_component_jsx_name,
|
|
190
188
|
is_dynamic_element_id,
|
|
191
189
|
is_jsx_child,
|
|
190
|
+
jsx_name_to_expression,
|
|
192
191
|
set_loc,
|
|
193
192
|
to_text_expression,
|
|
194
193
|
} from './transform/jsx/ast-builders.js';
|
|
@@ -199,7 +198,7 @@ export {
|
|
|
199
198
|
export {
|
|
200
199
|
prepare_stylesheet_for_render as prepareStylesheetForRender,
|
|
201
200
|
is_style_element as isStyleElement,
|
|
202
|
-
|
|
201
|
+
is_composite_jsx_element as isCompositeElement,
|
|
203
202
|
annotate_with_hash as annotateWithHash,
|
|
204
203
|
annotate_component_with_hash as annotateComponentWithHash,
|
|
205
204
|
add_hash_class as addHashClass,
|
|
@@ -241,15 +240,24 @@ export {
|
|
|
241
240
|
TSRX_DO_WHILE_STATEMENT_ERROR,
|
|
242
241
|
TSRX_FOR_IN_STATEMENT_ERROR,
|
|
243
242
|
TSRX_FOR_STATEMENT_ERROR,
|
|
243
|
+
TSRX_IF_BREAK_ERROR,
|
|
244
|
+
TSRX_IF_CONTINUE_ERROR,
|
|
245
|
+
TSRX_IF_RETURN_ERROR,
|
|
244
246
|
TSRX_LOOP_BREAK_ERROR,
|
|
247
|
+
TSRX_LOOP_CONTINUE_ERROR,
|
|
245
248
|
TSRX_LOOP_RETURN_ERROR,
|
|
246
249
|
TSRX_RETURN_STATEMENT_ERROR,
|
|
247
250
|
TSRX_WHILE_STATEMENT_ERROR,
|
|
248
251
|
get_return_keyword_node as getReturnKeywordNode,
|
|
249
252
|
get_statement_keyword_node as getStatementKeywordNode,
|
|
253
|
+
validate_tsrx_if_break_statement as validateTsrxIfBreakStatement,
|
|
254
|
+
validate_tsrx_if_continue_statement as validateTsrxIfContinueStatement,
|
|
255
|
+
validate_tsrx_if_return_statement as validateTsrxIfReturnStatement,
|
|
250
256
|
validate_tsrx_loop_break_statement as validateTsrxLoopBreakStatement,
|
|
257
|
+
validate_tsrx_loop_continue_statement as validateTsrxLoopContinueStatement,
|
|
251
258
|
validate_tsrx_loop_return_statement as validateTsrxLoopReturnStatement,
|
|
252
259
|
validate_tsrx_return_statement as validateTsrxReturnStatement,
|
|
253
260
|
validate_tsrx_unsupported_loop_statement as validateTsrxUnsupportedLoopStatement,
|
|
254
261
|
validate_nesting as validateNesting,
|
|
262
|
+
is_template_value_position as isTemplateValuePosition,
|
|
255
263
|
} from './analyze/validation.js';
|