@tsrx/solid 0.0.23 → 0.0.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.
Files changed (2) hide show
  1. package/package.json +2 -2
  2. package/src/transform.js +296 -48
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.23",
6
+ "version": "0.0.24",
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.23"
25
+ "@tsrx/core": "0.0.24"
26
26
  },
27
27
  "peerDependencies": {
28
28
  "solid-js": ">=1.8 || >=2.0.0-beta"
package/src/transform.js CHANGED
@@ -12,6 +12,7 @@ import {
12
12
  applyLazyTransforms as apply_lazy_transforms,
13
13
  collectLazyBindingsFromComponent as collect_lazy_bindings_from_component,
14
14
  replaceLazyParams as replace_lazy_params,
15
+ rewriteLoopContinuesToBareReturns as rewrite_loop_continues_to_bare_returns,
15
16
  isInterleavedBody as is_interleaved_body_core,
16
17
  isCapturableJsxChild as is_capturable_jsx_child,
17
18
  captureJsxChild,
@@ -177,31 +178,35 @@ function component_to_function_declaration(component, transform_context) {
177
178
 
178
179
  const lazy_bindings = collect_lazy_bindings_from_component(params, body, transform_context);
179
180
 
180
- // Detect top-level early-return pattern: `if (cond) { return; }`.
181
+ // Detect top-level early-return patterns such as `if (cond) { return; }`
182
+ // and `if (cond) { <p />; return; }`.
181
183
  // Solid components run their body once at setup, so an early `return` would
182
184
  // make subsequent statements and JSX permanently inert. To preserve
183
185
  // React-like "stop rendering the rest when cond becomes true" semantics,
184
- // lift JSX from after the early `if` (plus any JSX that appears before
185
- // it, since that too must disappear when cond flips) into a
186
- // `<Show when={!cond}>` whose function-children re-runs when cond changes.
186
+ // keep JSX before the guard outside and lift the guarded/continuation JSX
187
+ // into `<Show>` branches whose function-children re-run when `cond` changes.
187
188
  // Non-JSX statements on either side stay in the outer body so setup code
188
189
  // (signal creation, resource declarations, etc.) runs exactly once at
189
190
  // component setup — putting them inside the `<Show>` arrow would re-run
190
191
  // them on every toggle, creating fresh signals and losing state.
191
192
  //
192
193
  // The `if` node itself is elided: its `test` expression lives on in the
193
- // `<Show when={!cond}>` attribute and is evaluated reactively by Solid's
194
- // runtime, so any side effects or reactive reads in `cond` are preserved.
194
+ // `<Show>` attribute and is evaluated reactively by Solid's runtime, so
195
+ // any side effects or reactive reads in `cond` are preserved.
195
196
  // Non-JSX statements after the guard run unconditionally rather than being
196
197
  // gated by it; this is an intentional divergence from imperative `return`
197
198
  // semantics required by the setup-once component model.
198
- const early_idx = body.findIndex(is_early_return_if);
199
+ const early_idx = body.findIndex((node) => get_returning_if_info(node) !== null);
199
200
  /** @type {any[]} */
200
201
  let effective_body = body;
201
202
  if (early_idx !== -1) {
202
203
  const early_if = /** @type {any} */ (body[early_idx]);
204
+ const early_info = /** @type {{ consequent_body: any[], return_index: number }} */ (
205
+ get_returning_if_info(early_if)
206
+ );
203
207
  const before = body.slice(0, early_idx);
204
208
  const after = body.slice(early_idx + 1);
209
+ const branch_has_content_before_return = early_info.return_index > 0;
205
210
 
206
211
  // If mutations are interleaved with JSX children, the mutation and the
207
212
  // JSX it affects can't both be hoisted out of order — that is the same
@@ -249,13 +254,21 @@ function component_to_function_declaration(component, transform_context) {
249
254
  collect(before, before_non_jsx, before_jsx);
250
255
  collect(after, after_non_jsx, after_jsx);
251
256
 
252
- const lifted = [...before_jsx, ...after_jsx];
253
- if (lifted.length > 0) {
257
+ const next_body = [...before_non_jsx, ...before_jsx, ...after_non_jsx];
258
+
259
+ if (branch_has_content_before_return) {
260
+ transform_context.needs_show = true;
261
+ const branch_body = body_to_jsx_child(early_info.consequent_body, transform_context);
262
+ const fallback_body =
263
+ after_jsx.length > 0 ? body_to_jsx_child(after_jsx, transform_context) : null;
264
+ next_body.push(build_show_element(early_if.test, branch_body, fallback_body));
265
+ } else if (after_jsx.length > 0) {
254
266
  transform_context.needs_show = true;
255
- const show_body = body_to_jsx_child(lifted, transform_context);
256
- const show_element = build_show_element(negate_expression(early_if.test), show_body, null);
257
- effective_body = [...before_non_jsx, ...after_non_jsx, show_element];
267
+ const show_body = body_to_jsx_child(after_jsx, transform_context);
268
+ next_body.push(build_show_element(negate_expression(early_if.test), show_body, null));
258
269
  }
270
+
271
+ effective_body = next_body;
259
272
  }
260
273
 
261
274
  const statements = [];
@@ -406,8 +419,23 @@ function body_to_jsx_child(body_nodes, transform_context) {
406
419
  const statements = [];
407
420
  /** @type {any[]} */
408
421
  const children = [];
422
+ let has_bare_return = false;
409
423
  let capture_index = 0;
410
424
  for (const child of body_nodes) {
425
+ if (is_bare_return_statement(child)) {
426
+ statements.push({
427
+ type: 'ReturnStatement',
428
+ argument: children.length > 0 ? build_return_expression(children) : create_null_literal(),
429
+ metadata: { path: [] },
430
+ start: child.start,
431
+ end: child.end,
432
+ loc: child.loc,
433
+ });
434
+ children.length = 0;
435
+ has_bare_return = true;
436
+ continue;
437
+ }
438
+
411
439
  if (is_jsx_child(child)) {
412
440
  const jsx = to_jsx_child(child, transform_context);
413
441
  if (interleaved && is_capturable_jsx_child(jsx)) {
@@ -435,14 +463,16 @@ function body_to_jsx_child(body_nodes, transform_context) {
435
463
  // Branch body has non-JSX statements: wrap everything in an arrow so the
436
464
  // statements run when (and only when) the branch actually renders.
437
465
  /** @type {any[]} */
438
- const block_body = [
439
- ...statements,
440
- /** @type {any} */ ({
441
- type: 'ReturnStatement',
442
- argument: children.length > 0 ? build_return_expression(children) : create_null_literal(),
443
- metadata: { path: [] },
444
- }),
445
- ];
466
+ const block_body = [...statements];
467
+ if (children.length > 0 || !has_bare_return) {
468
+ block_body.push(
469
+ /** @type {any} */ ({
470
+ type: 'ReturnStatement',
471
+ argument: children.length > 0 ? build_return_expression(children) : create_null_literal(),
472
+ metadata: { path: [] },
473
+ }),
474
+ );
475
+ }
446
476
 
447
477
  return /** @type {any} */ ({
448
478
  type: 'ArrowFunctionExpression',
@@ -459,6 +489,206 @@ function body_to_jsx_child(body_nodes, transform_context) {
459
489
  });
460
490
  }
461
491
 
492
+ /**
493
+ * @param {any} node
494
+ * @returns {boolean}
495
+ */
496
+ function is_bare_return_statement(node) {
497
+ return node?.type === 'ReturnStatement' && node.argument == null;
498
+ }
499
+
500
+ /**
501
+ * @param {any} node
502
+ * @returns {any[]}
503
+ */
504
+ function get_if_consequent_body(node) {
505
+ return node.consequent.type === 'BlockStatement' ? node.consequent.body : [node.consequent];
506
+ }
507
+
508
+ /**
509
+ * @param {any[]} body_nodes
510
+ * @returns {boolean}
511
+ */
512
+ function body_has_loop_skip(body_nodes) {
513
+ return body_nodes.some(
514
+ (node) => is_bare_return_statement(node) || get_returning_if_info(node) !== null,
515
+ );
516
+ }
517
+
518
+ /**
519
+ * @param {any[]} body_nodes
520
+ * @param {TransformContext} transform_context
521
+ * @returns {any[]}
522
+ */
523
+ function loop_body_to_callback_statements(body_nodes, transform_context) {
524
+ /** @type {any[]} */
525
+ const statements = [];
526
+ /** @type {any[]} */
527
+ const children = [];
528
+
529
+ /**
530
+ * @param {any} source_node
531
+ * @param {any[]} render_nodes
532
+ */
533
+ const create_return_statement = (source_node, render_nodes) => {
534
+ const cloned = render_nodes.map((node) => clone_expression_node(node));
535
+ const argument = cloned.length > 0 ? build_return_expression(cloned) : create_null_literal();
536
+ return {
537
+ type: 'ReturnStatement',
538
+ argument,
539
+ metadata: { path: [] },
540
+ start: source_node?.start,
541
+ end: source_node?.end,
542
+ loc: source_node?.loc,
543
+ };
544
+ };
545
+
546
+ /** @param {any} source_node */
547
+ const flush_children_to_return = (source_node) => {
548
+ const statement = create_return_statement(source_node, children);
549
+ children.length = 0;
550
+ return statement;
551
+ };
552
+
553
+ let has_terminal_return = false;
554
+
555
+ for (const child of body_nodes) {
556
+ if (is_bare_return_statement(child)) {
557
+ statements.push(flush_children_to_return(child));
558
+ has_terminal_return = true;
559
+ break;
560
+ }
561
+
562
+ const returning_if_info = get_returning_if_info(child);
563
+ if (returning_if_info !== null) {
564
+ const branch_statements = loop_body_to_callback_statements(
565
+ returning_if_info.consequent_body,
566
+ transform_context,
567
+ );
568
+ prepend_render_nodes_to_return_statements(branch_statements, children);
569
+ statements.push({
570
+ type: 'IfStatement',
571
+ test: child.test,
572
+ consequent: {
573
+ type: 'BlockStatement',
574
+ body: branch_statements,
575
+ metadata: { path: [] },
576
+ },
577
+ alternate: null,
578
+ metadata: { path: [] },
579
+ start: child.start,
580
+ end: child.end,
581
+ loc: child.loc,
582
+ });
583
+ continue;
584
+ }
585
+
586
+ if (is_jsx_child(child)) {
587
+ children.push(to_jsx_child(child, transform_context));
588
+ } else {
589
+ statements.push(child);
590
+ }
591
+ }
592
+
593
+ if (!has_terminal_return) {
594
+ statements.push(flush_children_to_return(body_nodes.at(-1)));
595
+ }
596
+ return statements;
597
+ }
598
+
599
+ /**
600
+ * @param {any[]} statements
601
+ * @param {any[]} render_nodes
602
+ * @returns {void}
603
+ */
604
+ function prepend_render_nodes_to_return_statements(statements, render_nodes) {
605
+ if (render_nodes.length === 0) {
606
+ return;
607
+ }
608
+
609
+ for (const statement of statements) {
610
+ prepend_render_nodes_to_return_statement(statement, render_nodes, false);
611
+ }
612
+ }
613
+
614
+ /**
615
+ * @param {any} node
616
+ * @param {any[]} render_nodes
617
+ * @param {boolean} inside_nested_function
618
+ * @returns {void}
619
+ */
620
+ function prepend_render_nodes_to_return_statement(node, render_nodes, inside_nested_function) {
621
+ if (!node || typeof node !== 'object') {
622
+ return;
623
+ }
624
+
625
+ if (
626
+ node.type === 'FunctionDeclaration' ||
627
+ node.type === 'FunctionExpression' ||
628
+ node.type === 'ArrowFunctionExpression'
629
+ ) {
630
+ inside_nested_function = true;
631
+ }
632
+
633
+ if (!inside_nested_function && node.type === 'ReturnStatement') {
634
+ node.argument = combine_render_return_argument(render_nodes, node.argument);
635
+ return;
636
+ }
637
+
638
+ if (Array.isArray(node)) {
639
+ for (const child of node) {
640
+ prepend_render_nodes_to_return_statement(child, render_nodes, inside_nested_function);
641
+ }
642
+ return;
643
+ }
644
+
645
+ for (const key of Object.keys(node)) {
646
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
647
+ continue;
648
+ }
649
+ prepend_render_nodes_to_return_statement(node[key], render_nodes, inside_nested_function);
650
+ }
651
+ }
652
+
653
+ /**
654
+ * @param {any[]} render_nodes
655
+ * @param {any} return_argument
656
+ * @returns {any}
657
+ */
658
+ function combine_render_return_argument(render_nodes, return_argument) {
659
+ const combined = render_nodes.map((node) => clone_expression_node(node));
660
+
661
+ if (return_argument != null && !is_null_literal(return_argument)) {
662
+ combined.push(return_argument_to_render_node(return_argument));
663
+ }
664
+
665
+ return build_return_expression(combined) || create_null_literal();
666
+ }
667
+
668
+ /**
669
+ * @param {any} argument
670
+ * @returns {any}
671
+ */
672
+ function return_argument_to_render_node(argument) {
673
+ if (
674
+ argument?.type === 'JSXElement' ||
675
+ argument?.type === 'JSXFragment' ||
676
+ argument?.type === 'JSXExpressionContainer'
677
+ ) {
678
+ return argument;
679
+ }
680
+
681
+ return to_jsx_expression_container(argument);
682
+ }
683
+
684
+ /**
685
+ * @param {any} node
686
+ * @returns {boolean}
687
+ */
688
+ function is_null_literal(node) {
689
+ return node?.type === 'Literal' && node.value == null;
690
+ }
691
+
462
692
  /**
463
693
  * Solid-specific binding of the core `isInterleavedBody` helper with this
464
694
  * target's `is_jsx_child` predicate.
@@ -525,26 +755,34 @@ function merge_branch_body_into_arrow(outer_arrow, branch_body) {
525
755
  }
526
756
 
527
757
  /**
528
- * Detect the top-level early-return pattern `if (cond) { return; }` (or
529
- * `if (cond) return;`) with no `else` branch.
758
+ * Detect a top-level `if` branch with a bare `return` and no `else` branch.
530
759
  *
531
760
  * @param {any} node
532
- * @returns {boolean}
761
+ * @returns {{ consequent_body: any[], return_index: number } | null}
533
762
  */
534
- function is_early_return_if(node) {
535
- if (!node || node.type !== 'IfStatement' || node.alternate) return false;
763
+ function get_returning_if_info(node) {
764
+ if (!node || node.type !== 'IfStatement' || node.alternate) return null;
536
765
  const consequent = node.consequent;
537
- if (!consequent) return false;
538
- if (consequent.type === 'ReturnStatement' && !consequent.argument) return true;
539
- if (
540
- consequent.type === 'BlockStatement' &&
541
- consequent.body.length === 1 &&
542
- consequent.body[0].type === 'ReturnStatement' &&
543
- !consequent.body[0].argument
544
- ) {
545
- return true;
766
+ if (!consequent) return null;
767
+
768
+ if (is_bare_return_statement(consequent)) {
769
+ return {
770
+ consequent_body: [consequent],
771
+ return_index: 0,
772
+ };
546
773
  }
547
- return false;
774
+
775
+ if (consequent.type === 'BlockStatement') {
776
+ const return_index = consequent.body.findIndex(is_bare_return_statement);
777
+ if (return_index !== -1) {
778
+ return {
779
+ consequent_body: consequent.body,
780
+ return_index,
781
+ };
782
+ }
783
+ }
784
+
785
+ return null;
548
786
  }
549
787
 
550
788
  /**
@@ -732,22 +970,32 @@ function for_of_statement_to_jsx_child(node, transform_context) {
732
970
  transform_context.needs_for = true;
733
971
 
734
972
  const loop_params = get_for_of_iteration_params(node.left, node.index);
735
- const loop_body = node.body.type === 'BlockStatement' ? node.body.body : [node.body];
973
+ const loop_body = /** @type {any[]} */ (
974
+ rewrite_loop_continues_to_bare_returns(
975
+ node.body.type === 'BlockStatement' ? node.body.body : [node.body],
976
+ )
977
+ );
736
978
 
737
- const body_jsx = body_to_jsx_child(loop_body, transform_context);
979
+ let arrow = /** @type {any} */ ({
980
+ type: 'ArrowFunctionExpression',
981
+ params: loop_params,
982
+ body: null,
983
+ async: false,
984
+ generator: false,
985
+ expression: true,
986
+ metadata: { path: [] },
987
+ });
738
988
 
739
- const arrow = merge_branch_body_into_arrow(
740
- /** @type {any} */ ({
741
- type: 'ArrowFunctionExpression',
742
- params: loop_params,
743
- body: null,
744
- async: false,
745
- generator: false,
746
- expression: true,
989
+ if (body_has_loop_skip(loop_body)) {
990
+ arrow.body = {
991
+ type: 'BlockStatement',
992
+ body: loop_body_to_callback_statements(loop_body, transform_context),
747
993
  metadata: { path: [] },
748
- }),
749
- body_jsx,
750
- );
994
+ };
995
+ arrow.expression = false;
996
+ } else {
997
+ arrow = merge_branch_body_into_arrow(arrow, body_to_jsx_child(loop_body, transform_context));
998
+ }
751
999
 
752
1000
  const attributes = [
753
1001
  {