ripple 0.3.7 → 0.3.9

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 (119) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/package.json +2 -2
  3. package/src/compiler/phases/1-parse/index.js +48 -349
  4. package/src/compiler/phases/2-analyze/index.js +343 -52
  5. package/src/compiler/phases/3-transform/client/index.js +28 -160
  6. package/src/compiler/phases/3-transform/segments.js +0 -7
  7. package/src/compiler/phases/3-transform/server/index.js +31 -154
  8. package/src/compiler/types/acorn.d.ts +1 -1
  9. package/src/compiler/types/estree.d.ts +1 -1
  10. package/src/compiler/types/import.d.ts +0 -2
  11. package/src/compiler/types/index.d.ts +5 -17
  12. package/src/compiler/types/parse.d.ts +1 -17
  13. package/src/compiler/utils.js +53 -20
  14. package/src/runtime/index-client.js +2 -13
  15. package/src/runtime/index-server.js +2 -2
  16. package/src/runtime/internal/client/bindings.js +3 -1
  17. package/src/runtime/internal/client/composite.js +3 -2
  18. package/src/runtime/internal/client/events.js +1 -1
  19. package/src/runtime/internal/client/head.js +3 -4
  20. package/src/runtime/internal/client/index.js +0 -1
  21. package/src/runtime/internal/client/runtime.js +0 -52
  22. package/src/runtime/internal/server/index.js +31 -55
  23. package/tests/client/array/array.copy-within.test.ripple +12 -12
  24. package/tests/client/array/array.derived.test.ripple +46 -46
  25. package/tests/client/array/array.iteration.test.ripple +10 -10
  26. package/tests/client/array/array.mutations.test.ripple +20 -20
  27. package/tests/client/array/array.to-methods.test.ripple +6 -6
  28. package/tests/client/async-suspend.test.ripple +5 -5
  29. package/tests/client/basic/basic.attributes.test.ripple +81 -81
  30. package/tests/client/basic/basic.collections.test.ripple +9 -9
  31. package/tests/client/basic/basic.components.test.ripple +28 -28
  32. package/tests/client/basic/basic.errors.test.ripple +46 -18
  33. package/tests/client/basic/basic.events.test.ripple +37 -37
  34. package/tests/client/basic/basic.get-set.test.ripple +6 -6
  35. package/tests/client/basic/basic.reactivity.test.ripple +58 -203
  36. package/tests/client/basic/basic.rendering.test.ripple +19 -19
  37. package/tests/client/basic/basic.utilities.test.ripple +3 -3
  38. package/tests/client/boundaries.test.ripple +12 -12
  39. package/tests/client/compiler/__snapshots__/compiler.assignments.test.ripple.snap +5 -5
  40. package/tests/client/compiler/compiler.assignments.test.ripple +19 -19
  41. package/tests/client/compiler/compiler.basic.test.ripple +46 -27
  42. package/tests/client/compiler/compiler.tracked-access.test.ripple +2 -2
  43. package/tests/client/composite/composite.dynamic-components.test.ripple +9 -9
  44. package/tests/client/composite/composite.props.test.ripple +14 -16
  45. package/tests/client/composite/composite.reactivity.test.ripple +69 -70
  46. package/tests/client/composite/composite.render.test.ripple +3 -3
  47. package/tests/client/computed-properties.test.ripple +4 -4
  48. package/tests/client/date.test.ripple +42 -42
  49. package/tests/client/dynamic-elements.test.ripple +44 -45
  50. package/tests/client/events.test.ripple +70 -70
  51. package/tests/client/for.test.ripple +25 -25
  52. package/tests/client/head.test.ripple +19 -19
  53. package/tests/client/html.test.ripple +3 -3
  54. package/tests/client/input-value.test.ripple +84 -84
  55. package/tests/client/lazy-destructuring.test.ripple +138 -26
  56. package/tests/client/map.test.ripple +16 -16
  57. package/tests/client/media-query.test.ripple +7 -7
  58. package/tests/client/portal.test.ripple +11 -11
  59. package/tests/client/ref.test.ripple +4 -4
  60. package/tests/client/return.test.ripple +52 -52
  61. package/tests/client/set.test.ripple +6 -6
  62. package/tests/client/svg.test.ripple +5 -5
  63. package/tests/client/switch.test.ripple +44 -44
  64. package/tests/client/try.test.ripple +5 -5
  65. package/tests/client/url/url.derived.test.ripple +6 -6
  66. package/tests/client/url-search-params/url-search-params.derived.test.ripple +8 -8
  67. package/tests/client/url-search-params/url-search-params.iteration.test.ripple +10 -10
  68. package/tests/client/url-search-params/url-search-params.mutation.test.ripple +10 -10
  69. package/tests/client/url-search-params/url-search-params.retrieval.test.ripple +18 -18
  70. package/tests/client/url-search-params/url-search-params.serialization.test.ripple +2 -2
  71. package/tests/hydration/compiled/client/events.js +25 -25
  72. package/tests/hydration/compiled/client/for.js +70 -66
  73. package/tests/hydration/compiled/client/head.js +25 -25
  74. package/tests/hydration/compiled/client/hmr.js +2 -2
  75. package/tests/hydration/compiled/client/html.js +3 -3
  76. package/tests/hydration/compiled/client/if-children.js +24 -24
  77. package/tests/hydration/compiled/client/if.js +18 -18
  78. package/tests/hydration/compiled/client/mixed-control-flow.js +9 -9
  79. package/tests/hydration/compiled/client/portal.js +3 -3
  80. package/tests/hydration/compiled/client/reactivity.js +16 -16
  81. package/tests/hydration/compiled/client/return.js +40 -40
  82. package/tests/hydration/compiled/client/switch.js +12 -12
  83. package/tests/hydration/compiled/server/events.js +19 -19
  84. package/tests/hydration/compiled/server/for.js +41 -41
  85. package/tests/hydration/compiled/server/head.js +26 -26
  86. package/tests/hydration/compiled/server/hmr.js +2 -2
  87. package/tests/hydration/compiled/server/html.js +2 -2
  88. package/tests/hydration/compiled/server/if-children.js +16 -16
  89. package/tests/hydration/compiled/server/if.js +11 -11
  90. package/tests/hydration/compiled/server/mixed-control-flow.js +6 -6
  91. package/tests/hydration/compiled/server/portal.js +2 -2
  92. package/tests/hydration/compiled/server/reactivity.js +16 -16
  93. package/tests/hydration/compiled/server/return.js +25 -25
  94. package/tests/hydration/compiled/server/switch.js +8 -8
  95. package/tests/hydration/components/events.ripple +25 -25
  96. package/tests/hydration/components/for.ripple +66 -66
  97. package/tests/hydration/components/head.ripple +16 -16
  98. package/tests/hydration/components/hmr.ripple +2 -2
  99. package/tests/hydration/components/html.ripple +3 -3
  100. package/tests/hydration/components/if-children.ripple +24 -24
  101. package/tests/hydration/components/if.ripple +18 -18
  102. package/tests/hydration/components/mixed-control-flow.ripple +9 -9
  103. package/tests/hydration/components/portal.ripple +3 -3
  104. package/tests/hydration/components/reactivity.ripple +16 -16
  105. package/tests/hydration/components/return.ripple +40 -40
  106. package/tests/hydration/components/switch.ripple +20 -20
  107. package/tests/server/await.test.ripple +3 -3
  108. package/tests/server/basic.attributes.test.ripple +34 -34
  109. package/tests/server/basic.components.test.ripple +10 -10
  110. package/tests/server/basic.test.ripple +38 -40
  111. package/tests/server/compiler.test.ripple +22 -0
  112. package/tests/server/composite.props.test.ripple +12 -14
  113. package/tests/server/dynamic-elements.test.ripple +15 -15
  114. package/tests/server/head.test.ripple +11 -11
  115. package/tests/server/lazy-destructuring.test.ripple +92 -13
  116. package/tsconfig.typecheck.json +4 -0
  117. package/types/index.d.ts +0 -19
  118. package/tests/client/__snapshots__/tracked-expression.test.ripple.snap +0 -34
  119. package/tests/client/tracked-expression.test.ripple +0 -26
