@tsrx/solid 0.0.17 → 0.0.19

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 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.17",
6
+ "version": "0.0.19",
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.17"
25
+ "@tsrx/core": "0.0.19"
26
26
  },
27
27
  "peerDependencies": {
28
28
  "solid-js": ">=1.8 || >=2.0.0-beta"
package/src/index.js CHANGED
@@ -26,9 +26,19 @@ export function parse(source, filename, options) {
26
26
  */
27
27
  export function compile(source, filename, options) {
28
28
  const errors = /** @type {CompileError[]} */ ([]);
29
+ const comments = /** @type {AST.CommentWithLocation[]} */ ([]);
29
30
  const collect = !!options?.loose;
30
- const ast = parseModule(source, filename, collect ? { loose: true, errors } : undefined);
31
- const { ast: _ast, ...result } = transform(ast, source, filename);
31
+ const ast = parseModule(
32
+ source,
33
+ filename,
34
+ collect ? { loose: true, errors, comments } : undefined,
35
+ );
36
+ const { ast: _ast, ...result } = transform(
37
+ ast,
38
+ source,
39
+ filename,
40
+ collect ? { loose: true, errors, comments } : undefined,
41
+ );
32
42
  return { ...result, errors };
33
43
  }
34
44
 
@@ -42,8 +52,13 @@ export function compile(source, filename, options) {
42
52
  */
43
53
  export function compile_to_volar_mappings(source, filename, options) {
44
54
  const errors = /** @type {import('@tsrx/core/types').CompileError[]} */ ([]);
45
- const ast = parseModule(source, filename, { ...options, errors });
46
- const transformed = transform(ast, source, filename);
55
+ const comments = /** @type {AST.CommentWithLocation[]} */ ([]);
56
+ const ast = parseModule(source, filename, { ...options, errors, comments });
57
+ const transformed = transform(ast, source, filename, {
58
+ loose: true,
59
+ errors,
60
+ comments,
61
+ });
47
62
  const result = createVolarMappingsResult({
48
63
  ast: transformed.ast,
49
64
  ast_from_source: ast,
package/src/transform.js CHANGED
@@ -1,8 +1,13 @@
1
1
  /** @import * as AST from 'estree' */
2
2
  /** @import * as ESTreeJSX from 'estree-jsx' */
3
+ /** @import { JsxTransformContext } from '@tsrx/core/types' */
3
4
 
4
5
  import {
5
6
  createJsxTransform,
7
+ error,
8
+ mergeDuplicateRefs,
9
+ toJsxAttribute,
10
+ validateAtMostOneRefAttribute,
6
11
  setLocation,
7
12
  applyLazyTransforms as apply_lazy_transforms,
8
13
  collectLazyBindingsFromComponent as collect_lazy_bindings_from_component,
@@ -15,7 +20,6 @@ import {
15
20
  clone_expression_node,
16
21
  clone_identifier,
17
22
  clone_jsx_name,
18
- create_compile_error,
19
23
  create_generated_identifier,
20
24
  create_null_literal,
21
25
  flatten_switch_consequent,
@@ -28,15 +32,19 @@ import {
28
32
  } from '@tsrx/core';
29
33
 
30
34
  /**
31
- * @typedef {{
35
+ * Solid extends the shared `JsxTransformContext` with `needs_*` flags that
36
+ * track which Solid runtime primitives (`Show`, `For`, `Switch`, `Match`,
37
+ * `Errored`, `Loading`) the lowered output requires. The factory seeds these
38
+ * via `hooks.initialState`; everything else (filename, loose, errors,
39
+ * helper_state, …) comes from the shared base.
40
+ *
41
+ * @typedef {JsxTransformContext & {
32
42
  * needs_show: boolean,
33
43
  * needs_for: boolean,
34
44
  * needs_switch: boolean,
35
45
  * needs_match: boolean,
36
46
  * needs_errored: boolean,
37
47
  * needs_loading: boolean,
38
- * lazy_next_id: number,
39
- * current_css_hash: string | null,
40
48
  * }} TransformContext
41
49
  */
42
50
 
@@ -71,6 +79,10 @@ const solid_platform = {
71
79
  jsx: {
72
80
  rewriteClassAttr: false,
73
81
  acceptedTsxKinds: ['solid'],
82
+ // Solid's runtime accepts an array of refs natively, so multiple
83
+ // `ref` attributes collapse to `ref={[a, b, ...]}` rather than
84
+ // going through a `mergeRefs` helper.
85
+ multiRefStrategy: 'array',
74
86
  },
75
87
  validation: {
76
88
  requireUseServerForAwait: true,
@@ -88,14 +100,20 @@ const solid_platform = {
88
100
  needs_errored: false,
89
101
  needs_loading: false,
90
102
  }),
91
- validateComponentAwait: (await_expression, _component, _ctx, _requires, source) => {
103
+ validateComponentAwait: (await_expression, _component, ctx, _requires, source) => {
92
104
  const await_start = get_await_keyword_start(await_expression, source);
93
105
  const adjusted_node = /** @type {any} */ ({
94
106
  ...await_expression,
95
107
  start: await_start,
96
108
  end: await_start + 'await'.length,
97
109
  });
98
- throw create_compile_error(adjusted_node, '`await` is not allowed inside Solid components.');
110
+ error(
111
+ '`await` is not allowed inside Solid components.',
112
+ ctx?.filename ?? null,
113
+ adjusted_node,
114
+ ctx?.errors,
115
+ ctx?.comments,
116
+ );
99
117
  },
100
118
  controlFlow: {
101
119
  ifStatement: if_statement_to_jsx_child,
@@ -106,8 +124,12 @@ const solid_platform = {
106
124
  componentToFunction: (component, ctx) =>
107
125
  component_to_function_declaration(component, /** @type {any} */ (ctx)),
108
126
  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)),
127
+ // `transformElementAttributes` is intentionally omitted: the
128
+ // `transformElement` hook below short-circuits core's element walker
129
+ // before `to_jsx_element` runs, so the dispatch path that would call
130
+ // `transformElementAttributes` is never reached for Solid. Attribute
131
+ // lowering happens in Solid's local `transform_element_attributes`,
132
+ // which `to_jsx_element` and `create_dynamic_jsx_element` call directly.
111
133
  transformElement: (inner, ctx, raw_children) =>
112
134
  to_jsx_element(/** @type {any} */ (inner), /** @type {any} */ (ctx), raw_children),
113
135
  },
@@ -321,7 +343,7 @@ function to_jsx_child(node, transform_context) {
321
343
  // containers wrapped. See helpers.js.
322
344
  return tsx_node_to_jsx_expression(node, true);
323
345
  case 'TsxCompat':
324
- return tsx_compat_node_to_jsx_expression(node, true);
346
+ return tsx_compat_node_to_jsx_expression(node, transform_context, true);
325
347
  case 'Element':
326
348
  return to_jsx_element(node, transform_context);
327
349
  case 'Text':
@@ -847,17 +869,24 @@ function try_statement_to_jsx_child(node, transform_context) {
847
869
  const finalizer = node.finalizer;
848
870
 
849
871
  if (finalizer) {
850
- throw create_compile_error(
851
- finalizer,
872
+ error(
852
873
  'Solid TSRX does not support JavaScript `try/finally` in component templates. `finally` is not part of TSRX control flow; move the try/finally into a function if you need cleanup logic.',
874
+ transform_context.filename,
875
+ finalizer,
876
+ transform_context.errors,
877
+ transform_context.comments,
853
878
  );
854
879
  }
855
880
 
856
881
  if (!pending && !handler) {
857
- throw create_compile_error(
858
- node,
882
+ error(
859
883
  'Component try statements must have a `pending` or `catch` block.',
884
+ transform_context.filename,
885
+ node,
886
+ transform_context.errors,
887
+ transform_context.comments,
860
888
  );
889
+ return to_jsx_expression_container(create_null_literal());
861
890
  }
862
891
 
863
892
  const try_body_nodes = node.block.body || [];
@@ -1045,7 +1074,22 @@ function to_jsx_element(node, transform_context, pre_walk_children) {
1045
1074
  }
1046
1075
 
1047
1076
  if (!node.id) {
1048
- throw create_compile_error(node, TEMPLATE_FRAGMENT_ERROR);
1077
+ error(
1078
+ TEMPLATE_FRAGMENT_ERROR,
1079
+ transform_context.filename,
1080
+ node,
1081
+ transform_context.errors,
1082
+ transform_context.comments,
1083
+ );
1084
+ return set_loc(
1085
+ /** @type {any} */ ({
1086
+ type: 'JSXFragment',
1087
+ openingFragment: { type: 'JSXOpeningFragment' },
1088
+ closingFragment: { type: 'JSXClosingFragment' },
1089
+ children: [],
1090
+ }),
1091
+ node,
1092
+ );
1049
1093
  }
1050
1094
 
1051
1095
  if (is_dynamic_element_id(node.id)) {
@@ -1175,44 +1219,6 @@ function create_element_children(children, transform_context) {
1175
1219
  return children.map((/** @type {any} */ child) => to_jsx_child(child, transform_context));
1176
1220
  }
1177
1221
 
1178
- /**
1179
- * Attribute transform. Unlike React, Solid uses the native `class` attribute
1180
- * (not `className`). `RefAttribute` and `SpreadAttribute` nodes are handled
1181
- * at the element level by {@link transform_element_attributes} so this
1182
- * function only sees plain attributes.
1183
- *
1184
- * @param {any} attr
1185
- * @returns {any}
1186
- */
1187
- function to_jsx_attribute(attr) {
1188
- if (!attr) return attr;
1189
- if (attr.type === 'JSXAttribute' || attr.type === 'JSXSpreadAttribute') return attr;
1190
-
1191
- const attr_name = attr.name;
1192
- const name =
1193
- attr_name && attr_name.type === 'Identifier' ? identifier_to_jsx_name(attr_name) : attr_name;
1194
-
1195
- let value = attr.value;
1196
- if (value) {
1197
- if (value.type === 'Literal' && typeof value.value === 'string') {
1198
- // Keep string literal as attribute string.
1199
- } else if (value.type !== 'JSXExpressionContainer') {
1200
- value = to_jsx_expression_container(value);
1201
- }
1202
- }
1203
-
1204
- return set_loc(
1205
- /** @type {any} */ ({
1206
- type: 'JSXAttribute',
1207
- name,
1208
- value: value || null,
1209
- shorthand: false,
1210
- metadata: { path: [] },
1211
- }),
1212
- attr,
1213
- );
1214
- }
1215
-
1216
1222
  /**
1217
1223
  * Detect whether an `Element` node represents a composite component (tag
1218
1224
  * name starts with an uppercase letter, or is a member expression like
@@ -1254,26 +1260,17 @@ function has_text_content_attribute(attributes) {
1254
1260
  }
1255
1261
 
1256
1262
  /**
1257
- * Transform a list of raw attributes into JSX attributes, lifting
1258
- * `{ref expr}` handling to the element level.
1259
- *
1260
- * `{ref expr}` compiles to `ref={expr}` on both DOM elements and composite
1261
- * components. On DOM elements, Solid's JSX transform takes over: if `expr`
1262
- * is a mutable `let`-declared identifier it assigns the element to the
1263
- * variable; if `expr` is a function (or other callable) it invokes it
1264
- * with the element. On composite components, `ref` is passed through as a
1265
- * regular prop; the receiving child can consume it explicitly as
1266
- * `props.ref` or spread `{...props}` onto a DOM element, where Solid's
1267
- * spread runtime automatically applies the `ref` entry. Solid's merge
1268
- * proxies drop Symbol keys, so the Symbol-based forwarding used by
1269
- * Ripple doesn't port; the Solid target relies on its native `ref` prop
1270
- * support instead.
1263
+ * Transform a list of raw attributes into JSX attributes.
1271
1264
  *
1272
- * Multiple `{ref ...}` attributes on the same element are collected into
1273
- * a single `ref={[a, b, ...]}` array so every callback fires. Solid's
1274
- * ref/spread runtime (`applyRef`) already iterates array refs, so this
1275
- * works on both DOM elements and composite components (when the child
1276
- * spreads `props` or forwards `props.ref`).
1265
+ * Per-attribute conversion (RefAttribute → `ref={expr}`, SpreadAttribute
1266
+ * `{...expr}`, plain Attribute JSXAttribute, JSXAttribute pass-through)
1267
+ * is delegated to `@tsrx/core`'s shared {@link toJsxAttribute}. The list
1268
+ * is then run through {@link mergeDuplicateRefs} so multiple ref attributes
1269
+ * on the same element — whether from `{ref expr}` or TSX-style `ref={expr}`
1270
+ * collapse to a single `ref={[a, b, ...]}` array (the strategy chosen by
1271
+ * Solid's `multiRefStrategy: 'array'`). Solid's runtime iterates array refs
1272
+ * natively, so this works on both DOM elements and composite components
1273
+ * (when the child spreads `props` or forwards `props.ref`).
1277
1274
  *
1278
1275
  * @param {any[]} raw_attrs
1279
1276
  * @param {boolean} is_composite
@@ -1282,66 +1279,15 @@ function has_text_content_attribute(attributes) {
1282
1279
  */
1283
1280
  function transform_element_attributes(raw_attrs, is_composite, transform_context) {
1284
1281
  void is_composite;
1285
- void transform_context;
1282
+ validateAtMostOneRefAttribute(raw_attrs, /** @type {any} */ (transform_context));
1286
1283
  /** @type {any[]} */
1287
1284
  const result = [];
1288
- /** @type {any[]} */
1289
- const ref_attrs = [];
1290
1285
 
1291
1286
  for (const attr of raw_attrs) {
1292
1287
  if (!attr) continue;
1293
- if (attr.type === 'RefAttribute') {
1294
- ref_attrs.push(attr);
1295
- continue;
1296
- }
1297
- if (attr.type === 'SpreadAttribute') {
1298
- result.push(
1299
- set_loc(
1300
- /** @type {any} */ ({
1301
- type: 'JSXSpreadAttribute',
1302
- argument: attr.argument,
1303
- }),
1304
- attr,
1305
- ),
1306
- );
1307
- continue;
1308
- }
1309
- result.push(to_jsx_attribute(attr));
1310
- }
1311
-
1312
- if (ref_attrs.length === 1) {
1313
- result.push(build_ref_attribute(ref_attrs[0].argument, ref_attrs[0]));
1314
- } else if (ref_attrs.length > 1) {
1315
- const array_expr = /** @type {any} */ ({
1316
- type: 'ArrayExpression',
1317
- elements: ref_attrs.map((attr) => attr.argument),
1318
- metadata: { path: [] },
1319
- });
1320
- result.push(build_ref_attribute(array_expr, ref_attrs[0]));
1288
+ result.push(toJsxAttribute(attr, /** @type {any} */ (transform_context)));
1321
1289
  }
1322
-
1323
- return result;
1324
- }
1325
-
1326
- /**
1327
- * Build a `ref={expr}` JSX attribute, passing the expression through
1328
- * unchanged so Solid's JSX transform can apply its normal ref semantics.
1329
- *
1330
- * @param {any} argument
1331
- * @param {any} source_node
1332
- * @returns {any}
1333
- */
1334
- function build_ref_attribute(argument, source_node) {
1335
- return set_loc(
1336
- /** @type {any} */ ({
1337
- type: 'JSXAttribute',
1338
- name: { type: 'JSXIdentifier', name: 'ref', metadata: { path: [] } },
1339
- value: to_jsx_expression_container(argument),
1340
- shorthand: false,
1341
- metadata: { path: [] },
1342
- }),
1343
- source_node,
1344
- );
1290
+ return mergeDuplicateRefs(result, /** @type {any} */ (transform_context));
1345
1291
  }
1346
1292
 
1347
1293
  /**
@@ -1493,14 +1439,18 @@ function build_return_expression(render_nodes) {
1493
1439
 
1494
1440
  /**
1495
1441
  * @param {any} node
1442
+ * @param {TransformContext} transform_context
1496
1443
  * @param {boolean} [in_jsx_child]
1497
1444
  * @returns {any}
1498
1445
  */
1499
- function tsx_compat_node_to_jsx_expression(node, in_jsx_child = false) {
1446
+ function tsx_compat_node_to_jsx_expression(node, transform_context, in_jsx_child = false) {
1500
1447
  if (node.kind !== 'solid') {
1501
- throw create_compile_error(
1502
- node,
1448
+ error(
1503
1449
  `Solid TSRX does not support <tsx:${node.kind}> blocks. Use <tsx> or <tsx:solid>.`,
1450
+ transform_context.filename,
1451
+ node,
1452
+ transform_context.errors,
1453
+ transform_context.comments,
1504
1454
  );
1505
1455
  }
1506
1456
  return tsx_node_to_jsx_expression(node, in_jsx_child);