@tsrx/core 0.1.8 → 0.1.10
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 +5 -5
- package/src/analyze/prune.js +1151 -0
- package/src/diagnostics.js +1 -0
- package/src/index.js +8 -0
- package/src/plugin.js +56 -12
- package/src/transform/jsx/index.js +255 -10
- package/src/transform/scoping.js +38 -12
- package/src/transform/segments.js +17 -8
- package/types/jsx-platform.d.ts +2 -0
package/src/diagnostics.js
CHANGED
|
@@ -5,4 +5,5 @@ export const DIAGNOSTIC_CODES = {
|
|
|
5
5
|
UNCLOSED_TAG: 'tsrx-unclosed-tag',
|
|
6
6
|
MISMATCHED_CLOSING_TAG: 'tsrx-mismatched-closing-tag',
|
|
7
7
|
TEMPLATE_EXPRESSION_TRAILING_SEMICOLON: 'tsrx-template-expression-trailing-semicolon',
|
|
8
|
+
HTML_DIRECTIVE_AS_ATTRIBUTE_VALUE: 'tsrx-html-directive-as-attribute-value',
|
|
8
9
|
};
|
package/src/index.js
CHANGED
|
@@ -146,14 +146,21 @@ export {
|
|
|
146
146
|
clone_switch_helper_invocation as cloneSwitchHelperInvocation,
|
|
147
147
|
collect_param_bindings as collectParamBindings,
|
|
148
148
|
collect_statement_bindings as collectStatementBindings,
|
|
149
|
+
create_host_html_attribute as createHostHtmlAttribute,
|
|
150
|
+
create_host_html_conflict_error as createHostHtmlConflictError,
|
|
149
151
|
createJsxTransform,
|
|
150
152
|
CREATE_REF_PROP_INTERNAL_NAME,
|
|
151
153
|
extract_jsx_setup_declarations as extractJsxSetupDeclarations,
|
|
154
|
+
get_host_html_conflicting_attribute as getHostHtmlConflictingAttribute,
|
|
155
|
+
get_invalid_html_child_error_message as getInvalidHtmlChildErrorMessage,
|
|
156
|
+
is_component_like_element,
|
|
152
157
|
is_ref_prop_expression as isRefPropExpression,
|
|
153
158
|
MERGE_REFS_INTERNAL_NAME,
|
|
154
159
|
merge_duplicate_refs as mergeDuplicateRefs,
|
|
155
160
|
NORMALIZE_SPREAD_PROPS_INTERNAL_NAME,
|
|
156
161
|
plan_switch_lift as planSwitchLift,
|
|
162
|
+
recover_invalid_html_child as recoverInvalidHtmlChild,
|
|
163
|
+
rewrite_host_html_children as rewriteHostHtmlChildren,
|
|
157
164
|
return_value_body_to_expression as returnValueBodyToExpression,
|
|
158
165
|
rewrite_loop_continues_to_bare_returns as rewriteLoopContinuesToBareReturns,
|
|
159
166
|
to_jsx_attribute as toJsxAttribute,
|
|
@@ -229,6 +236,7 @@ export {
|
|
|
229
236
|
|
|
230
237
|
// Analyze
|
|
231
238
|
export { analyze_css as analyzeCss } from './analyze/css-analyze.js';
|
|
239
|
+
export { prune_css as pruneCss } from './analyze/prune.js';
|
|
232
240
|
export {
|
|
233
241
|
CLASS_COMPONENT_AS_NON_ARROW_PROPERTY_ERROR,
|
|
234
242
|
COMPONENT_DO_WHILE_STATEMENT_ERROR,
|
package/src/plugin.js
CHANGED
|
@@ -19,6 +19,8 @@ import { DIAGNOSTIC_CODES } from './diagnostics.js';
|
|
|
19
19
|
|
|
20
20
|
const JSX_EXPRESSION_VALUE_ERROR =
|
|
21
21
|
'JSX elements cannot be used as expressions. Wrap JSX with `<>...</>` or `<tsx>...</tsx>`, wrap TSRX templates with `<tsrx>...</tsrx>`, or use elements as statements within a component.';
|
|
22
|
+
const HTML_ATTRIBUTE_VALUE_ERROR =
|
|
23
|
+
'`{html ...}` is not supported as an attribute value. Use a string literal or expression without `html`.';
|
|
22
24
|
|
|
23
25
|
const CharCode = Object.freeze({
|
|
24
26
|
tab: 9,
|
|
@@ -1969,6 +1971,27 @@ export function TSRXPlugin(config) {
|
|
|
1969
1971
|
/** @type {AST.RefAttribute} */ (node).argument = this.parseMaybeAssign();
|
|
1970
1972
|
this.expect(tt.braceR);
|
|
1971
1973
|
return /** @type {AST.RefAttribute} */ (this.finishNode(node, 'RefAttribute'));
|
|
1974
|
+
} else if (this.type === tt.name && this.value === 'html') {
|
|
1975
|
+
// {html ...}
|
|
1976
|
+
// The support is purely for better error messages to avoid
|
|
1977
|
+
// the parser throw an unexpected token error
|
|
1978
|
+
const id = /** @type {AST.Identifier} */ (this.parseIdentNode());
|
|
1979
|
+
id.tracked = false;
|
|
1980
|
+
this.finishNode(id, 'Identifier');
|
|
1981
|
+
this.next();
|
|
1982
|
+
const value = this.type === tt.braceR ? id : this.parseMaybeAssign();
|
|
1983
|
+
const report_end = this.type === tt.braceR ? this.end : (value.end ?? this.end);
|
|
1984
|
+
this.#report_recoverable_error_range(
|
|
1985
|
+
node.start ?? id.start ?? this.start,
|
|
1986
|
+
report_end,
|
|
1987
|
+
HTML_ATTRIBUTE_VALUE_ERROR,
|
|
1988
|
+
DIAGNOSTIC_CODES.HTML_DIRECTIVE_AS_ATTRIBUTE_VALUE,
|
|
1989
|
+
);
|
|
1990
|
+
/** @type {AST.Attribute} */ (node).name = id;
|
|
1991
|
+
/** @type {AST.Attribute} */ (node).value = value;
|
|
1992
|
+
/** @type {AST.Attribute} */ (node).shorthand = false;
|
|
1993
|
+
this.expect(tt.braceR);
|
|
1994
|
+
return this.finishNode(node, 'Attribute');
|
|
1972
1995
|
} else if (this.type === tt.ellipsis) {
|
|
1973
1996
|
this.expect(tt.ellipsis);
|
|
1974
1997
|
/** @type {AST.SpreadAttribute} */ (node).argument = this.parseMaybeAssign();
|
|
@@ -1992,10 +2015,18 @@ export function TSRXPlugin(config) {
|
|
|
1992
2015
|
}
|
|
1993
2016
|
}
|
|
1994
2017
|
/** @type {ESTreeJSX.JSXAttribute} */ (node).name = this.jsx_parseNamespacedName();
|
|
1995
|
-
/** @type {ESTreeJSX.JSXAttribute} */ (
|
|
1996
|
-
|
|
1997
|
-
|
|
2018
|
+
const value = /** @type {ESTreeJSX.JSXAttribute['value'] | null} */ (
|
|
2019
|
+
this.eat(tt.eq) ? this.jsx_parseAttributeValue() : null
|
|
2020
|
+
);
|
|
2021
|
+
if (value?.type === 'JSXExpressionContainer' && value.html) {
|
|
2022
|
+
this.#report_recoverable_error_range(
|
|
2023
|
+
value.start ?? node.start ?? this.start,
|
|
2024
|
+
value.end ?? node.end ?? this.end,
|
|
2025
|
+
HTML_ATTRIBUTE_VALUE_ERROR,
|
|
2026
|
+
DIAGNOSTIC_CODES.HTML_DIRECTIVE_AS_ATTRIBUTE_VALUE,
|
|
1998
2027
|
);
|
|
2028
|
+
}
|
|
2029
|
+
/** @type {ESTreeJSX.JSXAttribute} */ (node).value = value;
|
|
1999
2030
|
return this.finishNode(node, 'JSXAttribute');
|
|
2000
2031
|
}
|
|
2001
2032
|
|
|
@@ -2205,6 +2236,8 @@ export function TSRXPlugin(config) {
|
|
|
2205
2236
|
// In JSX text mode, '<' and '{' always start a tag/expression container.
|
|
2206
2237
|
// `exprAllowed` can be false here due to surrounding parser state, but
|
|
2207
2238
|
// throwing breaks valid templates (e.g. sibling tags after a close).
|
|
2239
|
+
this.start = this.pos;
|
|
2240
|
+
this.startLoc = this.curPosition();
|
|
2208
2241
|
if (ch === CharCode.lessThan) {
|
|
2209
2242
|
++this.pos;
|
|
2210
2243
|
return this.finishToken(tstt.jsxTagStart);
|
|
@@ -2434,6 +2467,8 @@ export function TSRXPlugin(config) {
|
|
|
2434
2467
|
/** @type {AST.NodeWithLocation} */ (element).loc.start = position;
|
|
2435
2468
|
element.metadata = { path: [] };
|
|
2436
2469
|
element.children = [];
|
|
2470
|
+
element.type = 'Element';
|
|
2471
|
+
this.#path.push(element);
|
|
2437
2472
|
|
|
2438
2473
|
const open = /** @type {ESTreeJSX.JSXOpeningElement & AST.NodeWithLocation} */ (
|
|
2439
2474
|
this.jsx_parseOpeningElementAt(start, position)
|
|
@@ -2492,8 +2527,6 @@ export function TSRXPlugin(config) {
|
|
|
2492
2527
|
element.type = 'Element';
|
|
2493
2528
|
}
|
|
2494
2529
|
|
|
2495
|
-
this.#path.push(element);
|
|
2496
|
-
|
|
2497
2530
|
for (const attr of open.attributes) {
|
|
2498
2531
|
if (attr.type === 'JSXAttribute') {
|
|
2499
2532
|
/** @type {AST.Attribute} */ (/** @type {unknown} */ (attr)).type = 'Attribute';
|
|
@@ -2534,7 +2567,12 @@ export function TSRXPlugin(config) {
|
|
|
2534
2567
|
|
|
2535
2568
|
element.attributes = open.attributes;
|
|
2536
2569
|
element.metadata ??= { path: [] };
|
|
2537
|
-
|
|
2570
|
+
// Opening-tag parsing can tokenize comments that appear before the first
|
|
2571
|
+
// child. Preserve that early container id so the comment stays associated
|
|
2572
|
+
// with this element during comment attachment/printing.
|
|
2573
|
+
if (element.metadata.commentContainerId === undefined) {
|
|
2574
|
+
element.metadata.commentContainerId = ++this.#commentContextId;
|
|
2575
|
+
}
|
|
2538
2576
|
|
|
2539
2577
|
if (element.selfClosing) {
|
|
2540
2578
|
this.#path.pop();
|
|
@@ -2548,7 +2586,7 @@ export function TSRXPlugin(config) {
|
|
|
2548
2586
|
enterScope: true,
|
|
2549
2587
|
});
|
|
2550
2588
|
|
|
2551
|
-
if (element.type === 'Tsx') {
|
|
2589
|
+
if (/** @type {AST.Tsx} */ (element).type === 'Tsx') {
|
|
2552
2590
|
this.#path.pop();
|
|
2553
2591
|
|
|
2554
2592
|
if (!element.unclosed) {
|
|
@@ -2725,7 +2763,7 @@ export function TSRXPlugin(config) {
|
|
|
2725
2763
|
enterScope: true,
|
|
2726
2764
|
});
|
|
2727
2765
|
|
|
2728
|
-
if (element.type === 'Tsx') {
|
|
2766
|
+
if (/** @type {AST.Tsx} */ (element).type === 'Tsx') {
|
|
2729
2767
|
this.#path.pop();
|
|
2730
2768
|
|
|
2731
2769
|
if (!element.unclosed) {
|
|
@@ -2749,12 +2787,15 @@ export function TSRXPlugin(config) {
|
|
|
2749
2787
|
this.#popTsxTokenContextBeforeTemplateExpressionChild();
|
|
2750
2788
|
this.next();
|
|
2751
2789
|
}
|
|
2752
|
-
} else if (element.type === 'TsxCompat') {
|
|
2790
|
+
} else if (/** @type {AST.TsxCompat} */ (element).type === 'TsxCompat') {
|
|
2753
2791
|
this.#path.pop();
|
|
2754
2792
|
|
|
2755
2793
|
if (!element.unclosed) {
|
|
2756
2794
|
const raise_error = () => {
|
|
2757
|
-
this.raise(
|
|
2795
|
+
this.raise(
|
|
2796
|
+
this.start,
|
|
2797
|
+
`Expected closing tag '</tsx:${/** @type {AST.TsxCompat} */ (element).kind}>'`,
|
|
2798
|
+
);
|
|
2758
2799
|
};
|
|
2759
2800
|
|
|
2760
2801
|
this.next();
|
|
@@ -2771,7 +2812,7 @@ export function TSRXPlugin(config) {
|
|
|
2771
2812
|
raise_error();
|
|
2772
2813
|
}
|
|
2773
2814
|
this.next();
|
|
2774
|
-
if (this.value !== element.kind) {
|
|
2815
|
+
if (this.value !== /** @type {AST.TsxCompat} */ (element).kind) {
|
|
2775
2816
|
raise_error();
|
|
2776
2817
|
}
|
|
2777
2818
|
this.next();
|
|
@@ -2781,7 +2822,10 @@ export function TSRXPlugin(config) {
|
|
|
2781
2822
|
this.#popTsxTokenContextBeforeTemplateExpressionChild();
|
|
2782
2823
|
this.next();
|
|
2783
2824
|
}
|
|
2784
|
-
} else if (
|
|
2825
|
+
} else if (
|
|
2826
|
+
/** @type {AST.Tsrx} */ (element).type === 'Tsrx' &&
|
|
2827
|
+
this.#path[this.#path.length - 1] === element
|
|
2828
|
+
) {
|
|
2785
2829
|
this.#report_broken_markup_error(
|
|
2786
2830
|
this.start,
|
|
2787
2831
|
"Unclosed tag '<tsrx>'. Expected '</tsrx>' before end of component.",
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
import { walk } from 'zimmerframe';
|
|
6
6
|
import { print } from 'esrap';
|
|
7
7
|
import { error } from '../../errors.js';
|
|
8
|
+
import { analyze_css } from '../../analyze/css-analyze.js';
|
|
9
|
+
import { prune_css } from '../../analyze/prune.js';
|
|
8
10
|
import {
|
|
9
11
|
ensure_function_metadata,
|
|
10
12
|
in_jsx_child_context,
|
|
@@ -36,7 +38,11 @@ import {
|
|
|
36
38
|
import * as b from '../../utils/builders.js';
|
|
37
39
|
import { apply_lazy_transforms, preallocate_lazy_ids } from '../lazy.js';
|
|
38
40
|
import { find_first_top_level_await_in_component_body } from '../await.js';
|
|
39
|
-
import {
|
|
41
|
+
import {
|
|
42
|
+
prepare_stylesheet_for_render,
|
|
43
|
+
annotate_component_with_hash,
|
|
44
|
+
is_style_element,
|
|
45
|
+
} from '../scoping.js';
|
|
40
46
|
import {
|
|
41
47
|
validate_class_component_declarations,
|
|
42
48
|
validate_component_loop_break_statement,
|
|
@@ -60,20 +66,43 @@ const HOOK_CALLBACK_OUTER_MUTATION_ERROR =
|
|
|
60
66
|
const TEMPLATE_FRAGMENT_ERROR =
|
|
61
67
|
'JSX fragment syntax is not needed in TSRX templates. TSRX renders in immediate mode, so everything is already a fragment. Use `<>...</>` only within <tsx>...</tsx>.';
|
|
62
68
|
|
|
69
|
+
/**
|
|
70
|
+
* @param {TransformContext} transform_context
|
|
71
|
+
* @returns {string}
|
|
72
|
+
*/
|
|
73
|
+
export function get_invalid_html_child_error_message(transform_context) {
|
|
74
|
+
return `\`{html ...}\` is only supported as the sole child of an element in ${transform_context.platform.name}.`;
|
|
75
|
+
}
|
|
76
|
+
|
|
63
77
|
/**
|
|
64
78
|
* @param {AST.Node} node
|
|
65
79
|
* @param {TransformContext} transform_context
|
|
66
80
|
*/
|
|
67
|
-
function
|
|
68
|
-
// this should be a fatal error so we don't pass the errors collection,
|
|
69
|
-
// since we don't have a transform for the Html node
|
|
81
|
+
function report_invalid_html_child_error(node, transform_context) {
|
|
70
82
|
error(
|
|
71
|
-
|
|
83
|
+
get_invalid_html_child_error_message(transform_context),
|
|
72
84
|
transform_context.filename,
|
|
73
85
|
node,
|
|
86
|
+
transform_context.errors,
|
|
87
|
+
transform_context.comments,
|
|
74
88
|
);
|
|
75
89
|
}
|
|
76
90
|
|
|
91
|
+
/**
|
|
92
|
+
* In loose/editor mode `error(...)` records the diagnostic and continues, so an
|
|
93
|
+
* invalid standalone `{html ...}` child still needs a valid expression node for
|
|
94
|
+
* the virtual TSX output.
|
|
95
|
+
*
|
|
96
|
+
* @param {any} node
|
|
97
|
+
* @param {TransformContext} transform_context
|
|
98
|
+
* @returns {ESTreeJSX.JSXExpressionContainer}
|
|
99
|
+
*/
|
|
100
|
+
export function recover_invalid_html_child(node, transform_context) {
|
|
101
|
+
report_invalid_html_child_error(node, transform_context);
|
|
102
|
+
const expression = set_loc(clone_expression_node(node.expression), node);
|
|
103
|
+
return to_jsx_expression_container(expression, node);
|
|
104
|
+
}
|
|
105
|
+
|
|
77
106
|
/**
|
|
78
107
|
* @param {AST.Node} node
|
|
79
108
|
* @param {TransformContext} transform_context
|
|
@@ -403,12 +432,14 @@ export function createJsxTransform(platform) {
|
|
|
403
432
|
|
|
404
433
|
const css = as_any.css;
|
|
405
434
|
if (css) {
|
|
435
|
+
apply_css_definition_metadata(as_any, css);
|
|
406
436
|
stylesheets.push(css);
|
|
407
437
|
const hash = css.hash;
|
|
408
438
|
annotate_component_with_hash(
|
|
409
439
|
as_any,
|
|
410
440
|
hash,
|
|
411
441
|
platform.jsx.rewriteClassAttr ? 'className' : 'class',
|
|
442
|
+
transform_context.typeOnly,
|
|
412
443
|
);
|
|
413
444
|
}
|
|
414
445
|
return next(state);
|
|
@@ -586,6 +617,73 @@ export function createJsxTransform(platform) {
|
|
|
586
617
|
return transform;
|
|
587
618
|
}
|
|
588
619
|
|
|
620
|
+
/**
|
|
621
|
+
* Attach selector-location metadata used by editor definitions/hover before
|
|
622
|
+
* the shared scoping pass mutates class attributes with the component hash.
|
|
623
|
+
*
|
|
624
|
+
* @param {any} component
|
|
625
|
+
* @param {any} css
|
|
626
|
+
* @returns {void}
|
|
627
|
+
*/
|
|
628
|
+
function apply_css_definition_metadata(component, css) {
|
|
629
|
+
analyze_css(css);
|
|
630
|
+
|
|
631
|
+
const metadata = component.metadata || (component.metadata = { path: [] });
|
|
632
|
+
const style_classes = metadata.styleClasses || (metadata.styleClasses = new Map());
|
|
633
|
+
const top_scoped_classes = metadata.topScopedClasses || new Map();
|
|
634
|
+
const elements = collect_css_prunable_elements(component.body || []);
|
|
635
|
+
|
|
636
|
+
for (const element of elements) {
|
|
637
|
+
prune_css(css, element, style_classes, top_scoped_classes);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
if (top_scoped_classes.size > 0) {
|
|
641
|
+
metadata.topScopedClasses = top_scoped_classes;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* @param {any} value
|
|
647
|
+
* @param {any[]} [elements]
|
|
648
|
+
* @returns {any[]}
|
|
649
|
+
*/
|
|
650
|
+
function collect_css_prunable_elements(value, elements = []) {
|
|
651
|
+
if (!value || typeof value !== 'object') {
|
|
652
|
+
return elements;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
if (Array.isArray(value)) {
|
|
656
|
+
for (const child of value) {
|
|
657
|
+
collect_css_prunable_elements(child, elements);
|
|
658
|
+
}
|
|
659
|
+
return elements;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
if (
|
|
663
|
+
value.type === 'FunctionDeclaration' ||
|
|
664
|
+
value.type === 'FunctionExpression' ||
|
|
665
|
+
value.type === 'ArrowFunctionExpression' ||
|
|
666
|
+
value.type === 'Component'
|
|
667
|
+
) {
|
|
668
|
+
return elements;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (value.type === 'Element') {
|
|
672
|
+
if (!is_style_element(value)) {
|
|
673
|
+
elements.push(value);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
for (const key of Object.keys(value)) {
|
|
678
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata' || key === 'css') {
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
collect_css_prunable_elements(value[key], elements);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
return elements;
|
|
685
|
+
}
|
|
686
|
+
|
|
589
687
|
/**
|
|
590
688
|
* Detect a top-level `"use server"` directive. Used by platforms whose
|
|
591
689
|
* validation rule requires the directive to enable top-level `await`
|
|
@@ -2310,10 +2408,19 @@ function to_jsx_element(node, transform_context, raw_children = node.children ||
|
|
|
2310
2408
|
selfClosing = child_transform.selfClosing;
|
|
2311
2409
|
}
|
|
2312
2410
|
} else {
|
|
2313
|
-
|
|
2314
|
-
|
|
2411
|
+
const html_child_transform = rewrite_host_html_children(
|
|
2412
|
+
node,
|
|
2413
|
+
walked_children,
|
|
2414
|
+
raw_children,
|
|
2415
|
+
attributes,
|
|
2416
|
+
transform_context,
|
|
2417
|
+
);
|
|
2418
|
+
if (html_child_transform) {
|
|
2419
|
+
children = html_child_transform.children;
|
|
2420
|
+
selfClosing = html_child_transform.selfClosing;
|
|
2421
|
+
} else {
|
|
2422
|
+
children = create_element_children(walked_children, transform_context);
|
|
2315
2423
|
}
|
|
2316
|
-
children = create_element_children(walked_children, transform_context);
|
|
2317
2424
|
}
|
|
2318
2425
|
const has_unmappable_attribute = attributes.some(
|
|
2319
2426
|
(/** @type {any} */ attribute) => attribute?.metadata?.has_unmappable_value,
|
|
@@ -2341,6 +2448,117 @@ function to_jsx_element(node, transform_context, raw_children = node.children ||
|
|
|
2341
2448
|
return set_loc(b.jsx_element_fresh(openingElement, closingElement, children), node);
|
|
2342
2449
|
}
|
|
2343
2450
|
|
|
2451
|
+
/**
|
|
2452
|
+
* @param {any} node
|
|
2453
|
+
* @param {any[]} walked_children
|
|
2454
|
+
* @param {any[]} raw_children
|
|
2455
|
+
* @param {any[]} attributes
|
|
2456
|
+
* @param {TransformContext} transform_context
|
|
2457
|
+
* @returns {{ children: any[]; selfClosing: boolean } | null}
|
|
2458
|
+
*/
|
|
2459
|
+
export function rewrite_host_html_children(
|
|
2460
|
+
node,
|
|
2461
|
+
walked_children,
|
|
2462
|
+
raw_children,
|
|
2463
|
+
attributes,
|
|
2464
|
+
transform_context,
|
|
2465
|
+
) {
|
|
2466
|
+
const source_children = raw_children || walked_children;
|
|
2467
|
+
const source_html_index = source_children.findIndex((child) => child?.type === 'Html');
|
|
2468
|
+
if (source_html_index === -1) {
|
|
2469
|
+
return null;
|
|
2470
|
+
}
|
|
2471
|
+
const source_html = source_children[source_html_index];
|
|
2472
|
+
const walked_html =
|
|
2473
|
+
walked_children[source_html_index]?.type === 'Html'
|
|
2474
|
+
? walked_children[source_html_index]
|
|
2475
|
+
: source_html;
|
|
2476
|
+
|
|
2477
|
+
if (is_component_like_element(node) || source_children.length !== 1) {
|
|
2478
|
+
report_invalid_html_child_error(source_html, transform_context);
|
|
2479
|
+
}
|
|
2480
|
+
|
|
2481
|
+
const conflicting_attribute = get_host_html_conflicting_attribute(attributes, transform_context);
|
|
2482
|
+
if (conflicting_attribute !== null) {
|
|
2483
|
+
error(
|
|
2484
|
+
create_host_html_conflict_error(conflicting_attribute, transform_context),
|
|
2485
|
+
transform_context.filename,
|
|
2486
|
+
source_html,
|
|
2487
|
+
transform_context.errors,
|
|
2488
|
+
transform_context.comments,
|
|
2489
|
+
);
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
attributes.push(create_host_html_attribute(walked_html, source_html, transform_context));
|
|
2493
|
+
|
|
2494
|
+
return { children: [], selfClosing: true };
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2497
|
+
/**
|
|
2498
|
+
* @param {any[]} attributes
|
|
2499
|
+
* @param {TransformContext} transform_context
|
|
2500
|
+
* @returns {{ kind: 'attribute'; name: string } | null}
|
|
2501
|
+
*/
|
|
2502
|
+
export function get_host_html_conflicting_attribute(attributes, transform_context) {
|
|
2503
|
+
const conflicting_attributes = get_host_html_conflicting_attribute_names(transform_context);
|
|
2504
|
+
for (const name of conflicting_attributes) {
|
|
2505
|
+
if (has_jsx_attribute(attributes, name)) {
|
|
2506
|
+
return { kind: 'attribute', name };
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
return null;
|
|
2511
|
+
}
|
|
2512
|
+
|
|
2513
|
+
/**
|
|
2514
|
+
* @param {{ kind: 'attribute'; name: string }} conflicting_attribute
|
|
2515
|
+
* @param {TransformContext} transform_context
|
|
2516
|
+
* @returns {string}
|
|
2517
|
+
*/
|
|
2518
|
+
export function create_host_html_conflict_error(conflicting_attribute, transform_context) {
|
|
2519
|
+
const html_attribute = get_host_html_attribute_name(transform_context);
|
|
2520
|
+
return `\`{html ...}\` lowers to \`${html_attribute}\` on the ${transform_context.platform.name} target and cannot be combined with an existing \`${conflicting_attribute.name}\` attribute.`;
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2523
|
+
/**
|
|
2524
|
+
* @param {TransformContext} transform_context
|
|
2525
|
+
* @returns {string[]}
|
|
2526
|
+
*/
|
|
2527
|
+
function get_host_html_conflicting_attribute_names(transform_context) {
|
|
2528
|
+
switch (transform_context.platform.name) {
|
|
2529
|
+
case 'Solid':
|
|
2530
|
+
return ['innerHTML', 'textContent'];
|
|
2531
|
+
case 'Vue':
|
|
2532
|
+
return ['innerHTML'];
|
|
2533
|
+
default:
|
|
2534
|
+
return [get_host_html_attribute_name(transform_context)];
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
/**
|
|
2539
|
+
* @param {TransformContext} transform_context
|
|
2540
|
+
* @returns {'dangerouslySetInnerHTML' | 'innerHTML'}
|
|
2541
|
+
*/
|
|
2542
|
+
function get_host_html_attribute_name(transform_context) {
|
|
2543
|
+
return transform_context.platform.jsx?.htmlProp === 'dangerouslySetInnerHTML'
|
|
2544
|
+
? 'dangerouslySetInnerHTML'
|
|
2545
|
+
: 'innerHTML';
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
/**
|
|
2549
|
+
* @param {any[]} attributes
|
|
2550
|
+
* @param {string} name
|
|
2551
|
+
* @returns {boolean}
|
|
2552
|
+
*/
|
|
2553
|
+
function has_jsx_attribute(attributes, name) {
|
|
2554
|
+
return attributes.some(
|
|
2555
|
+
(attr) =>
|
|
2556
|
+
attr?.type === 'JSXAttribute' &&
|
|
2557
|
+
attr.name?.type === 'JSXIdentifier' &&
|
|
2558
|
+
attr.name.name === name,
|
|
2559
|
+
);
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2344
2562
|
/**
|
|
2345
2563
|
* @param {any[]} children
|
|
2346
2564
|
* @param {TransformContext} transform_context
|
|
@@ -3580,7 +3798,7 @@ function to_jsx_child(node, transform_context) {
|
|
|
3580
3798
|
case 'TSRXExpression':
|
|
3581
3799
|
return to_jsx_expression_container(node.expression, node);
|
|
3582
3800
|
case 'Html':
|
|
3583
|
-
return
|
|
3801
|
+
return recover_invalid_html_child(node, transform_context);
|
|
3584
3802
|
case 'IfStatement':
|
|
3585
3803
|
return (
|
|
3586
3804
|
transform_context.platform.hooks?.controlFlow?.ifStatement ?? if_statement_to_jsx_child
|
|
@@ -4918,7 +5136,7 @@ function transform_element_attributes_dispatch(attrs, transform_context, element
|
|
|
4918
5136
|
* @param {any} element
|
|
4919
5137
|
* @returns {boolean}
|
|
4920
5138
|
*/
|
|
4921
|
-
function is_component_like_element(element) {
|
|
5139
|
+
export function is_component_like_element(element) {
|
|
4922
5140
|
const id = element?.id;
|
|
4923
5141
|
if (!id) return false;
|
|
4924
5142
|
if (id.type === 'Identifier') return /^[A-Z]/.test(id.name);
|
|
@@ -5127,6 +5345,31 @@ function is_named_ref_attribute(attr) {
|
|
|
5127
5345
|
);
|
|
5128
5346
|
}
|
|
5129
5347
|
|
|
5348
|
+
/**
|
|
5349
|
+
* @param {any} html_expression
|
|
5350
|
+
* @param {any} source_attr
|
|
5351
|
+
* @param {TransformContext} transform_context
|
|
5352
|
+
* @returns {any}
|
|
5353
|
+
*/
|
|
5354
|
+
export function create_host_html_attribute(html_expression, source_attr, transform_context) {
|
|
5355
|
+
const expression =
|
|
5356
|
+
html_expression?.type === 'Html' ? html_expression.expression : html_expression;
|
|
5357
|
+
const name = get_host_html_attribute_name(transform_context);
|
|
5358
|
+
const value =
|
|
5359
|
+
name === 'dangerouslySetInnerHTML'
|
|
5360
|
+
? set_loc(b.object([b.prop('init', b.id('__html'), expression)]), source_attr)
|
|
5361
|
+
: expression;
|
|
5362
|
+
const value_container = to_jsx_expression_container(value, source_attr);
|
|
5363
|
+
if (name !== 'dangerouslySetInnerHTML') {
|
|
5364
|
+
setLocation(value_container, source_attr, true);
|
|
5365
|
+
}
|
|
5366
|
+
|
|
5367
|
+
return set_loc(
|
|
5368
|
+
build_jsx_attribute(b.jsx_id(name), value_container, false, source_attr),
|
|
5369
|
+
source_attr,
|
|
5370
|
+
);
|
|
5371
|
+
}
|
|
5372
|
+
|
|
5130
5373
|
/**
|
|
5131
5374
|
* @param {any} expression
|
|
5132
5375
|
* @returns {boolean}
|
|
@@ -5379,6 +5622,8 @@ export function to_jsx_attribute(attr, transform_context) {
|
|
|
5379
5622
|
attr_name.name === 'class'
|
|
5380
5623
|
) {
|
|
5381
5624
|
attr_name = set_loc(b.id('className'), attr.name);
|
|
5625
|
+
attr_name.metadata.source_name = 'class';
|
|
5626
|
+
attr_name.metadata.source_length = 'class'.length;
|
|
5382
5627
|
}
|
|
5383
5628
|
|
|
5384
5629
|
const name =
|
package/src/transform/scoping.js
CHANGED
|
@@ -95,9 +95,15 @@ function is_composite_jsx_element(node) {
|
|
|
95
95
|
* @param {any} node
|
|
96
96
|
* @param {string} hash
|
|
97
97
|
* @param {'class' | 'className'} [jsx_class_attr_name='class']
|
|
98
|
+
* @param {boolean} [preserve_style_elements=false]
|
|
98
99
|
* @returns {any}
|
|
99
100
|
*/
|
|
100
|
-
export function annotate_with_hash(
|
|
101
|
+
export function annotate_with_hash(
|
|
102
|
+
node,
|
|
103
|
+
hash,
|
|
104
|
+
jsx_class_attr_name = 'class',
|
|
105
|
+
preserve_style_elements = false,
|
|
106
|
+
) {
|
|
101
107
|
if (!node || typeof node !== 'object') return node;
|
|
102
108
|
if (
|
|
103
109
|
node.type === 'Component' ||
|
|
@@ -109,13 +115,19 @@ export function annotate_with_hash(node, hash, jsx_class_attr_name = 'class') {
|
|
|
109
115
|
}
|
|
110
116
|
|
|
111
117
|
if (node.type === 'Element') {
|
|
118
|
+
if (preserve_style_elements && is_style_element(node)) {
|
|
119
|
+
node.children = [];
|
|
120
|
+
return node;
|
|
121
|
+
}
|
|
112
122
|
if (!is_style_element(node) && !is_composite_element(node)) {
|
|
113
123
|
add_hash_class(node, hash);
|
|
114
124
|
}
|
|
115
125
|
if (Array.isArray(node.children)) {
|
|
116
126
|
node.children = node.children
|
|
117
|
-
.filter((/** @type {any} */ child) => !is_style_element(child))
|
|
118
|
-
.map((/** @type {any} */ child) =>
|
|
127
|
+
.filter((/** @type {any} */ child) => preserve_style_elements || !is_style_element(child))
|
|
128
|
+
.map((/** @type {any} */ child) =>
|
|
129
|
+
annotate_with_hash(child, hash, jsx_class_attr_name, preserve_style_elements),
|
|
130
|
+
);
|
|
119
131
|
}
|
|
120
132
|
return node;
|
|
121
133
|
}
|
|
@@ -126,7 +138,7 @@ export function annotate_with_hash(node, hash, jsx_class_attr_name = 'class') {
|
|
|
126
138
|
}
|
|
127
139
|
if (Array.isArray(node.children)) {
|
|
128
140
|
node.children = node.children.map((/** @type {any} */ child) =>
|
|
129
|
-
annotate_with_hash(child, hash, jsx_class_attr_name),
|
|
141
|
+
annotate_with_hash(child, hash, jsx_class_attr_name, preserve_style_elements),
|
|
130
142
|
);
|
|
131
143
|
}
|
|
132
144
|
return node;
|
|
@@ -140,10 +152,10 @@ export function annotate_with_hash(node, hash, jsx_class_attr_name = 'class') {
|
|
|
140
152
|
const value = node[key];
|
|
141
153
|
if (Array.isArray(value)) {
|
|
142
154
|
node[key] = value.map((/** @type {any} */ child) =>
|
|
143
|
-
annotate_with_hash(child, hash, jsx_class_attr_name),
|
|
155
|
+
annotate_with_hash(child, hash, jsx_class_attr_name, preserve_style_elements),
|
|
144
156
|
);
|
|
145
157
|
} else if (value && typeof value === 'object') {
|
|
146
|
-
node[key] = annotate_with_hash(value, hash, jsx_class_attr_name);
|
|
158
|
+
node[key] = annotate_with_hash(value, hash, jsx_class_attr_name, preserve_style_elements);
|
|
147
159
|
}
|
|
148
160
|
}
|
|
149
161
|
|
|
@@ -154,14 +166,22 @@ export function annotate_with_hash(node, hash, jsx_class_attr_name = 'class') {
|
|
|
154
166
|
* @param {any} component
|
|
155
167
|
* @param {string} hash
|
|
156
168
|
* @param {'class' | 'className'} [jsx_class_attr_name='class']
|
|
169
|
+
* @param {boolean} [preserve_style_elements=false]
|
|
157
170
|
* @returns {void}
|
|
158
171
|
*/
|
|
159
|
-
export function annotate_component_with_hash(
|
|
172
|
+
export function annotate_component_with_hash(
|
|
173
|
+
component,
|
|
174
|
+
hash,
|
|
175
|
+
jsx_class_attr_name = 'class',
|
|
176
|
+
preserve_style_elements = false,
|
|
177
|
+
) {
|
|
160
178
|
/** @type {any[]} */
|
|
161
179
|
const body = component.body;
|
|
162
180
|
component.body = body
|
|
163
|
-
.filter((/** @type {any} */ child) => !is_style_element(child))
|
|
164
|
-
.map((/** @type {any} */ child) =>
|
|
181
|
+
.filter((/** @type {any} */ child) => preserve_style_elements || !is_style_element(child))
|
|
182
|
+
.map((/** @type {any} */ child) =>
|
|
183
|
+
annotate_with_hash(child, hash, jsx_class_attr_name, preserve_style_elements),
|
|
184
|
+
);
|
|
165
185
|
}
|
|
166
186
|
|
|
167
187
|
/**
|
|
@@ -198,7 +218,8 @@ export function add_hash_class(element, hash) {
|
|
|
198
218
|
|
|
199
219
|
if (value.type === 'Literal' && typeof value.value === 'string') {
|
|
200
220
|
const merged = `${value.value} ${hash}`;
|
|
201
|
-
|
|
221
|
+
value.value = merged;
|
|
222
|
+
value.raw = JSON.stringify(merged);
|
|
202
223
|
return;
|
|
203
224
|
}
|
|
204
225
|
|
|
@@ -233,7 +254,11 @@ function add_hash_class_to_jsx_element(element, hash, jsx_class_attr_name) {
|
|
|
233
254
|
existing.name = {
|
|
234
255
|
type: 'JSXIdentifier',
|
|
235
256
|
name: jsx_class_attr_name,
|
|
236
|
-
metadata:
|
|
257
|
+
metadata: {
|
|
258
|
+
...(existing.name.metadata || { path: [] }),
|
|
259
|
+
source_name: existing.name.name,
|
|
260
|
+
source_length: existing.name.name.length,
|
|
261
|
+
},
|
|
237
262
|
};
|
|
238
263
|
}
|
|
239
264
|
|
|
@@ -245,7 +270,8 @@ function add_hash_class_to_jsx_element(element, hash, jsx_class_attr_name) {
|
|
|
245
270
|
|
|
246
271
|
if (value.type === 'Literal' && typeof value.value === 'string') {
|
|
247
272
|
const merged = `${value.value} ${hash}`;
|
|
248
|
-
|
|
273
|
+
value.value = merged;
|
|
274
|
+
value.raw = JSON.stringify(merged);
|
|
249
275
|
return;
|
|
250
276
|
}
|
|
251
277
|
|