@@ -4,6 +4,7 @@
4
4
  AnalysisResult,
5
5
  AnalysisState,
6
6
  AnalysisContext,
7
+ Context,
7
8
  ScopeInterface,
8
9
  Visitors,
9
10
  TopScopedClasses,
@@ -328,6 +329,275 @@ function setup_lazy_array_transforms(pattern, source_id, state, writable) {
328
329
  }
329
330
  }
330
331
 
332
+ /**
333
+ * @param {AST.Pattern} pattern
334
+ * @returns {AST.TypeNode | undefined}
335
+ */
336
+ function get_pattern_type_annotation(pattern) {
337
+ return pattern.typeAnnotation?.typeAnnotation;
338
+ }
339
+
340
+ /**
341
+ * @param {AST.TypeNode | undefined} type_annotation
342
+ * @returns {AST.TypeNode | undefined}
343
+ */
344
+ function unwrap_type_annotation(type_annotation) {
345
+ /** @type {AST.TypeNode | undefined} */
346
+ let annotation = type_annotation;
347
+
348
+ while (annotation) {
349
+ if (annotation.type === 'TSParenthesizedType') {
350
+ annotation = annotation.typeAnnotation;
351
+ continue;
352
+ }
353
+ if (annotation.type === 'TSOptionalType') {
354
+ annotation = annotation.typeAnnotation;
355
+ continue;
356
+ }
357
+ break;
358
+ }
359
+
360
+ return annotation;
361
+ }
362
+
363
+ /**
364
+ * @param {AST.TypeNode} type_annotation
365
+ * @returns {AST.TypeNode}
366
+ */
367
+ function normalize_tuple_element_type(type_annotation) {
368
+ /** @type {AST.TypeNode} */
369
+ let annotation = type_annotation;
370
+
371
+ while (true) {
372
+ if (annotation.type === 'TSNamedTupleMember') {
373
+ annotation = annotation.elementType;
374
+ continue;
375
+ }
376
+ if (annotation.type === 'TSParenthesizedType') {
377
+ annotation = annotation.typeAnnotation;
378
+ continue;
379
+ }
380
+ if (annotation.type === 'TSOptionalType') {
381
+ annotation = annotation.typeAnnotation;
382
+ continue;
383
+ }
384
+ break;
385
+ }
386
+
387
+ return annotation;
388
+ }
389
+
390
+ /**
391
+ * @param {AST.Expression} key
392
+ * @returns {string | null}
393
+ */
394
+ function get_object_pattern_key_name(key) {
395
+ if (key.type === 'Identifier') {
396
+ return key.name;
397
+ }
398
+ if (key.type === 'Literal' && (typeof key.value === 'string' || typeof key.value === 'number')) {
399
+ return String(key.value);
400
+ }
401
+ return null;
402
+ }
403
+
404
+ /**
405
+ * @param {AST.PropertyNameNonComputed} key
406
+ * @returns {string | null}
407
+ */
408
+ function get_type_property_key_name(key) {
409
+ if (key.type === 'Identifier') {
410
+ return key.name;
411
+ }
412
+ if (key.type === 'Literal' && (typeof key.value === 'string' || typeof key.value === 'number')) {
413
+ return String(key.value);
414
+ }
415
+ return null;
416
+ }
417
+
418
+ /**
419
+ * @param {AST.TypeNode | undefined} type_annotation
420
+ * @param {AST.Property | AST.RestElement} property
421
+ * @returns {AST.TypeNode | undefined}
422
+ */
423
+ function get_object_property_type_annotation(type_annotation, property) {
424
+ if (property.type === 'RestElement' || property.computed) {
425
+ return undefined;
426
+ }
427
+
428
+ const object_type_annotation = unwrap_type_annotation(type_annotation);
429
+ if (object_type_annotation?.type !== 'TSTypeLiteral') {
430
+ return undefined;
431
+ }
432
+
433
+ const key_name = get_object_pattern_key_name(property.key);
434
+ if (key_name === null) {
435
+ return undefined;
436
+ }
437
+
438
+ for (const member of object_type_annotation.members) {
439
+ if (member.type !== 'TSPropertySignature' || member.computed) {
440
+ continue;
441
+ }
442
+ const member_key_name = get_type_property_key_name(member.key);
443
+ if (member_key_name === key_name) {
444
+ return member.typeAnnotation?.typeAnnotation;
445
+ }
446
+ }
447
+
448
+ return undefined;
449
+ }
450
+
451
+ /**
452
+ * @param {AST.TypeNode | undefined} type_annotation
453
+ * @param {number} index
454
+ * @param {boolean} is_rest
455
+ * @returns {AST.TypeNode | undefined}
456
+ */
457
+ function get_array_element_type_annotation(type_annotation, index, is_rest) {
458
+ const array_type_annotation = unwrap_type_annotation(type_annotation);
459
+
460
+ if (array_type_annotation?.type === 'TSArrayType') {
461
+ return array_type_annotation.elementType;
462
+ }
463
+ if (array_type_annotation?.type !== 'TSTupleType') {
464
+ return undefined;
465
+ }
466
+
467
+ if (is_rest) {
468
+ for (let i = array_type_annotation.elementTypes.length - 1; i >= 0; i -= 1) {
469
+ const element_type = normalize_tuple_element_type(array_type_annotation.elementTypes[i]);
470
+ if (element_type.type === 'TSRestType') {
471
+ return element_type.typeAnnotation;
472
+ }
473
+ }
474
+ return undefined;
475
+ }
476
+
477
+ if (index < array_type_annotation.elementTypes.length) {
478
+ const element_type = normalize_tuple_element_type(array_type_annotation.elementTypes[index]);
479
+ if (element_type.type === 'TSRestType') {
480
+ const rest_type_annotation = unwrap_type_annotation(element_type.typeAnnotation);
481
+ return rest_type_annotation?.type === 'TSArrayType'
482
+ ? rest_type_annotation.elementType
483
+ : element_type.typeAnnotation;
484
+ }
485
+ return element_type;
486
+ }
487
+
488
+ const last_element = array_type_annotation.elementTypes.at(-1);
489
+ if (!last_element) {
490
+ return undefined;
491
+ }
492
+ const normalized_last_element = normalize_tuple_element_type(last_element);
493
+ if (normalized_last_element.type === 'TSRestType') {
494
+ const rest_type_annotation = unwrap_type_annotation(normalized_last_element.typeAnnotation);
495
+ return rest_type_annotation?.type === 'TSArrayType'
496
+ ? rest_type_annotation.elementType
497
+ : normalized_last_element.typeAnnotation;
498
+ }
499
+
500
+ return undefined;
501
+ }
502
+
503
+ /**
504
+ * Checks if a parameter source has a Tracked<T> type annotation imported from ripple.
505
+ * This is used to determine if lazy array destructuring should use the track tuple fast path.
506
+ * @param {AST.TypeNode | undefined} type_annotation - The source type annotation
507
+ * @param {AnalysisContext} context - The analysis context
508
+ * @returns {boolean}
509
+ */
510
+ function is_param_tracked_type(type_annotation, context) {
511
+ const annotation = unwrap_type_annotation(type_annotation);
512
+
513
+ if (
514
+ annotation?.type === 'TSTypeReference' &&
515
+ annotation.typeName?.type === 'Identifier' &&
516
+ annotation.typeName.name === 'Tracked'
517
+ ) {
518
+ const binding = context.state.scope.get('Tracked');
519
+
520
+ return (
521
+ binding?.declaration_kind === 'import' &&
522
+ binding.initial !== null &&
523
+ binding.initial.type === 'ImportDeclaration' &&
524
+ binding.initial.source.type === 'Literal' &&
525
+ binding.initial.source.value === 'ripple'
526
+ );
527
+ }
528
+
529
+ return false;
530
+ }
531
+
532
+ /**
533
+ * Sets up lazy transforms for any lazy subpatterns nested inside a function or component param.
534
+ * @param {AST.Pattern} pattern
535
+ * @param {AnalysisContext} context
536
+ * @param {AST.TypeNode | undefined} [type_annotation]
537
+ */
538
+ function setup_nested_lazy_param_transforms(pattern, context, type_annotation = undefined) {
539
+ const pattern_type_annotation = get_pattern_type_annotation(pattern) ?? type_annotation;
540
+
541
+ switch (pattern.type) {
542
+ case 'AssignmentPattern':
543
+ setup_nested_lazy_param_transforms(pattern.left, context, pattern_type_annotation);
544
+ return;
545
+
546
+ case 'RestElement':
547
+ setup_nested_lazy_param_transforms(pattern.argument, context, pattern_type_annotation);
548
+ return;
549
+
550
+ case 'ObjectPattern':
551
+ case 'ArrayPattern': {
552
+ if (pattern.lazy) {
553
+ const param_id = b.id(context.state.scope.generate('lazy'));
554
+ const is_tracked_type =
555
+ pattern.type === 'ArrayPattern' &&
556
+ is_param_tracked_type(pattern_type_annotation, context);
557
+
558
+ setup_lazy_transforms(pattern, param_id, context.state, true, is_tracked_type);
559
+ pattern.metadata = { ...pattern.metadata, lazy_id: param_id.name };
560
+ return;
561
+ }
562
+
563
+ if (pattern.type === 'ObjectPattern') {
564
+ for (const property of pattern.properties) {
565
+ const property_type_annotation = get_object_property_type_annotation(
566
+ pattern_type_annotation,
567
+ property,
568
+ );
569
+ if (property.type === 'RestElement') {
570
+ setup_nested_lazy_param_transforms(
571
+ property.argument,
572
+ context,
573
+ property_type_annotation,
574
+ );
575
+ } else {
576
+ setup_nested_lazy_param_transforms(property.value, context, property_type_annotation);
577
+ }
578
+ }
579
+ } else {
580
+ for (let i = 0; i < pattern.elements.length; i += 1) {
581
+ const element = pattern.elements[i];
582
+ if (element !== null) {
583
+ setup_nested_lazy_param_transforms(
584
+ element,
585
+ context,
586
+ get_array_element_type_annotation(
587
+ pattern_type_annotation,
588
+ i,
589
+ element.type === 'RestElement',
590
+ ),
591
+ );
592
+ }
593
+ }
594
+ }
595
+
596
+ return;
597
+ }
598
+ }
599
+ }
600
+
331
601
  /**
332
602
  * @param {AST.Function} node
333
603
  * @param {AnalysisContext} context
@@ -342,12 +612,11 @@ function visit_function(node, context) {
342
612
  for (let i = 0; i < node.params.length; i++) {
343
613
  const param_node = node.params[i];
344
614
  const param = param_node.type === 'AssignmentPattern' ? param_node.left : param_node;
615
+ const param_type_annotation =
616
+ get_pattern_type_annotation(param) ?? param_node.typeAnnotation?.typeAnnotation;
345
617
 
346
- if ((param.type === 'ObjectPattern' || param.type === 'ArrayPattern') && param.lazy) {
347
- const param_id = b.id(context.state.scope.generate('param'));
348
- setup_lazy_transforms(param, param_id, context.state, true, false);
349
- // Store the generated identifier name on the pattern for the transform phase
350
- param.metadata = { ...param.metadata, lazy_id: param_id.name };
618
+ if (param.type === 'ObjectPattern' || param.type === 'ArrayPattern') {
619
+ setup_nested_lazy_param_transforms(param, context, param_type_annotation);
351
620
  }
352
621
  }
353
622
 
@@ -451,19 +720,14 @@ function unwrap_template_expression(expression) {
451
720
 
452
721
  /**
453
722
  * @param {AST.Expression} expression
454
- * @param {AnalysisState} state
723
+ * @param {Context<AST.Node, AnalysisState>} context
455
724
  * @returns {boolean}
456
725
  */
