ripple 0.3.8 → 0.3.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 (79) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/package.json +2 -2
  3. package/src/compiler/phases/1-parse/index.js +38 -172
  4. package/src/compiler/phases/2-analyze/index.js +308 -115
  5. package/src/compiler/phases/2-analyze/prune.js +13 -5
  6. package/src/compiler/phases/3-transform/client/index.js +197 -213
  7. package/src/compiler/phases/3-transform/segments.js +0 -7
  8. package/src/compiler/phases/3-transform/server/index.js +77 -170
  9. package/src/compiler/types/acorn.d.ts +1 -1
  10. package/src/compiler/types/estree.d.ts +1 -1
  11. package/src/compiler/types/import.d.ts +0 -2
  12. package/src/compiler/types/index.d.ts +14 -18
  13. package/src/compiler/types/parse.d.ts +3 -9
  14. package/src/compiler/utils.js +154 -21
  15. package/src/runtime/element.js +39 -0
  16. package/src/runtime/index-client.js +2 -13
  17. package/src/runtime/index-server.js +2 -2
  18. package/src/runtime/internal/client/bindings.js +3 -1
  19. package/src/runtime/internal/client/composite.js +11 -6
  20. package/src/runtime/internal/client/events.js +1 -1
  21. package/src/runtime/internal/client/expression.js +218 -0
  22. package/src/runtime/internal/client/head.js +3 -4
  23. package/src/runtime/internal/client/index.js +4 -1
  24. package/src/runtime/internal/client/portal.js +12 -6
  25. package/src/runtime/internal/client/runtime.js +0 -52
  26. package/src/runtime/internal/server/index.js +57 -56
  27. package/tests/client/basic/basic.components.test.ripple +85 -87
  28. package/tests/client/basic/basic.errors.test.ripple +28 -4
  29. package/tests/client/basic/basic.reactivity.test.ripple +10 -155
  30. package/tests/client/basic/basic.rendering.test.ripple +23 -8
  31. package/tests/client/capture-error.js +12 -0
  32. package/tests/client/compiler/compiler.basic.test.ripple +107 -18
  33. package/tests/client/composite/composite.props.test.ripple +5 -9
  34. package/tests/client/composite/composite.reactivity.test.ripple +35 -36
  35. package/tests/client/composite/composite.render.test.ripple +45 -13
  36. package/tests/client/css/global-additional-cases.test.ripple +3 -3
  37. package/tests/client/dynamic-elements.test.ripple +3 -4
  38. package/tests/client/lazy-destructuring.test.ripple +69 -12
  39. package/tests/client/svg.test.ripple +4 -4
  40. package/tests/hydration/basic.test.js +23 -0
  41. package/tests/hydration/compiled/client/basic.js +118 -66
  42. package/tests/hydration/compiled/client/composite.js +90 -37
  43. package/tests/hydration/compiled/client/events.js +18 -18
  44. package/tests/hydration/compiled/client/for.js +62 -62
  45. package/tests/hydration/compiled/client/head.js +10 -10
  46. package/tests/hydration/compiled/client/hmr.js +13 -10
  47. package/tests/hydration/compiled/client/html.js +274 -236
  48. package/tests/hydration/compiled/client/if-children.js +41 -35
  49. package/tests/hydration/compiled/client/if.js +2 -2
  50. package/tests/hydration/compiled/client/mixed-control-flow.js +12 -12
  51. package/tests/hydration/compiled/client/nested-control-flow.js +46 -46
  52. package/tests/hydration/compiled/client/portal.js +8 -8
  53. package/tests/hydration/compiled/client/reactivity.js +14 -14
  54. package/tests/hydration/compiled/client/return.js +2 -2
  55. package/tests/hydration/compiled/client/try.js +4 -4
  56. package/tests/hydration/compiled/server/basic.js +64 -31
  57. package/tests/hydration/compiled/server/composite.js +62 -29
  58. package/tests/hydration/compiled/server/hmr.js +24 -37
  59. package/tests/hydration/compiled/server/html.js +472 -611
  60. package/tests/hydration/compiled/server/if-children.js +77 -103
  61. package/tests/hydration/compiled/server/portal.js +8 -8
  62. package/tests/hydration/components/basic.ripple +15 -5
  63. package/tests/hydration/components/composite.ripple +13 -1
  64. package/tests/hydration/components/hmr.ripple +1 -3
  65. package/tests/hydration/components/html.ripple +13 -35
  66. package/tests/hydration/components/if-children.ripple +4 -8
  67. package/tests/hydration/composite.test.js +11 -0
  68. package/tests/server/basic.attributes.test.ripple +50 -0
  69. package/tests/server/basic.components.test.ripple +22 -28
  70. package/tests/server/basic.test.ripple +12 -0
  71. package/tests/server/compiler.test.ripple +43 -4
  72. package/tests/server/composite.props.test.ripple +5 -9
  73. package/tests/server/dynamic-elements.test.ripple +3 -4
  74. package/tests/server/lazy-destructuring.test.ripple +68 -12
  75. package/tests/server/style-identifier.test.ripple +2 -4
  76. package/tsconfig.typecheck.json +4 -0
  77. package/types/index.d.ts +9 -21
  78. package/tests/client/__snapshots__/tracked-expression.test.ripple.snap +0 -34
  79. 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,
