@tsrx/solid 0.0.8 → 0.0.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.
Files changed (2) hide show
  1. package/package.json +2 -2
  2. package/src/transform.js +129 -439
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Solid compiler built on @tsrx/core",
4
4
  "license": "MIT",
5
5
  "author": "Dominic Gannaway",
6
- "version": "0.0.8",
6
+ "version": "0.0.10",
7
7
  "type": "module",
8
8
  "publishConfig": {
9
9
  "access": "public"
@@ -22,7 +22,7 @@
22
22
  "dependencies": {
23
23
  "esrap": "^2.1.0",
24
24
  "zimmerframe": "^1.1.2",
25
- "@tsrx/core": "0.0.8"
25
+ "@tsrx/core": "0.0.10"
26
26
  },
27
27
  "peerDependencies": {
28
28
  "solid-js": ">=1.8 || >=2.0.0-beta"
package/src/transform.js CHANGED
@@ -1,22 +1,30 @@
1
1
  /** @import * as AST from 'estree' */
2
2
  /** @import * as ESTreeJSX from 'estree-jsx' */
3
3
 
4
- import { walk } from 'zimmerframe';
5
- import { print } from 'esrap';
6
- import tsx from 'esrap/languages/tsx';
7
4
  import {
8
- renderStylesheets,
5
+ createJsxTransform,
9
6
  setLocation,
10
7
  applyLazyTransforms as apply_lazy_transforms,
11
- findFirstTopLevelAwaitInComponentBody as find_first_top_level_await_in_component_body,
12
8
  collectLazyBindingsFromComponent as collect_lazy_bindings_from_component,
13
- preallocateLazyIds as preallocate_lazy_ids,
14
9
  replaceLazyParams as replace_lazy_params,
15
- prepareStylesheetForRender as prepare_stylesheet_for_render,
16
- annotateComponentWithHash as annotate_component_with_hash,
17
10
  isInterleavedBody as is_interleaved_body_core,
18
11
  isCapturableJsxChild as is_capturable_jsx_child,
19
12
  captureJsxChild,
13
+ tsxNodeToJsxExpression as tsx_node_to_jsx_expression,
14
+ // Shared AST builders (truly platform-agnostic utilities).
15
+ clone_expression_node,
16
+ clone_identifier,
17
+ clone_jsx_name,
18
+ create_compile_error,
19
+ create_generated_identifier,
20
+ create_null_literal,
21
+ flatten_switch_consequent,
22
+ get_for_of_iteration_params,
23
+ identifier_to_jsx_name,
24
+ is_dynamic_element_id,
25
+ is_jsx_child,
26
+ set_loc,
27
+ to_text_expression,
20
28
  } from '@tsrx/core';
21
29
 
22
30
  /**
@@ -37,149 +45,75 @@ import {
37
45
  */
38
46
 
39
47
  /**
40
- * Transform a parsed tsrx-solid AST into a TSX module targeting Solid 2.0.
48
+ * Solid platform descriptor consumed by `createJsxTransform`. Everything
49
+ * that diverges from React/Preact is plugged in via `hooks`:
50
+ * - Component-level `await` is rejected outright (no `"use server"` escape).
51
+ * - Control-flow statements become Solid's `<Show>` / `<For>` /
52
+ * `<Switch>/<Match>` / `<Errored>/<Loading>` instead of inline JSX.
53
+ * - `component` declarations run once at setup, with early-return JSX
54
+ * hoisted into a reactive `<Show when={!cond}>`.
55
+ * - Element attributes support composite elements and lift a lone
56
+ * `{text ...}` child into a `textContent` attribute.
57
+ * - `needs_show` / `needs_for` / etc. flags track which runtime
58
+ * primitives must be imported, injected by `inject_solid_imports`.
41
59
  *
42
- * Each `component` declaration becomes a plain `FunctionDeclaration` that
43
- * returns Solid JSX. Control flow statements are rewritten to Solid's
44
- * built-in components (`<Show>`, `<Switch>/<Match>`, `<For>`, `<Errored>`,
45
- * `<Loading>`) so they remain reactive. Per-component `<style>` blocks are
46
- * collected, rendered via `@tsrx/core`'s stylesheet renderer, and returned
47
- * alongside the JS output so a downstream plugin can inject them.
48
- *
49
- * @param {AST.Program} ast
50
- * @param {string} source
51
- * @param {string} [filename]
52
- * @returns {{ ast: AST.Program, code: string, map: any, css: { code: string, hash: string } | null }}
60
+ * @type {import('@tsrx/core/types').JsxPlatform}
53
61
  */
54
- export function transform(ast, source, filename) {
55
- /** @type {any[]} */
56
- const stylesheets = [];
57
-
58
- /** @type {TransformContext} */
59
- const transform_context = {
60
- needs_show: false,
61
- needs_for: false,
62
- needs_switch: false,
63
- needs_match: false,
64
- needs_errored: false,
65
- needs_loading: false,
66
- lazy_next_id: 0,
67
- current_css_hash: null,
68
- };
69
-
70
- preallocate_lazy_ids(/** @type {any} */ (ast), transform_context);
71
-
72
- // First pass: collect stylesheets and annotate elements with the component hash.
73
- walk(/** @type {any} */ (ast), transform_context, {
74
- Component(node, { next, state }) {
75
- const as_any = /** @type {any} */ (node);
76
- const await_expression = find_first_top_level_await_in_component_body(as_any.body || []);
77
-
78
- if (await_expression) {
79
- const await_start = get_await_keyword_start(await_expression, source);
80
- const adjusted_node = /** @type {any} */ ({
81
- ...await_expression,
82
- start: await_start,
83
- end: await_start + 'await'.length,
84
- });
85
-
86
- throw create_compile_error(
87
- adjusted_node,
88
- '`await` is not allowed inside Solid components.',
89
- );
90
- }
91
-
92
- const css = as_any.css;
93
- if (css) {
94
- stylesheets.push(css);
95
- annotate_component_with_hash(as_any, css.hash);
96
- }
97
- return next(state);
98
- },
99
- });
100
-
101
- // Second pass: transform Components, Elements, Text nodes, Tsx blocks, etc.
102
- const transformed = walk(/** @type {any} */ (ast), transform_context, {
103
- Component(node, { next, state }) {
104
- const as_any = /** @type {any} */ (node);
105
-
106
- const saved_css_hash = state.current_css_hash;
107
- state.current_css_hash = as_any.css ? as_any.css.hash : null;
108
-
109
- const inner = /** @type {any} */ (next() ?? node);
110
-
111
- state.current_css_hash = saved_css_hash;
112
-
113
- return /** @type {any} */ (component_to_function_declaration(inner, state));
114
- },
115
-
116
- Tsx(node, { next }) {
117
- const inner = /** @type {any} */ (next() ?? node);
118
- return /** @type {any} */ (tsx_node_to_jsx_expression(inner));
119
- },
120
-
121
- TsxCompat(node, { next }) {
122
- const inner = /** @type {any} */ (next() ?? node);
123
- return /** @type {any} */ (tsx_compat_node_to_jsx_expression(inner));
124
- },
125
-
126
- Element(node, { next, state }) {
127
- const inner = /** @type {any} */ (next() ?? node);
128
- return /** @type {any} */ (to_jsx_element(inner, state));
129
- },
130
-
131
- // `Text` nodes are lowered by `to_jsx_child` (and the `textContent`
132
- // optimization in `to_jsx_element`) rather than the walker, so the
133
- // parent element still sees a raw `Text` child when it runs and can
134
- // decide whether to hoist it up to an attribute.
135
- TSRXExpression(node, { next }) {
136
- const inner = /** @type {any} */ (next() ?? node);
137
- return /** @type {any} */ (to_jsx_expression_container(inner.expression, inner));
62
+ const solid_platform = {
63
+ name: 'Solid',
64
+ imports: {
65
+ // Solid doesn't use the React-style Suspense / ErrorBoundary pair.
66
+ // Both fields are here to satisfy the descriptor shape; actual
67
+ // import injection goes through `hooks.injectImports`.
68
+ suspense: 'solid-js',
69
+ errorBoundary: 'solid-js',
70
+ },
71
+ jsx: {
72
+ rewriteClassAttr: false,
73
+ acceptedTsxKinds: ['solid'],
74
+ },
75
+ validation: {
76
+ requireUseServerForAwait: true,
77
+ // Solid's custom validator always rejects component-level await,
78
+ // so directive scanning is redundant work. Keep the fallback flag
79
+ // above true as a safety net if the custom hook is removed.
80
+ scanUseServerDirectiveForAwaitWithCustomValidator: false,
81
+ },
82
+ hooks: {
83
+ initialState: () => ({
84
+ needs_show: false,
85
+ needs_for: false,
86
+ needs_switch: false,
87
+ needs_match: false,
88
+ needs_errored: false,
89
+ needs_loading: false,
90
+ }),
91
+ validateComponentAwait: (await_expression, _component, _ctx, _requires, source) => {
92
+ const await_start = get_await_keyword_start(await_expression, source);
93
+ const adjusted_node = /** @type {any} */ ({
94
+ ...await_expression,
95
+ start: await_start,
96
+ end: await_start + 'await'.length,
97
+ });
98
+ throw create_compile_error(adjusted_node, '`await` is not allowed inside Solid components.');
138
99
  },
139
-
140
- MemberExpression(node, { next, state }) {
141
- const as_any = /** @type {any} */ (node);
142
- if (as_any.object && as_any.object.type === 'StyleIdentifier' && state.current_css_hash) {
143
- const class_name = as_any.computed ? as_any.property.value : as_any.property.name;
144
- const value = `${state.current_css_hash} ${class_name}`;
145
- return /** @type {any} */ ({ type: 'Literal', value, raw: JSON.stringify(value) });
146
- }
147
- return next();
100
+ controlFlow: {
101
+ ifStatement: if_statement_to_jsx_child,
102
+ forOf: for_of_statement_to_jsx_child,
103
+ switchStatement: switch_statement_to_jsx_child,
104
+ tryStatement: try_statement_to_jsx_child,
148
105
  },
149
- });
150
-
151
- inject_solid_imports(/** @type {AST.Program} */ (transformed), transform_context);
152
-
153
- // Apply lazy destructuring transforms to module-level code (top-level function
154
- // declarations, arrow functions, etc.). Component bodies have already been
155
- // transformed inside component_to_function_declaration; this catches plain
156
- // functions outside components and any lazy patterns in module scope.
157
- const final_program = /** @type {any} */ (
158
- apply_lazy_transforms(/** @type {any} */ (transformed), new Map())
159
- );
160
-
161
- const result = print(/** @type {any} */ (final_program), tsx(), {
162
- sourceMapSource: filename,
163
- sourceMapContent: source,
164
- });
165
-
166
- const css =
167
- stylesheets.length > 0
168
- ? {
169
- code: renderStylesheets(
170
- /** @type {any} */ (stylesheets.map(prepare_stylesheet_for_render)),
171
- ),
172
- hash: stylesheets.map((s) => s.hash).join(' '),
173
- }
174
- : null;
175
-
176
- return {
177
- ast: /** @type {AST.Program} */ (final_program),
178
- code: result.code,
179
- map: result.map,
180
- css,
181
- };
182
- }
106
+ componentToFunction: (component, ctx) =>
107
+ component_to_function_declaration(component, /** @type {any} */ (ctx)),
108
+ injectImports: (program, ctx) => inject_solid_imports(program, /** @type {any} */ (ctx)),
109
+ transformElementAttributes: (attrs, ctx, element) =>
110
+ transform_element_attributes(attrs, is_composite_element(element), /** @type {any} */ (ctx)),
111
+ transformElement: (inner, ctx, raw_children) =>
112
+ to_jsx_element(/** @type {any} */ (inner), /** @type {any} */ (ctx), raw_children),
113
+ },
114
+ };
115
+
116
+ export const transform = createJsxTransform(solid_platform);
183
117
 
184
118
  /**
185
119
  * @param {any} await_node
@@ -373,31 +307,6 @@ function component_to_function_declaration(component, transform_context) {
373
307
  // Control flow → Solid JSX components
374
308
  // =====================================================================
375
309
 
376
- /**
377
- * @param {any} node
378
- * @returns {boolean}
379
- */
380
- function is_jsx_child(node) {
381
- if (!node) return false;
382
- const t = node.type;
383
- return (
384
- t === 'JSXElement' ||
385
- t === 'JSXFragment' ||
386
- t === 'JSXExpressionContainer' ||
387
- t === 'JSXText' ||
388
- t === 'Tsx' ||
389
- t === 'TsxCompat' ||
390
- t === 'Element' ||
391
- t === 'Text' ||
392
- t === 'TSRXExpression' ||
393
- t === 'Html' ||
394
- t === 'IfStatement' ||
395
- t === 'ForOfStatement' ||
396
- t === 'SwitchStatement' ||
397
- t === 'TryStatement'
398
- );
399
- }
400
-
401
310
  /**
402
311
  * @param {any} node
403
312
  * @param {TransformContext} transform_context
@@ -407,9 +316,11 @@ function to_jsx_child(node, transform_context) {
407
316
  if (!node) return node;
408
317
  switch (node.type) {
409
318
  case 'Tsx':
410
- return tsx_node_to_jsx_expression(node);
319
+ // We're inside a JSX child position by construction; keep `{expr}`
320
+ // containers wrapped. See helpers.js.
321
+ return tsx_node_to_jsx_expression(node, true);
411
322
  case 'TsxCompat':
412
- return tsx_compat_node_to_jsx_expression(node);
323
+ return tsx_compat_node_to_jsx_expression(node, true);
413
324
  case 'Element':
414
325
  return to_jsx_element(node, transform_context);
415
326
  case 'Text':
@@ -1102,11 +1013,22 @@ function inject_solid_imports(program, transform_context) {
1102
1013
  // =====================================================================
1103
1014
 
1104
1015
  /**
1105
- * @param {any} node
1016
+ * @param {any} node - walker-transformed Element whose `children` have
1017
+ * already had `StyleIdentifier` / `TSRXExpression` / nested `Element`
1018
+ * walker rewrites applied.
1106
1019
  * @param {TransformContext} transform_context
1020
+ * @param {any[]} [pre_walk_children] - optional pre-walk children list
1021
+ * from the `transformElement` hook. Only used to detect the
1022
+ * "single `Text` child" shape for the `textContent` optimization —
1023
+ * once detected we build the attribute from the original `Text.expression`.
1024
+ * The factory's `Text` walker lowers `Text` → `JSXExpressionContainer`, so
1025
+ * without these we'd miss the optimization. For rendering non-textContent
1026
+ * children we keep using `node.children` (walker-transformed), so
1027
+ * `MemberExpression` rewrites on `StyleIdentifier` refs inside children
1028
+ * are preserved.
1107
1029
  * @returns {any}
1108
1030
  */
1109
- function to_jsx_element(node, transform_context) {
1031
+ function to_jsx_element(node, transform_context, pre_walk_children) {
1110
1032
  if (node.type === 'JSXElement') return node;
1111
1033
 
1112
1034
  // `{html expr}` isn't supported on the Solid target — users should reach
@@ -1115,8 +1037,9 @@ function to_jsx_element(node, transform_context) {
1115
1037
  // explicit in their source. Only Ripple has a `{html ...}` primitive.
1116
1038
  // The check runs before the dynamic-element branch so `<@Dyn>{html x}</@Dyn>`
1117
1039
  // fails with the same diagnostic as the static-element case.
1118
- const raw_children = node.children || [];
1119
- if (raw_children.some((/** @type {any} */ c) => c && c.type === 'Html')) {
1040
+ const walked_children = node.children || [];
1041
+ const text_optimization_children = pre_walk_children ?? walked_children;
1042
+ if (walked_children.some((/** @type {any} */ c) => c && c.type === 'Html')) {
1120
1043
  throw new Error(
1121
1044
  '`{html ...}` is not supported on the Solid target. Use `innerHTML={...}` as an element attribute instead.',
1122
1045
  );
@@ -1146,16 +1069,20 @@ function to_jsx_element(node, transform_context) {
1146
1069
  // the parent is a host element (composite components receive
1147
1070
  // `textContent` as an opaque prop with no DOM semantics), and when the
1148
1071
  // user hasn't already set `textContent` themselves.
1072
+ //
1073
+ // We check `text_optimization_children` (pre-walk) rather than
1074
+ // `walked_children` because the factory's `Text` walker has already
1075
+ // lowered `Text` → `JSXExpressionContainer`, which wouldn't match.
1149
1076
  let selfClosing = !!node.selfClosing;
1150
1077
  let children;
1151
1078
  if (
1152
1079
  !is_composite &&
1153
- raw_children.length === 1 &&
1154
- raw_children[0] &&
1155
- raw_children[0].type === 'Text' &&
1080
+ text_optimization_children.length === 1 &&
1081
+ text_optimization_children[0] &&
1082
+ text_optimization_children[0].type === 'Text' &&
1156
1083
  !has_text_content_attribute(attributes)
1157
1084
  ) {
1158
- const text_child = raw_children[0];
1085
+ const text_child = text_optimization_children[0];
1159
1086
  attributes.push(
1160
1087
  set_loc(
1161
1088
  /** @type {any} */ ({
@@ -1165,10 +1092,14 @@ function to_jsx_element(node, transform_context) {
1165
1092
  name: 'textContent',
1166
1093
  metadata: { path: [] },
1167
1094
  },
1168
- value: to_jsx_expression_container(
1169
- to_text_expression(text_child.expression, text_child),
1170
- text_child,
1171
- ),
1095
+ // preserves the walker's rewrites on the Text's inner expression
1096
+ value:
1097
+ walked_children[0] && walked_children[0].type === 'JSXExpressionContainer'
1098
+ ? walked_children[0]
1099
+ : to_jsx_expression_container(
1100
+ to_text_expression(text_child.expression, text_child),
1101
+ text_child,
1102
+ ),
1172
1103
  shorthand: false,
1173
1104
  metadata: { path: [] },
1174
1105
  }),
@@ -1178,7 +1109,10 @@ function to_jsx_element(node, transform_context) {
1178
1109
  children = [];
1179
1110
  selfClosing = true;
1180
1111
  } else {
1181
- children = create_element_children(raw_children, transform_context);
1112
+ // Use walker-transformed children so `MemberExpression` /
1113
+ // `StyleIdentifier` rewrites from the factory walker are preserved
1114
+ // in the emitted JSX.
1115
+ children = create_element_children(walked_children, transform_context);
1182
1116
  }
1183
1117
 
1184
1118
  const openingElement = set_loc(
@@ -1196,7 +1130,11 @@ function to_jsx_element(node, transform_context) {
1196
1130
  : set_loc(
1197
1131
  /** @type {any} */ ({
1198
1132
  type: 'JSXClosingElement',
1199
- name: clone_jsx_name(name, node.closingElement || node),
1133
+ // Forward the source *name* (not the JSXClosingElement wrapper)
1134
+ // so `clone_jsx_name` can propagate member-expression sub-part
1135
+ // locations from the closing tag. See the identical fix in
1136
+ // packages/tsrx/src/transform/jsx/index.js.
1137
+ name: clone_jsx_name(name, node.closingElement?.name || node.closingElement || node),
1200
1138
  }),
1201
1139
  node.closingElement || node,
1202
1140
  );
@@ -1276,17 +1214,6 @@ function to_jsx_attribute(attr) {
1276
1214
  );
1277
1215
  }
1278
1216
 
1279
- /**
1280
- * @param {any} id
1281
- * @returns {boolean}
1282
- */
1283
- function is_dynamic_element_id(id) {
1284
- if (!id || typeof id !== 'object') return false;
1285
- if (id.type === 'Identifier') return !!id.tracked;
1286
- if (id.type === 'MemberExpression') return is_dynamic_element_id(id.object);
1287
- return false;
1288
- }
1289
-
1290
1217
  /**
1291
1218
  * Detect whether an `Element` node represents a composite component (tag
1292
1219
  * name starts with an uppercase letter, or is a member expression like
@@ -1538,39 +1465,6 @@ function to_jsx_expression_container(expression, source_node = expression) {
1538
1465
  );
1539
1466
  }
1540
1467
 
1541
- /**
1542
- * `{text expr}` → `expr == null ? '' : expr + ''` — coerce to string,
1543
- * matching React's text semantics so booleans/objects render as text.
1544
- *
1545
- * @param {AST.Expression} expression
1546
- * @param {any} [source_node]
1547
- * @returns {AST.Expression}
1548
- */
1549
- function to_text_expression(expression, source_node = expression) {
1550
- return set_loc(
1551
- /** @type {AST.Expression} */ ({
1552
- type: 'ConditionalExpression',
1553
- test: {
1554
- type: 'BinaryExpression',
1555
- operator: '==',
1556
- left: clone_expression_node(expression),
1557
- right: { type: 'Literal', value: null, raw: 'null', metadata: { path: [] } },
1558
- metadata: { path: [] },
1559
- },
1560
- consequent: { type: 'Literal', value: '', raw: "''", metadata: { path: [] } },
1561
- alternate: {
1562
- type: 'BinaryExpression',
1563
- operator: '+',
1564
- left: clone_expression_node(expression),
1565
- right: { type: 'Literal', value: '', raw: "''", metadata: { path: [] } },
1566
- metadata: { path: [] },
1567
- },
1568
- metadata: { path: [] },
1569
- }),
1570
- source_node,
1571
- );
1572
- }
1573
-
1574
1468
  /**
1575
1469
  * @param {any[]} render_nodes
1576
1470
  * @returns {any}
@@ -1598,221 +1492,17 @@ function build_return_expression(render_nodes) {
1598
1492
  );
1599
1493
  }
1600
1494
 
1601
- /**
1602
- * @param {AST.Identifier | AST.MemberExpression | any} id
1603
- * @returns {any}
1604
- */
1605
- function identifier_to_jsx_name(id) {
1606
- if (id.type === 'Identifier') {
1607
- return set_loc(
1608
- /** @type {any} */ ({
1609
- type: 'JSXIdentifier',
1610
- name: id.name,
1611
- metadata: { path: [], is_component: /^[A-Z]/.test(id.name) },
1612
- }),
1613
- id,
1614
- );
1615
- }
1616
- if (id.type === 'MemberExpression') {
1617
- return set_loc(
1618
- /** @type {any} */ ({
1619
- type: 'JSXMemberExpression',
1620
- object: /** @type {any} */ (identifier_to_jsx_name(id.object)),
1621
- property: /** @type {any} */ (identifier_to_jsx_name(id.property)),
1622
- }),
1623
- id,
1624
- );
1625
- }
1626
- return id;
1627
- }
1628
-
1629
- /**
1630
- * @param {any} name
1631
- * @param {any} [source_node]
1632
- * @returns {any}
1633
- */
1634
- function clone_jsx_name(name, source_node = name) {
1635
- if (name.type === 'JSXIdentifier') {
1636
- return set_loc(
1637
- { type: 'JSXIdentifier', name: name.name, metadata: name.metadata || { path: [] } },
1638
- source_node,
1639
- );
1640
- }
1641
- if (name.type === 'JSXMemberExpression') {
1642
- return set_loc(
1643
- {
1644
- type: 'JSXMemberExpression',
1645
- object: clone_jsx_name(name.object, source_node.object || name.object),
1646
- property: clone_jsx_name(name.property, source_node.property || name.property),
1647
- metadata: name.metadata || { path: [] },
1648
- },
1649
- source_node,
1650
- );
1651
- }
1652
- return name;
1653
- }
1654
-
1655
- /**
1656
- * @param {AST.Identifier} identifier
1657
- * @returns {any}
1658
- */
1659
- function clone_identifier(identifier) {
1660
- return set_loc(
1661
- /** @type {any} */ ({
1662
- type: 'Identifier',
1663
- name: identifier.name,
1664
- metadata: { path: [] },
1665
- }),
1666
- identifier,
1667
- );
1668
- }
1669
-
1670
1495
  /**
1671
1496
  * @param {any} node
1497
+ * @param {boolean} [in_jsx_child]
1672
1498
  * @returns {any}
1673
1499
  */
1674
- function clone_expression_node(node) {
1675
- if (!node || typeof node !== 'object') return node;
1676
- if (Array.isArray(node)) return node.map(clone_expression_node);
1677
- const clone = { ...node };
1678
- for (const key of Object.keys(clone)) {
1679
- // Positional keys are value-shared across nodes and — more importantly —
1680
- // `loc` objects often contain back-references to sub-objects that would
1681
- // blow the stack without a cycle guard. Every other AST traversal in
1682
- // this file skips these; keep them as shallow-copied references.
1683
- if (key === 'loc' || key === 'start' || key === 'end') continue;
1684
- if (key === 'metadata') {
1685
- clone.metadata = clone.metadata ? { ...clone.metadata } : { path: [] };
1686
- continue;
1687
- }
1688
- clone[key] = clone_expression_node(clone[key]);
1689
- }
1690
- return clone;
1691
- }
1692
-
1693
- /**
1694
- * @returns {AST.Literal}
1695
- */
1696
- function create_null_literal() {
1697
- return /** @type {any} */ ({ type: 'Literal', value: null, raw: 'null', metadata: { path: [] } });
1698
- }
1699
-
1700
- /**
1701
- * @template T
1702
- * @param {T} node
1703
- * @param {any} source_node
1704
- * @returns {T}
1705
- */
1706
- function set_loc(node, source_node) {
1707
- /** @type {any} */ (node).metadata ??= { path: [] };
1708
- if (source_node?.loc) {
1709
- return /** @type {T} */ (setLocation(/** @type {any} */ (node), source_node, true));
1710
- }
1711
- return node;
1712
- }
1713
-
1714
- /**
1715
- * @param {any} left
1716
- * @param {any} index
1717
- * @returns {any[]}
1718
- */
1719
- function get_for_of_iteration_params(left, index) {
1720
- const params = [];
1721
- if (left?.type === 'VariableDeclaration') {
1722
- params.push(left.declarations[0]?.id);
1723
- } else {
1724
- params.push(left);
1725
- }
1726
- if (index) params.push(index);
1727
- return params;
1728
- }
1729
-
1730
- /**
1731
- * @param {string} name
1732
- * @returns {any}
1733
- */
1734
- function create_generated_identifier(name) {
1735
- return /** @type {any} */ ({ type: 'Identifier', name, metadata: { path: [] } });
1736
- }
1737
-
1738
- /**
1739
- * @param {any} node
1740
- * @param {string} message
1741
- * @returns {Error & { pos: number, end: number }}
1742
- */
1743
- function create_compile_error(node, message) {
1744
- const error = /** @type {Error & { pos: number, end: number }} */ (new Error(message));
1745
- error.pos = node.start ?? 0;
1746
- error.end = node.end ?? error.pos + 1;
1747
- return error;
1748
- }
1749
-
1750
- /**
1751
- * @param {any[]} consequent
1752
- * @returns {any[]}
1753
- */
1754
- function flatten_switch_consequent(consequent) {
1755
- const result = [];
1756
- for (const node of consequent) {
1757
- if (node.type === 'BlockStatement') result.push(...node.body);
1758
- else result.push(node);
1759
- }
1760
- return result;
1761
- }
1762
-
1763
- /**
1764
- * @param {any} node
1765
- * @returns {any}
1766
- */
1767
- function tsx_compat_node_to_jsx_expression(node) {
1500
+ function tsx_compat_node_to_jsx_expression(node, in_jsx_child = false) {
1768
1501
  if (node.kind !== 'solid') {
1769
1502
  throw create_compile_error(
1770
1503
  node,
1771
1504
  `Solid TSRX does not support <tsx:${node.kind}> blocks. Use <tsx> or <tsx:solid>.`,
1772
1505
  );
1773
1506
  }
1774
- return tsx_node_to_jsx_expression(node);
1775
- }
1776
-
1777
- /**
1778
- * `<tsx>...</tsx>` → Solid JSX fragment (or single child if only one).
1779
- *
1780
- * @param {any} node
1781
- * @returns {any}
1782
- */
1783
- function tsx_node_to_jsx_expression(node) {
1784
- const children = (node.children || []).filter(
1785
- (/** @type {any} */ child) => child.type !== 'JSXText' || child.value.trim() !== '',
1786
- );
1787
-
1788
- if (children.length === 1 && children[0].type !== 'JSXText') {
1789
- return strip_locations(children[0]);
1790
- }
1791
-
1792
- return strip_locations(
1793
- /** @type {any} */ ({
1794
- type: 'JSXFragment',
1795
- openingFragment: { type: 'JSXOpeningFragment', metadata: { path: [] } },
1796
- closingFragment: { type: 'JSXClosingFragment', metadata: { path: [] } },
1797
- children,
1798
- metadata: { path: [] },
1799
- }),
1800
- );
1801
- }
1802
-
1803
- /**
1804
- * @param {any} node
1805
- * @returns {any}
1806
- */
1807
- function strip_locations(node) {
1808
- if (!node || typeof node !== 'object') return node;
1809
- if (Array.isArray(node)) return node.map(strip_locations);
1810
- delete node.loc;
1811
- delete node.start;
1812
- delete node.end;
1813
- for (const key of Object.keys(node)) {
1814
- if (key === 'metadata') continue;
1815
- node[key] = strip_locations(node[key]);
1816
- }
1817
- return node;
1507
+ return tsx_node_to_jsx_expression(node, in_jsx_child);
1818
1508
  }