457
- function is_children_template_expression(expression, state) {
726
+ function is_children_template_expression(expression, context) {
727
+ const component = context.path.findLast((node) => node.type === 'Component');
728
+ const component_scope = component ? context.state.scopes.get(component) : null;
458
729
  const unwrapped = unwrap_template_expression(expression);
459
730
 
460
- if (unwrapped.type === 'TrackedExpression') {
461
- return is_children_template_expression(
462
- /** @type {AST.Expression} */ (unwrapped.argument),
463
- state,
464
- );
465
- }
466
-
467
731
  if (unwrapped.type === 'MemberExpression') {
468
732
  let property_name = null;
469
733
 
@@ -481,13 +745,25 @@ function is_children_template_expression(expression, state) {
481
745
  const target = unwrap_template_expression(/** @type {AST.Expression} */ (unwrapped.object));
482
746
 
483
747
  if (target.type === 'Identifier') {
484
- const binding = state.scope.get(target.name);
485
- return binding?.declaration_kind === 'param';
748
+ const binding = context.state.scope.get(target.name);
749
+ return binding?.declaration_kind === 'param' && binding.scope === component_scope;
486
750
  }
487
751
  }
488
752
  }
489
753
 
490
- return unwrapped.type === 'Identifier' && unwrapped.name === 'children';
754
+ if (unwrapped.type !== 'Identifier' || unwrapped.name !== 'children') {
755
+ return false;
756
+ }
757
+
758
+ const binding = context.state.scope.get(unwrapped.name);
759
+ return (
760
+ (binding?.declaration_kind === 'param' ||
761
+ binding?.kind === 'prop' ||
762
+ binding?.kind === 'prop_fallback' ||
763
+ binding?.kind === 'lazy' ||
764
+ binding?.kind === 'lazy_fallback') &&
765
+ binding.scope === component_scope
766
+ );
491
767
  }
492
768
 
493
769
  /** @type {Visitors<AST.Node, AnalysisState>} */
@@ -579,7 +855,7 @@ const visitors = {
579
855
  }
580
856
 
581
857
  // Lazy bindings from track() calls (read_unwraps) are inherently reactive —
582
- // propagate tracking even without the @ prefix so that control flow (if/for/switch)
858
+ // propagate tracking so that control flow (if/for/switch)
583
859
  // and early returns create reactive blocks
584
860
  if (
585
861
  !node.tracked &&
@@ -599,16 +875,6 @@ const visitors = {
599
875
  MemberExpression(node, context) {
600
876
  const parent = context.path.at(-1);
601
877
 
602
- if (
603
- context.state.metadata?.tracking === false &&
604
- parent?.type !== 'AssignmentExpression' &&
605
- (node.tracked ||
606
- ((node.property.type === 'Identifier' || node.property.type === 'Literal') &&
607
- /** @type {AST.TrackedNode} */ (node.property).tracked))
608
- ) {
609
- context.state.metadata.tracking = true;
610
- }
611
-
612
878
  // Track #style.className or #style['className'] references
613
879
  if (node.object.type === 'StyleIdentifier') {
614
880
  const component = is_inside_component(context, true);
@@ -673,7 +939,7 @@ const visitors = {
673
939
 
674
940
  if (propertyName && internalProperties.has(propertyName)) {
675
941
  error(
676
- `Directly accessing internal property "${propertyName}" of a tracked object is not allowed. Use \`get(${node.object.name})\` or \`@${node.object.name}\` instead.`,
942
+ `Directly accessing internal property "${propertyName}" of a tracked object is not allowed. Use \`${node.object.name}.value\` or \`&[]\` lazy destructuring instead.`,
677
943
  context.state.analysis.module.filename,
678
944
  node.property,
679
945
  context.state.loose ? context.state.analysis.errors : undefined,
@@ -684,6 +950,8 @@ const visitors = {
684
950
 
685
951
  if (
686
952
  binding !== null &&
953
+ binding.kind !== 'lazy' &&
954
+ binding.kind !== 'lazy_fallback' &&
687
955
  binding.initial?.type === 'CallExpression' &&
688
956
  is_ripple_track_call(binding.initial.callee, context)
689
957
  ) {
@@ -701,7 +969,7 @@ const visitors = {
701
969
  // pass through
702
970
  } else {
703
971
  error(
704
- `Accessing a tracked object directly is not allowed, use the \`@\` prefix to read the value inside a tracked object - for example \`@${node.object.name}${node.property.type === 'Identifier' ? `.${node.property.name}` : ''}\``,
972
+ `Accessing a tracked object directly is not allowed, use \`.value\` or \`&[]\` lazy destructuring to read the value inside a tracked object - for example \`${node.object.name}.value\``,
705
973
  context.state.analysis.module.filename,
706
974
  node.object,
707
975
  context.state.loose ? context.state.analysis.errors : undefined,
@@ -726,6 +994,19 @@ const visitors = {
726
994
 
727
995
  const callee = node.callee;
728
996
 
997
+ if (
998
+ !context.path.some((path_node) => path_node.type === 'TsxCompat') &&
999
+ is_children_template_expression(/** @type {AST.Expression} */ (callee), context)
1000
+ ) {
1001
+ error(
1002
+ '`children` cannot be called like a regular function. Use element syntax instead, e.g. `<children />` or `<props.children />`.',
1003
+ context.state.analysis.module.filename,
1004
+ callee,
1005
+ context.state.loose ? context.state.analysis.errors : undefined,
1006
+ context.state.analysis.comments,
1007
+ );
1008
+ }
1009
+
729
1010
  if (context.state.function_depth === 0 && is_ripple_track_call(callee, context)) {
730
1011
  error(
731
1012
  '`track` can only be used within a reactive context, such as a component, function or class that is used or created from a component',
@@ -794,20 +1075,6 @@ const visitors = {
794
1075
  declarator.id.metadata = { ...declarator.id.metadata, lazy_id: lazy_id.name };
795
1076
  }
796
1077
 
797
- const paths = extract_paths(declarator.id);
798
-
799
- for (const path of paths) {
800
- if (path.node.tracked) {
801
- error(
802
- 'Variables cannot be reactively referenced using @',
803
- state.analysis.module.filename,
804
- path.node,
805
- context.state.loose ? context.state.analysis.errors : undefined,
806
- context.state.analysis.comments,
807
- );
808
- }
809
- }
810
-
811
1078
  visit(declarator, state);
812
1079
  }
813
1080
 
@@ -815,6 +1082,30 @@ const visitors = {
815
1082
  }
816
1083
  },
817
1084
 
1085
+ ExpressionStatement(node, context) {
1086
+ const { state, visit } = context;
1087
+
1088
+ // Handle standalone lazy destructuring assignment: &[data] = track(0);
1089
+ if (
1090
+ node.expression.type === 'AssignmentExpression' &&
1091
+ node.expression.operator === '=' &&
1092
+ (node.expression.left.type === 'ObjectPattern' ||
1093
+ node.expression.left.type === 'ArrayPattern') &&
1094
+ node.expression.left.lazy
1095
+ ) {
1096
+ const pattern = /** @type {AST.ObjectPattern | AST.ArrayPattern} */ (node.expression.left);
1097
+ const lazy_id = b.id(state.scope.generate('lazy'));
1098
+ const init = /** @type {AST.Expression} */ (node.expression.right);
1099
+ const init_is_track =
1100
+ init?.type === 'CallExpression' && is_ripple_track_call(init.callee, context) === 'track';
1101
+ setup_lazy_transforms(pattern, lazy_id, state, true, !!init_is_track);
1102
+ // Store the generated identifier name on the pattern for the transform phase
1103
+ pattern.metadata = { ...pattern.metadata, lazy_id: lazy_id.name };
1104
+ }
1105
+
1106
+ context.next();
1107
+ },
1108
+
818
1109
  StyleIdentifier(node, context) {
819
1110
  const component = is_inside_component(context, true);
820
1111
  const parent = context.path.at(-1);
@@ -868,9 +1159,13 @@ const visitors = {
868
1159
  if (node.params.length > 0) {
869
1160
  const props = node.params[0];
870
1161
 
871
- if ((props.type === 'ObjectPattern' || props.type === 'ArrayPattern') && props.lazy) {
1162
+ if (props.type === 'ObjectPattern' || props.type === 'ArrayPattern') {
872
1163
  // Lazy destructuring: &{...} or &[...] — set up lazy transforms
873
- setup_lazy_transforms(props, b.id('__props'), context.state, true, false);
1164
+ if (props.lazy) {
1165
+ setup_lazy_transforms(props, b.id('__props'), context.state, true, false);
1166
+ } else {
1167
+ setup_nested_lazy_param_transforms(props, context, get_pattern_type_annotation(props));
1168
+ }
874
1169
  } else if (props.type === 'AssignmentPattern') {
875
1170
  error(
876
1171
  'Props are always an object, use destructured props with default values instead',
@@ -1418,6 +1713,7 @@ const visitors = {
1418
1713
 
1419
1714
  const { state, visit, path } = context;
1420
1715
  const is_dom_element = is_element_dom_element(node);
1716
+ /** @type {Set<AST.Identifier>} */
1421
1717
  const attribute_names = new Set();
1422
1718
 
1423
1719
  mark_control_flow_has_template(path);
@@ -1673,12 +1969,7 @@ const visitors = {
1673
1969
  Text(node, context) {
1674
1970
  mark_control_flow_has_template(context.path);
1675
1971
 
1676
- if (
1677
- is_children_template_expression(
1678
- /** @type {AST.Expression} */ (node.expression),
1679
- context.state,
1680
- )
1681
- ) {
1972
+ if (is_children_template_expression(/** @type {AST.Expression} */ (node.expression), context)) {
1682
1973
  error(
1683
1974
  '`children` cannot be rendered using text interpolation. Use `<children />` instead.',
1684
1975
  context.state.analysis.module.filename,