@tsrx/core 0.1.20 → 0.1.24
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 +117 -70
- package/src/analyze/validation.js +122 -5
- package/src/diagnostics.js +1 -0
- package/src/index.js +10 -4
- package/src/parse/index.js +157 -99
- package/src/plugin.js +2540 -867
- package/src/runtime/html.js +1 -1
- package/src/runtime/iterable.js +15 -13
- package/src/runtime/language-helpers.js +39 -0
- 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 +6 -81
- package/src/transform/jsx/helpers.js +8 -5
- package/src/transform/jsx/index.js +1440 -451
- package/src/transform/jsx-interleave.js +1 -2
- package/src/transform/scoping.js +26 -63
- package/src/transform/segments.js +66 -48
- package/src/transform/style-ref.js +3 -11
- package/src/utils/builders.js +2 -3
- package/types/index.d.ts +181 -115
- package/types/jsx-platform.d.ts +14 -11
- package/types/parse.d.ts +36 -10
- package/types/runtime/html.d.ts +1 -0
- package/types/runtime/language-helpers.d.ts +4 -0
- 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.24",
|
|
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,30 +19,72 @@ 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
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @param {AST.Node} node
|
|
67
|
+
* @returns {boolean}
|
|
68
|
+
*/
|
|
69
|
+
function is_runtime_dynamic_element(node) {
|
|
70
|
+
return node?.metadata?.runtime_dynamic_element === true;
|
|
71
|
+
}
|
|
72
|
+
|
|
22
73
|
/**
|
|
23
74
|
* Returns true if node is a DOM element (not a component).
|
|
24
75
|
* @param {AST.Node} node
|
|
25
76
|
* @returns {boolean}
|
|
26
77
|
*/
|
|
27
78
|
function is_element_dom_element(node) {
|
|
28
|
-
const id =
|
|
79
|
+
const id = get_element_name(node);
|
|
29
80
|
return (
|
|
30
|
-
id.type === 'Identifier' &&
|
|
81
|
+
(id.type === 'Identifier' || id.type === 'JSXIdentifier') &&
|
|
31
82
|
id.name[0].toLowerCase() === id.name[0] &&
|
|
32
83
|
id.name !== 'children' &&
|
|
33
84
|
!id.tracked
|
|
34
85
|
);
|
|
35
86
|
}
|
|
36
87
|
|
|
37
|
-
/**
|
|
38
|
-
* Returns true if element is dynamic.
|
|
39
|
-
* @param {AST.Element} node
|
|
40
|
-
* @returns {boolean}
|
|
41
|
-
*/
|
|
42
|
-
function is_element_dynamic(node) {
|
|
43
|
-
return node.id.type === 'Identifier' ? !!node.id.tracked : false;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
88
|
// CSS selector constants
|
|
47
89
|
/**
|
|
48
90
|
* @param {number} start
|
|
@@ -220,7 +262,7 @@ function truncate(node) {
|
|
|
220
262
|
/**
|
|
221
263
|
* @param {AST.CSS.RelativeSelector[]} relative_selectors
|
|
222
264
|
* @param {AST.CSS.Rule} rule
|
|
223
|
-
* @param {
|
|
265
|
+
* @param {any} element
|
|
224
266
|
* @param {Direction} direction
|
|
225
267
|
* @returns {boolean}
|
|
226
268
|
*/
|
|
@@ -271,12 +313,12 @@ function apply_selector(relative_selectors, rule, element, direction) {
|
|
|
271
313
|
}
|
|
272
314
|
|
|
273
315
|
/**
|
|
274
|
-
* @param {
|
|
316
|
+
* @param {any} node
|
|
275
317
|
* @param {boolean} adjacent_only
|
|
276
|
-
* @returns {
|
|
318
|
+
* @returns {any[]}
|
|
277
319
|
*/
|
|
278
320
|
function get_ancestor_elements(node, adjacent_only) {
|
|
279
|
-
/** @type {
|
|
321
|
+
/** @type {any[]} */
|
|
280
322
|
const ancestors = [];
|
|
281
323
|
|
|
282
324
|
const path = node.metadata.path;
|
|
@@ -285,7 +327,7 @@ function get_ancestor_elements(node, adjacent_only) {
|
|
|
285
327
|
while (i--) {
|
|
286
328
|
const parent = path[i];
|
|
287
329
|
|
|
288
|
-
if (parent
|
|
330
|
+
if (is_native_jsx_element(parent)) {
|
|
289
331
|
ancestors.push(parent);
|
|
290
332
|
if (adjacent_only) {
|
|
291
333
|
break;
|
|
@@ -297,12 +339,12 @@ function get_ancestor_elements(node, adjacent_only) {
|
|
|
297
339
|
}
|
|
298
340
|
|
|
299
341
|
/**
|
|
300
|
-
* @param {
|
|
342
|
+
* @param {any} node
|
|
301
343
|
* @param {boolean} adjacent_only
|
|
302
|
-
* @returns {
|
|
344
|
+
* @returns {any[]}
|
|
303
345
|
*/
|
|
304
346
|
function get_descendant_elements(node, adjacent_only) {
|
|
305
|
-
/** @type {
|
|
347
|
+
/** @type {any[]} */
|
|
306
348
|
const descendants = [];
|
|
307
349
|
|
|
308
350
|
/**
|
|
@@ -311,26 +353,23 @@ function get_descendant_elements(node, adjacent_only) {
|
|
|
311
353
|
* @returns {void}
|
|
312
354
|
*/
|
|
313
355
|
function visit(current_node, depth = 0) {
|
|
314
|
-
if (current_node
|
|
356
|
+
if (is_native_jsx_element(current_node) && current_node !== node) {
|
|
315
357
|
descendants.push(current_node);
|
|
316
358
|
if (adjacent_only) return; // Only direct children for '>' combinator
|
|
317
359
|
}
|
|
318
360
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
for (const child of /** @type {AST.Element} */ (current_node).children) {
|
|
361
|
+
if (Array.isArray(/** @type {any} */ (current_node).children)) {
|
|
362
|
+
for (const child of /** @type {any} */ (current_node).children) {
|
|
322
363
|
visit(child, depth + 1);
|
|
323
364
|
}
|
|
324
365
|
}
|
|
325
366
|
|
|
326
|
-
// For template nodes and interpolation expressions
|
|
327
367
|
if (
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
typeof
|
|
331
|
-
'object'
|
|
368
|
+
current_node.type === 'JSXExpressionContainer' &&
|
|
369
|
+
current_node.expression &&
|
|
370
|
+
typeof current_node.expression === 'object'
|
|
332
371
|
) {
|
|
333
|
-
visit(
|
|
372
|
+
visit(current_node.expression, depth + 1);
|
|
334
373
|
}
|
|
335
374
|
}
|
|
336
375
|
|
|
@@ -351,24 +390,23 @@ function get_descendant_elements(node, adjacent_only) {
|
|
|
351
390
|
* @returns {boolean}
|
|
352
391
|
*/
|
|
353
392
|
function can_render_dynamic_content(element, check_classes = false) {
|
|
354
|
-
if (
|
|
393
|
+
if (is_runtime_dynamic_element(element)) {
|
|
355
394
|
return true;
|
|
356
395
|
}
|
|
357
396
|
|
|
358
|
-
|
|
359
|
-
// But dynamic elements should return false ideally
|
|
360
|
-
if (is_element_dynamic(/** @type {AST.Element} */ (element))) {
|
|
397
|
+
if (!is_element_dom_element(element)) {
|
|
361
398
|
return true;
|
|
362
399
|
}
|
|
363
400
|
|
|
364
401
|
// Check for dynamic class attributes if requested (for class-based selectors)
|
|
365
|
-
if (check_classes
|
|
366
|
-
for (const attr of
|
|
367
|
-
if (attr.type === '
|
|
402
|
+
if (check_classes) {
|
|
403
|
+
for (const attr of get_element_attributes(element)) {
|
|
404
|
+
if (attr.type === 'JSXAttribute' && get_attribute_name(attr) === 'class') {
|
|
405
|
+
const value = get_attribute_value(attr);
|
|
368
406
|
// Check if class value is an expression (not a static string)
|
|
369
|
-
if (
|
|
407
|
+
if (value && typeof value === 'object') {
|
|
370
408
|
// If it's a CallExpression or other dynamic value, it's dynamic
|
|
371
|
-
if (
|
|
409
|
+
if (value.type !== 'Literal') {
|
|
372
410
|
return true;
|
|
373
411
|
}
|
|
374
412
|
}
|
|
@@ -383,7 +421,7 @@ function can_render_dynamic_content(element, check_classes = false) {
|
|
|
383
421
|
* @param {AST.Node} node
|
|
384
422
|
* @param {Direction} direction
|
|
385
423
|
* @param {boolean} adjacent_only
|
|
386
|
-
* @returns {Map<
|
|
424
|
+
* @returns {Map<any, boolean>}
|
|
387
425
|
*/
|
|
388
426
|
function get_possible_element_siblings(node, direction, adjacent_only) {
|
|
389
427
|
const siblings = new Map();
|
|
@@ -415,7 +453,7 @@ function get_possible_element_siblings(node, direction, adjacent_only) {
|
|
|
415
453
|
for (let i = start; i !== end; i += step) {
|
|
416
454
|
const sibling = container[i];
|
|
417
455
|
|
|
418
|
-
if (sibling
|
|
456
|
+
if (is_native_jsx_element(sibling)) {
|
|
419
457
|
siblings.set(sibling, true);
|
|
420
458
|
// Don't break for dynamic elements (children and dynamic components)
|
|
421
459
|
// as they can render dynamic content or might render nothing
|
|
@@ -425,13 +463,7 @@ function get_possible_element_siblings(node, direction, adjacent_only) {
|
|
|
425
463
|
}
|
|
426
464
|
}
|
|
427
465
|
// 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
|
-
) {
|
|
466
|
+
else if (adjacent_only && sibling.type === 'JSXText' && sibling.value.trim()) {
|
|
435
467
|
break;
|
|
436
468
|
}
|
|
437
469
|
}
|
|
@@ -443,7 +475,7 @@ function get_possible_element_siblings(node, direction, adjacent_only) {
|
|
|
443
475
|
* @param {AST.CSS.RelativeSelector} relative_selector
|
|
444
476
|
* @param {AST.CSS.RelativeSelector[]} rest_selectors
|
|
445
477
|
* @param {AST.CSS.Rule} rule
|
|
446
|
-
* @param {
|
|
478
|
+
* @param {any} node
|
|
447
479
|
* @param {Direction} direction
|
|
448
480
|
* @returns {boolean}
|
|
449
481
|
*/
|
|
@@ -515,7 +547,7 @@ function apply_combinator(relative_selector, rest_selectors, rule, node, directi
|
|
|
515
547
|
|
|
516
548
|
for (let i = search_start; i < search_end; i++) {
|
|
517
549
|
const subsequent = container[i];
|
|
518
|
-
if (subsequent
|
|
550
|
+
if (is_native_jsx_element(subsequent)) {
|
|
519
551
|
if (apply_selector(remaining, rule, subsequent, direction)) {
|
|
520
552
|
sibling_matched = true;
|
|
521
553
|
break;
|
|
@@ -529,7 +561,7 @@ function apply_combinator(relative_selector, rest_selectors, rule, node, directi
|
|
|
529
561
|
}
|
|
530
562
|
// Don't apply_selector for dynamic elements - they won't match regular element selectors
|
|
531
563
|
} else if (
|
|
532
|
-
possible_sibling
|
|
564
|
+
is_native_jsx_element(possible_sibling) &&
|
|
533
565
|
apply_selector(rest_selectors, rule, possible_sibling, direction)
|
|
534
566
|
) {
|
|
535
567
|
sibling_matched = true;
|
|
@@ -551,7 +583,7 @@ function apply_combinator(relative_selector, rest_selectors, rule, node, directi
|
|
|
551
583
|
}
|
|
552
584
|
/**
|
|
553
585
|
* @param {AST.Node} node
|
|
554
|
-
* @returns {
|
|
586
|
+
* @returns {any | null}
|
|
555
587
|
*/
|
|
556
588
|
function get_element_parent(node) {
|
|
557
589
|
// Check if metadata and path exist
|
|
@@ -565,7 +597,7 @@ function get_element_parent(node) {
|
|
|
565
597
|
while (i--) {
|
|
566
598
|
const parent = path[i];
|
|
567
599
|
|
|
568
|
-
if (parent
|
|
600
|
+
if (is_native_jsx_element(parent)) {
|
|
569
601
|
return parent;
|
|
570
602
|
}
|
|
571
603
|
}
|
|
@@ -664,11 +696,12 @@ function is_global(selector, rule) {
|
|
|
664
696
|
}
|
|
665
697
|
|
|
666
698
|
/**
|
|
667
|
-
* @param {
|
|
668
|
-
* @returns {
|
|
699
|
+
* @param {any} attribute
|
|
700
|
+
* @returns {boolean}
|
|
669
701
|
*/
|
|
670
702
|
function is_text_attribute(attribute) {
|
|
671
|
-
|
|
703
|
+
const value = get_attribute_value(attribute);
|
|
704
|
+
return value?.type === 'Literal' && typeof value.value === 'string';
|
|
672
705
|
}
|
|
673
706
|
|
|
674
707
|
/**
|
|
@@ -702,7 +735,7 @@ function test_attribute(operator, expected_value, case_insensitive, value) {
|
|
|
702
735
|
}
|
|
703
736
|
|
|
704
737
|
/**
|
|
705
|
-
* @param {
|
|
738
|
+
* @param {any} node
|
|
706
739
|
* @param {string} name
|
|
707
740
|
* @param {string | null} expected_value
|
|
708
741
|
* @param {string | null} operator
|
|
@@ -710,18 +743,29 @@ function test_attribute(operator, expected_value, case_insensitive, value) {
|
|
|
710
743
|
* @returns {boolean}
|
|
711
744
|
*/
|
|
712
745
|
function attribute_matches(node, name, expected_value, operator, case_insensitive) {
|
|
713
|
-
for (const attribute of node
|
|
714
|
-
if (attribute.type === '
|
|
746
|
+
for (const attribute of get_element_attributes(node)) {
|
|
747
|
+
if (attribute.type === 'JSXSpreadAttribute') return true;
|
|
715
748
|
|
|
716
|
-
if (attribute.type !== '
|
|
749
|
+
if (attribute.type !== 'JSXAttribute') continue;
|
|
717
750
|
|
|
718
751
|
const lowerCaseName = name.toLowerCase();
|
|
719
|
-
|
|
752
|
+
const attributeName = get_attribute_name(attribute);
|
|
753
|
+
if (
|
|
754
|
+
!attributeName ||
|
|
755
|
+
![lowerCaseName, `$${lowerCaseName}`].includes(attributeName.toLowerCase())
|
|
756
|
+
) {
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
720
759
|
|
|
721
760
|
if (expected_value === null) return true;
|
|
722
761
|
|
|
723
762
|
if (is_text_attribute(attribute)) {
|
|
724
|
-
return test_attribute(
|
|
763
|
+
return test_attribute(
|
|
764
|
+
operator,
|
|
765
|
+
expected_value,
|
|
766
|
+
case_insensitive,
|
|
767
|
+
get_attribute_value(attribute).value,
|
|
768
|
+
);
|
|
725
769
|
} else {
|
|
726
770
|
return true;
|
|
727
771
|
}
|
|
@@ -754,7 +798,7 @@ function is_outer_global(relative_selector) {
|
|
|
754
798
|
/**
|
|
755
799
|
* @param {AST.CSS.RelativeSelector} relative_selector
|
|
756
800
|
* @param {AST.CSS.Rule} rule
|
|
757
|
-
* @param {
|
|
801
|
+
* @param {any} element
|
|
758
802
|
* @param {Direction} direction
|
|
759
803
|
* @return {boolean}
|
|
760
804
|
*/
|
|
@@ -879,7 +923,7 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
|
|
|
879
923
|
selector.metadata.scoped = true;
|
|
880
924
|
}
|
|
881
925
|
|
|
882
|
-
/** @type {
|
|
926
|
+
/** @type {any | null} */
|
|
883
927
|
let el = element;
|
|
884
928
|
while (el) {
|
|
885
929
|
el.metadata.scoped = true;
|
|
@@ -929,9 +973,11 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
|
|
|
929
973
|
}
|
|
930
974
|
|
|
931
975
|
case 'AttributeSelector': {
|
|
932
|
-
const
|
|
933
|
-
|
|
934
|
-
|
|
976
|
+
const element_name = get_element_name(element);
|
|
977
|
+
const whitelisted =
|
|
978
|
+
element_name?.type === 'Identifier' || element_name?.type === 'JSXIdentifier'
|
|
979
|
+
? whitelist_attribute_selector.get(element_name.name.toLowerCase())
|
|
980
|
+
: undefined;
|
|
935
981
|
if (
|
|
936
982
|
!whitelisted?.includes(selector.name.toLowerCase()) &&
|
|
937
983
|
!attribute_matches(
|
|
@@ -968,13 +1014,14 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
|
|
|
968
1014
|
}
|
|
969
1015
|
|
|
970
1016
|
case 'TypeSelector': {
|
|
971
|
-
if (
|
|
1017
|
+
if (is_runtime_dynamic_element(element)) {
|
|
972
1018
|
break;
|
|
973
1019
|
}
|
|
974
1020
|
|
|
1021
|
+
const element_name = get_element_name(element);
|
|
975
1022
|
if (
|
|
976
|
-
|
|
977
|
-
|
|
1023
|
+
(element_name?.type === 'Identifier' || element_name?.type === 'JSXIdentifier') &&
|
|
1024
|
+
element_name.name.toLowerCase() !== name.toLowerCase() &&
|
|
978
1025
|
name !== '*'
|
|
979
1026
|
) {
|
|
980
1027
|
return false;
|
|
@@ -1063,7 +1110,7 @@ function rule_has_animation(rule) {
|
|
|
1063
1110
|
|
|
1064
1111
|
/**
|
|
1065
1112
|
* @param {AST.CSS.StyleSheet} css
|
|
1066
|
-
* @param {
|
|
1113
|
+
* @param {any} element
|
|
1067
1114
|
* @param {StyleClasses} styleClasses
|
|
1068
1115
|
* @param {TopScopedClasses} topScopedClasses
|
|
1069
1116
|
* @return {void}
|
|
@@ -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 @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/diagnostics.js
CHANGED
|
@@ -4,4 +4,5 @@ export const DIAGNOSTIC_CODES = {
|
|
|
4
4
|
MISMATCHED_CLOSING_TAG: 'tsrx-mismatched-closing-tag',
|
|
5
5
|
TEMPLATE_EXPRESSION_TRAILING_SEMICOLON: 'tsrx-template-expression-trailing-semicolon',
|
|
6
6
|
TEMPLATE_RETURN_STATEMENT: 'tsrx-template-return-statement',
|
|
7
|
+
FORGOTTEN_STATEMENT_CONTAINER: 'tsrx-forgotten-statement-container',
|
|
7
8
|
};
|
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,13 +180,11 @@ 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,
|
|
188
186
|
is_bare_render_expression,
|
|
189
187
|
is_component_jsx_name,
|
|
190
|
-
is_dynamic_element_id,
|
|
191
188
|
is_jsx_child,
|
|
192
189
|
set_loc,
|
|
193
190
|
to_text_expression,
|
|
@@ -199,7 +196,7 @@ export {
|
|
|
199
196
|
export {
|
|
200
197
|
prepare_stylesheet_for_render as prepareStylesheetForRender,
|
|
201
198
|
is_style_element as isStyleElement,
|
|
202
|
-
|
|
199
|
+
is_composite_jsx_element as isCompositeElement,
|
|
203
200
|
annotate_with_hash as annotateWithHash,
|
|
204
201
|
annotate_component_with_hash as annotateComponentWithHash,
|
|
205
202
|
add_hash_class as addHashClass,
|
|
@@ -241,15 +238,24 @@ export {
|
|
|
241
238
|
TSRX_DO_WHILE_STATEMENT_ERROR,
|
|
242
239
|
TSRX_FOR_IN_STATEMENT_ERROR,
|
|
243
240
|
TSRX_FOR_STATEMENT_ERROR,
|
|
241
|
+
TSRX_IF_BREAK_ERROR,
|
|
242
|
+
TSRX_IF_CONTINUE_ERROR,
|
|
243
|
+
TSRX_IF_RETURN_ERROR,
|
|
244
244
|
TSRX_LOOP_BREAK_ERROR,
|
|
245
|
+
TSRX_LOOP_CONTINUE_ERROR,
|
|
245
246
|
TSRX_LOOP_RETURN_ERROR,
|
|
246
247
|
TSRX_RETURN_STATEMENT_ERROR,
|
|
247
248
|
TSRX_WHILE_STATEMENT_ERROR,
|
|
248
249
|
get_return_keyword_node as getReturnKeywordNode,
|
|
249
250
|
get_statement_keyword_node as getStatementKeywordNode,
|
|
251
|
+
validate_tsrx_if_break_statement as validateTsrxIfBreakStatement,
|
|
252
|
+
validate_tsrx_if_continue_statement as validateTsrxIfContinueStatement,
|
|
253
|
+
validate_tsrx_if_return_statement as validateTsrxIfReturnStatement,
|
|
250
254
|
validate_tsrx_loop_break_statement as validateTsrxLoopBreakStatement,
|
|
255
|
+
validate_tsrx_loop_continue_statement as validateTsrxLoopContinueStatement,
|
|
251
256
|
validate_tsrx_loop_return_statement as validateTsrxLoopReturnStatement,
|
|
252
257
|
validate_tsrx_return_statement as validateTsrxReturnStatement,
|
|
253
258
|
validate_tsrx_unsupported_loop_statement as validateTsrxUnsupportedLoopStatement,
|
|
254
259
|
validate_nesting as validateNesting,
|
|
260
|
+
is_template_value_position as isTemplateValuePosition,
|
|
255
261
|
} from './analyze/validation.js';
|