@@ -25,6 +26,7 @@ import {
25
26
  is_inside_component,
26
27
  is_ripple_track_call,
27
28
  is_void_element,
29
+ is_children_template_expression as is_children_template_expression_in_scope,
28
30
  normalize_children,
29
31
  is_binding_function,
30
32
  is_inside_try_block,
@@ -329,14 +331,185 @@ function setup_lazy_array_transforms(pattern, source_id, state, writable) {
329
331
  }
330
332
 
331
333
  /**
332
- * Checks if a function parameter has a Tracked<T> type annotation imported from ripple.
334
+ * @param {AST.Pattern} pattern
335
+ * @returns {AST.TypeNode | undefined}
336
+ */
337
+ function get_pattern_type_annotation(pattern) {
338
+ return pattern.typeAnnotation?.typeAnnotation;
339
+ }
340
+
341
+ /**
342
+ * @param {AST.TypeNode | undefined} type_annotation
343
+ * @returns {AST.TypeNode | undefined}
344
+ */
345
+ function unwrap_type_annotation(type_annotation) {
346
+ /** @type {AST.TypeNode | undefined} */
347
+ let annotation = type_annotation;
348
+
349
+ while (annotation) {
350
+ if (annotation.type === 'TSParenthesizedType') {
351
+ annotation = annotation.typeAnnotation;
352
+ continue;
353
+ }
354
+ if (annotation.type === 'TSOptionalType') {
355
+ annotation = annotation.typeAnnotation;
356
+ continue;
357
+ }
358
+ break;
359
+ }
360
+
361
+ return annotation;
362
+ }
363
+
364
+ /**
365
+ * @param {AST.TypeNode} type_annotation
366
+ * @returns {AST.TypeNode}
367
+ */
368
+ function normalize_tuple_element_type(type_annotation) {
369
+ /** @type {AST.TypeNode} */
370
+ let annotation = type_annotation;
371
+
372
+ while (true) {
373
+ if (annotation.type === 'TSNamedTupleMember') {
374
+ annotation = annotation.elementType;
375
+ continue;
376
+ }
377
+ if (annotation.type === 'TSParenthesizedType') {
378
+ annotation = annotation.typeAnnotation;
379
+ continue;
380
+ }
381
+ if (annotation.type === 'TSOptionalType') {
382
+ annotation = annotation.typeAnnotation;
383
+ continue;
384
+ }
385
+ break;
386
+ }
387
+
388
+ return annotation;
389
+ }
390
+
391
+ /**
392
+ * @param {AST.Expression} key
393
+ * @returns {string | null}
394
+ */
395
+ function get_object_pattern_key_name(key) {
396
+ if (key.type === 'Identifier') {
397
+ return key.name;
398
+ }
399
+ if (key.type === 'Literal' && (typeof key.value === 'string' || typeof key.value === 'number')) {
400
+ return String(key.value);
401
+ }
402
+ return null;
403
+ }
404
+
405
+ /**
406
+ * @param {AST.PropertyNameNonComputed} key
407
+ * @returns {string | null}
408
+ */
409
+ function get_type_property_key_name(key) {
410
+ if (key.type === 'Identifier') {
411
+ return key.name;
412
+ }
413
+ if (key.type === 'Literal' && (typeof key.value === 'string' || typeof key.value === 'number')) {
414
+ return String(key.value);
415
+ }
416
+ return null;
417
+ }
418
+
419
+ /**
420
+ * @param {AST.TypeNode | undefined} type_annotation
421
+ * @param {AST.Property | AST.RestElement} property
422
+ * @returns {AST.TypeNode | undefined}
423
+ */
424
+ function get_object_property_type_annotation(type_annotation, property) {
425
+ if (property.type === 'RestElement' || property.computed) {
426
+ return undefined;
427
+ }
428
+
429
+ const object_type_annotation = unwrap_type_annotation(type_annotation);
430
+ if (object_type_annotation?.type !== 'TSTypeLiteral') {
431
+ return undefined;
432
+ }
433
+
434
+ const key_name = get_object_pattern_key_name(property.key);
435
+ if (key_name === null) {
436
+ return undefined;
437
+ }
438
+
439
+ for (const member of object_type_annotation.members) {
440
+ if (member.type !== 'TSPropertySignature' || member.computed) {
441
+ continue;
442
+ }
443
+ const member_key_name = get_type_property_key_name(member.key);
444
+ if (member_key_name === key_name) {
445
+ return member.typeAnnotation?.typeAnnotation;
446
+ }
447
+ }
448
+
449
+ return undefined;
450
+ }
451
+
452
+ /**
453
+ * @param {AST.TypeNode | undefined} type_annotation
454
+ * @param {number} index
455
+ * @param {boolean} is_rest
456
+ * @returns {AST.TypeNode | undefined}
457
+ */
458
+ function get_array_element_type_annotation(type_annotation, index, is_rest) {
459
+ const array_type_annotation = unwrap_type_annotation(type_annotation);
460
+
461
+ if (array_type_annotation?.type === 'TSArrayType') {
462
+ return array_type_annotation.elementType;
463
+ }
464
+ if (array_type_annotation?.type !== 'TSTupleType') {
465
+ return undefined;
466
+ }
467
+
468
+ if (is_rest) {
469
+ for (let i = array_type_annotation.elementTypes.length - 1; i >= 0; i -= 1) {
470
+ const element_type = normalize_tuple_element_type(array_type_annotation.elementTypes[i]);
471
+ if (element_type.type === 'TSRestType') {
472
+ return element_type.typeAnnotation;
473
+ }
474
+ }
475
+ return undefined;
476
+ }
477
+
478
+ if (index < array_type_annotation.elementTypes.length) {
479
+ const element_type = normalize_tuple_element_type(array_type_annotation.elementTypes[index]);
480
+ if (element_type.type === 'TSRestType') {
481
+ const rest_type_annotation = unwrap_type_annotation(element_type.typeAnnotation);
482
+ return rest_type_annotation?.type === 'TSArrayType'
483
+ ? rest_type_annotation.elementType
484
+ : element_type.typeAnnotation;
485
+ }
486
+ return element_type;
487
+ }
488
+
489
+ const last_element = array_type_annotation.elementTypes.at(-1);
490
+ if (!last_element) {
491
+ return undefined;
492
+ }
493
+ const normalized_last_element = normalize_tuple_element_type(last_element);
494
+ if (normalized_last_element.type === 'TSRestType') {
495
+ const rest_type_annotation = unwrap_type_annotation(normalized_last_element.typeAnnotation);
496
+ return rest_type_annotation?.type === 'TSArrayType'
497
+ ? rest_type_annotation.elementType
498
+ : normalized_last_element.typeAnnotation;
499
+ }
500
+
501
+ return undefined;
502
+ }
503
+
504
+ /**
505
+ * Checks if a parameter source has a Tracked<T> type annotation imported from ripple.
333
506
  * This is used to determine if lazy array destructuring should use the track tuple fast path.
334
- * @param {AST.ArrayPattern} param - The parameter pattern node
507
+ * @param {AST.TypeNode | undefined} type_annotation - The source type annotation
335
508
  * @param {AnalysisContext} context - The analysis context
336
509
  * @returns {boolean}
337
510
  */
338
- function is_param_tracked_type(param, context) {
339
- const annotation = param.typeAnnotation?.typeAnnotation;
511
+ function is_param_tracked_type(type_annotation, context) {
512
+ const annotation = unwrap_type_annotation(type_annotation);
340
513
 
341
514
  if (
342
515
  annotation?.type === 'TSTypeReference' &&
@@ -357,6 +530,75 @@ function is_param_tracked_type(param, context) {
357
530
  return false;
358
531
  }
359
532
 
533
+ /**
534
+ * Sets up lazy transforms for any lazy subpatterns nested inside a function or component param.
535
+ * @param {AST.Pattern} pattern
536
+ * @param {AnalysisContext} context
537
+ * @param {AST.TypeNode | undefined} [type_annotation]
538
+ */
539
+ function setup_nested_lazy_param_transforms(pattern, context, type_annotation = undefined) {
540
+ const pattern_type_annotation = get_pattern_type_annotation(pattern) ?? type_annotation;
541
+
542
+ switch (pattern.type) {
543
+ case 'AssignmentPattern':
544
+ setup_nested_lazy_param_transforms(pattern.left, context, pattern_type_annotation);
545
+ return;
546
+
547
+ case 'RestElement':
548
+ setup_nested_lazy_param_transforms(pattern.argument, context, pattern_type_annotation);
549
+ return;
550
+
551
+ case 'ObjectPattern':
552
+ case 'ArrayPattern': {
553
+ if (pattern.lazy) {
554
+ const param_id = b.id(context.state.scope.generate('lazy'));
555
+ const is_tracked_type =
556
+ pattern.type === 'ArrayPattern' &&
557
+ is_param_tracked_type(pattern_type_annotation, context);
558
+
559
+ setup_lazy_transforms(pattern, param_id, context.state, true, is_tracked_type);
560
+ pattern.metadata = { ...pattern.metadata, lazy_id: param_id.name };
561
+ return;
562
+ }
563
+
564
+ if (pattern.type === 'ObjectPattern') {
565
+ for (const property of pattern.properties) {
566
+ const property_type_annotation = get_object_property_type_annotation(
567
+ pattern_type_annotation,
568
+ property,
569
+ );
570
+ if (property.type === 'RestElement') {
571
+ setup_nested_lazy_param_transforms(
572
+ property.argument,
573
+ context,
574
+ property_type_annotation,
575
+ );
576
+ } else {
577
+ setup_nested_lazy_param_transforms(property.value, context, property_type_annotation);
578
+ }
579
+ }
580
+ } else {
581
+ for (let i = 0; i < pattern.elements.length; i += 1) {
582
+ const element = pattern.elements[i];
583
+ if (element !== null) {
584
+ setup_nested_lazy_param_transforms(
585
+ element,
586
+ context,
587
+ get_array_element_type_annotation(
588
+ pattern_type_annotation,
589
+ i,
590
+ element.type === 'RestElement',
591
+ ),
592
+ );
593
+ }
594
+ }
595
+ }
596
+
597
+ return;
598
+ }
599
+ }
600
+ }
601
+
360
602
  /**
361
603
  * @param {AST.Function} node
362
604
  * @param {AnalysisContext} context
@@ -371,16 +613,11 @@ function visit_function(node, context) {
371
613
  for (let i = 0; i < node.params.length; i++) {
372
614
  const param_node = node.params[i];
373
615
  const param = param_node.type === 'AssignmentPattern' ? param_node.left : param_node;
616
+ const param_type_annotation =
617
+ get_pattern_type_annotation(param) ?? param_node.typeAnnotation?.typeAnnotation;
374
618
 
375
- if ((param.type === 'ObjectPattern' || param.type === 'ArrayPattern') && param.lazy) {
376
- const param_id = b.id(context.state.scope.generate('param'));
377
- // For ArrayPattern params with a Tracked<T> type annotation from ripple,
378
- // use the track tuple fast path (get/set instead of source[0]/source[1])
379
- const is_tracked_type =
380
- param.type === 'ArrayPattern' && is_param_tracked_type(param, context);
381
- setup_lazy_transforms(param, param_id, context.state, true, is_tracked_type);
382
- // Store the generated identifier name on the pattern for the transform phase
383
- param.metadata = { ...param.metadata, lazy_id: param_id.name };
619
+ if (param.type === 'ObjectPattern' || param.type === 'ArrayPattern') {
620
+ setup_nested_lazy_param_transforms(param, context, param_type_annotation);
384
621
  }
385
622
  }
386
623
 
@@ -453,74 +690,13 @@ function error_return_keyword(node, context, message) {
453
690
 
454
691
  /**
455
692
  * @param {AST.Expression} expression
456
- * @returns {AST.Expression}
457
- */
458
- function unwrap_template_expression(expression) {
459
- /** @type {AST.Expression} */
460
- let node = expression;
461
-
462
- while (true) {
463
- if (
464
- node.type === 'ParenthesizedExpression' ||
465
- node.type === 'TSAsExpression' ||
466
- node.type === 'TSSatisfiesExpression' ||
467
- node.type === 'TSNonNullExpression' ||
468
- node.type === 'TSInstantiationExpression'
469
- ) {
470
- node = /** @type {AST.Expression} */ (node.expression);
471
- continue;
472
- }
473
-
474
- if (node.type === 'ChainExpression') {
475
- node = /** @type {AST.Expression} */ (node.expression);
476
- continue;
477
- }
478
-
479
- break;
480
- }
481
-
482
- return node;
483
- }
484
-
485
- /**
486
- * @param {AST.Expression} expression
487
- * @param {AnalysisState} state
693
+ * @param {Context<AST.Node, AnalysisState>} context
488
694
  * @returns {boolean}
489
695
  */
490
- function is_children_template_expression(expression, state) {
491
- const unwrapped = unwrap_template_expression(expression);
492
-
493
- if (unwrapped.type === 'TrackedExpression') {
494
- return is_children_template_expression(
495
- /** @type {AST.Expression} */ (unwrapped.argument),
496
- state,
497
- );
498
- }
499
-
500
- if (unwrapped.type === 'MemberExpression') {
501
- let property_name = null;
502
-
503
- if (!unwrapped.computed && unwrapped.property.type === 'Identifier') {
504
- property_name = unwrapped.property.name;
505
- } else if (
506
- unwrapped.computed &&
507
- unwrapped.property.type === 'Literal' &&
508
- typeof unwrapped.property.value === 'string'
509
- ) {
510
- property_name = unwrapped.property.value;
511
- }
512
-
513
- if (property_name === 'children') {
514
- const target = unwrap_template_expression(/** @type {AST.Expression} */ (unwrapped.object));
515
-
516
- if (target.type === 'Identifier') {
517
- const binding = state.scope.get(target.name);
518
- return binding?.declaration_kind === 'param';
519
- }
520
- }
521
- }
522
-
523
- return unwrapped.type === 'Identifier' && unwrapped.name === 'children';
696
+ function is_children_template_expression(expression, context) {
697
+ const component = context.path.findLast((node) => node.type === 'Component');
698
+ const component_scope = component ? context.state.scopes.get(component) : null;
699
+ return is_children_template_expression_in_scope(expression, context.state.scope, component_scope);
524
700
  }
525
701
 
526
702
  /** @type {Visitors<AST.Node, AnalysisState>} */
@@ -632,16 +808,6 @@ const visitors = {
632
808
  MemberExpression(node, context) {
633
809
  const parent = context.path.at(-1);
634
810
 
635
- if (
636
- context.state.metadata?.tracking === false &&
637
- parent?.type !== 'AssignmentExpression' &&
638
- (node.tracked ||
639
- ((node.property.type === 'Identifier' || node.property.type === 'Literal') &&
640
- /** @type {AST.TrackedNode} */ (node.property).tracked))
641
- ) {
642
- context.state.metadata.tracking = true;
643
- }
644
-
645
811
  // Track #style.className or #style['className'] references
646
812
  if (node.object.type === 'StyleIdentifier') {
647
813
  const component = is_inside_component(context, true);
@@ -761,6 +927,19 @@ const visitors = {
761
927
 
762
928
  const callee = node.callee;
763
929
 
930
+ if (
931
+ !context.path.some((path_node) => path_node.type === 'TsxCompat') &&
932
+ is_children_template_expression(/** @type {AST.Expression} */ (callee), context)
933
+ ) {
934
+ error(
935
+ '`children` cannot be called like a regular function. Render it with `{children}` or `{props.children}` instead.',
936
+ context.state.analysis.module.filename,
937
+ callee,
938
+ context.state.loose ? context.state.analysis.errors : undefined,
939
+ context.state.analysis.comments,
940
+ );
941
+ }
942
+
764
943
  if (context.state.function_depth === 0 && is_ripple_track_call(callee, context)) {
765
944
  error(
766
945
  '`track` can only be used within a reactive context, such as a component, function or class that is used or created from a component',
@@ -913,9 +1092,13 @@ const visitors = {
913
1092
  if (node.params.length > 0) {
914
1093
  const props = node.params[0];
915
1094
 
916
- if ((props.type === 'ObjectPattern' || props.type === 'ArrayPattern') && props.lazy) {
1095
+ if (props.type === 'ObjectPattern' || props.type === 'ArrayPattern') {
917
1096
  // Lazy destructuring: &{...} or &[...] — set up lazy transforms
918
- setup_lazy_transforms(props, b.id('__props'), context.state, true, false);
1097
+ if (props.lazy) {
1098
+ setup_lazy_transforms(props, b.id('__props'), context.state, true, false);
1099
+ } else {
1100
+ setup_nested_lazy_param_transforms(props, context, get_pattern_type_annotation(props));
1101
+ }
919
1102
  } else if (props.type === 'AssignmentPattern') {
920
1103
  error(
921
1104
  'Props are always an object, use destructured props with default values instead',
@@ -1463,10 +1646,24 @@ const visitors = {
1463
1646
 
1464
1647
  const { state, visit, path } = context;
1465
1648
  const is_dom_element = is_element_dom_element(node);
1649
+ /** @type {Set<AST.Identifier>} */
1466
1650
  const attribute_names = new Set();
1467
1651
 
1468
1652
  mark_control_flow_has_template(path);
1469
1653
 
1654
+ if (
1655
+ !is_dom_element &&
1656
+ is_children_template_expression(/** @type {AST.Expression} */ (node.id), context)
1657
+ ) {
1658
+ error(
1659
+ '`children` cannot be rendered as a component. Render it with `{children}` or `{props.children}` instead.',
1660
+ state.analysis.module.filename,
1661
+ node.id,
1662
+ context.state.loose ? context.state.analysis.errors : undefined,
1663
+ context.state.analysis.comments,
1664
+ );
1665
+ }
1666
+
1470
1667
  validate_nesting(node, context);
1471
1668
 
1472
1669
  // Store capitalized name for dynamic components/elements
@@ -1520,7 +1717,10 @@ const visitors = {
1520
1717
  if (/** @type {AST.Identifier} */ (node.id).name === 'title') {
1521
1718
  const children = normalize_children(node.children, context);
1522
1719
 
1523
- if (children.length !== 1 || children[0].type !== 'Text') {
1720
+ if (
1721
+ children.length !== 1 ||
1722
+ (children[0].type !== 'RippleExpression' && children[0].type !== 'Text')
1723
+ ) {
1524
1724
  // TODO: could transform children as something, e.g. Text Node, and avoid a fatal error
1525
1725
  error(
1526
1726
  '<title> must have only contain text nodes',
@@ -1665,30 +1865,22 @@ const visitors = {
1665
1865
  }
1666
1866
  /** @type {(AST.Node | AST.Expression)[]} */
1667
1867
  let implicit_children = [];
1668
- /** @type {AST.Identifier[]} */
1669
- let explicit_children = [];
1670
1868
 
1671
1869
  for (const child of node.children) {
1672
1870
  if (child.type === 'Component') {
1673
- if (child.id?.name === 'children') {
1674
- explicit_children.push(child.id);
1675
- }
1676
- } else if (child.type !== 'EmptyStatement') {
1677
- implicit_children.push(
1678
- child.type === 'Text' || child.type === 'Html' ? child.expression : child,
1679
- );
1680
- }
1681
- }
1682
-
1683
- if (implicit_children.length > 0 && explicit_children.length > 0) {
1684
- for (const item of [...explicit_children, ...implicit_children]) {
1685
1871
  error(
1686
- 'Cannot have both implicit and explicit children',
1872
+ 'Component declarations cannot be used inside composite component children. Pass them as explicit props on the template element instead.',
1687
1873
  state.analysis.module.filename,
1688
- item,
1874
+ child.id || child,
1689
1875
  context.state.loose ? context.state.analysis.errors : undefined,
1690
1876
  context.state.analysis.comments,
1691
1877
  );
1878
+ } else if (child.type !== 'EmptyStatement') {
1879
+ implicit_children.push(
1880
+ child.type === 'RippleExpression' || child.type === 'Text' || child.type === 'Html'
1881
+ ? child.expression
1882
+ : child,
1883
+ );
1692
1884
  }
1693
1885
  }
1694
1886
  }
@@ -1715,17 +1907,18 @@ const visitors = {
1715
1907
  };
1716
1908
  },
1717
1909
 
1910
+ RippleExpression(node, context) {
1911
+ mark_control_flow_has_template(context.path);
1912
+
1913
+ context.next();
1914
+ },
1915
+
1718
1916
  Text(node, context) {
1719
1917
  mark_control_flow_has_template(context.path);
1720
1918
 
1721
- if (
1722
- is_children_template_expression(
1723
- /** @type {AST.Expression} */ (node.expression),
1724
- context.state,
1725
- )
1726
- ) {
1919
+ if (is_children_template_expression(/** @type {AST.Expression} */ (node.expression), context)) {
1727
1920
  error(
1728
- '`children` cannot be rendered using text interpolation. Use `<children />` instead.',
1921
+ '`children` cannot be rendered using explicit text interpolation. Use `{children}` or `{props.children}` instead.',
1729
1922
  context.state.analysis.module.filename,
1730
1923
  node.expression,
1731
1924
  context.state.loose ? context.state.analysis.errors : undefined,
@@ -306,12 +306,20 @@ function get_descendant_elements(node, adjacent_only) {
306
306
  }
307
307
  }
308
308
 
309
- // For template nodes and text interpolations
309
+ // For template nodes and interpolation expressions
310
310
  if (
311
- /** @type {AST.TextNode} */ (current_node).expression &&
312
- typeof (/** @type {AST.TextNode} */ (current_node).expression) === 'object'
311
+ (current_node.type === 'RippleExpression' ||
312
+ current_node.type === 'Text' ||
313
+ current_node.type === 'Html') &&
314
+ /** @type {AST.RippleExpression | AST.Html | AST.TextNode} */ (current_node).expression &&
315
+ typeof (
316
+ /** @type {AST.RippleExpression | AST.Html | AST.TextNode} */ (current_node).expression
317
+ ) === 'object'
313
318
  ) {
314
- visit(/** @type {AST.TextNode} */ (current_node).expression, depth + 1);
319
+ visit(
320
+ /** @type {AST.RippleExpression | AST.Html | AST.TextNode} */ (current_node).expression,
321
+ depth + 1,
322
+ );
315
323
  }
316
324
  }
317
325
 
@@ -409,7 +417,7 @@ function get_possible_element_siblings(node, direction, adjacent_only) {
409
417
  // Stop at non-whitespace text nodes for adjacent selectors
410
418
  else if (
411
419
  adjacent_only &&
412
- sibling.type === 'Text' &&
420
+ (sibling.type === 'RippleExpression' || sibling.type === 'Text') &&
413
421
  sibling.expression.type === 'Literal' &&
414
422
  typeof sibling.expression.value === 'string' &&
415
423
  sibling.expression.value.trim()