ripple 0.2.216 → 0.3.0

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 (155) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/package.json +16 -7
  3. package/src/compiler/errors.js +1 -1
  4. package/src/compiler/identifier-utils.js +2 -0
  5. package/src/compiler/index.d.ts +2 -6
  6. package/src/compiler/phases/1-parse/index.js +171 -233
  7. package/src/compiler/phases/2-analyze/index.js +192 -16
  8. package/src/compiler/phases/2-analyze/prune.js +2 -2
  9. package/src/compiler/phases/3-transform/client/index.js +308 -91
  10. package/src/compiler/phases/3-transform/segments.js +43 -15
  11. package/src/compiler/phases/3-transform/server/index.js +71 -21
  12. package/src/compiler/scope.js +31 -12
  13. package/src/compiler/source-map-utils.js +4 -6
  14. package/src/compiler/types/acorn.d.ts +11 -0
  15. package/src/compiler/types/estree-jsx.d.ts +11 -0
  16. package/src/compiler/types/estree.d.ts +11 -0
  17. package/src/compiler/types/import.d.ts +32 -18
  18. package/src/compiler/types/index.d.ts +75 -23
  19. package/src/compiler/types/parse.d.ts +7 -10
  20. package/src/compiler/utils.js +48 -0
  21. package/src/runtime/array.js +53 -22
  22. package/src/runtime/date.js +15 -5
  23. package/src/runtime/index-client.js +41 -7
  24. package/src/runtime/index-server.js +7 -7
  25. package/src/runtime/internal/client/bindings.js +2 -2
  26. package/src/runtime/internal/client/blocks.js +40 -1
  27. package/src/runtime/internal/client/context.js +8 -0
  28. package/src/runtime/internal/client/for.js +3 -3
  29. package/src/runtime/internal/client/index.js +32 -5
  30. package/src/runtime/internal/client/render.js +20 -8
  31. package/src/runtime/internal/client/runtime.js +9 -7
  32. package/src/runtime/internal/client/try.js +15 -22
  33. package/src/runtime/internal/client/utils.js +1 -1
  34. package/src/runtime/internal/server/context.js +8 -0
  35. package/src/runtime/internal/server/index.js +99 -6
  36. package/src/runtime/map.js +7 -7
  37. package/src/runtime/media-query.js +10 -1
  38. package/src/runtime/object.js +6 -6
  39. package/src/runtime/proxy.js +6 -6
  40. package/src/runtime/set.js +11 -11
  41. package/src/runtime/url-search-params.js +13 -2
  42. package/src/runtime/url.js +15 -5
  43. package/src/utils/builders.js +13 -3
  44. package/tests/client/array/array.copy-within.test.ripple +11 -11
  45. package/tests/client/array/array.derived.test.ripple +42 -42
  46. package/tests/client/array/array.iteration.test.ripple +12 -12
  47. package/tests/client/array/array.mutations.test.ripple +25 -25
  48. package/tests/client/array/array.static.test.ripple +103 -106
  49. package/tests/client/array/array.to-methods.test.ripple +8 -8
  50. package/tests/client/async-suspend.test.ripple +94 -0
  51. package/tests/client/basic/basic.attributes.test.ripple +31 -31
  52. package/tests/client/basic/basic.collections.test.ripple +7 -7
  53. package/tests/client/basic/basic.components.test.ripple +48 -10
  54. package/tests/client/basic/basic.errors.test.ripple +46 -31
  55. package/tests/client/basic/basic.events.test.ripple +11 -11
  56. package/tests/client/basic/basic.get-set.test.ripple +18 -18
  57. package/tests/client/basic/basic.reactivity.test.ripple +47 -42
  58. package/tests/client/basic/basic.rendering.test.ripple +7 -7
  59. package/tests/client/basic/basic.utilities.test.ripple +4 -4
  60. package/tests/client/boundaries.test.ripple +7 -7
  61. package/tests/client/compiler/__snapshots__/compiler.assignments.test.ripple.snap +2 -2
  62. package/tests/client/compiler/compiler.assignments.test.ripple +21 -21
  63. package/tests/client/compiler/compiler.basic.test.ripple +223 -82
  64. package/tests/client/compiler/compiler.tracked-access.test.ripple +8 -9
  65. package/tests/client/composite/composite.dynamic-components.test.ripple +8 -8
  66. package/tests/client/composite/composite.generics.test.ripple +4 -4
  67. package/tests/client/composite/composite.props.test.ripple +9 -9
  68. package/tests/client/composite/composite.reactivity.test.ripple +32 -26
  69. package/tests/client/composite/composite.render.test.ripple +13 -4
  70. package/tests/client/computed-properties.test.ripple +3 -3
  71. package/tests/client/context.test.ripple +3 -3
  72. package/tests/client/css/global-additional-cases.test.ripple +4 -4
  73. package/tests/client/css/style-identifier.test.ripple +49 -41
  74. package/tests/client/date.test.ripple +40 -40
  75. package/tests/client/dynamic-elements.test.ripple +165 -30
  76. package/tests/client/events.test.ripple +25 -25
  77. package/tests/client/for.test.ripple +76 -8
  78. package/tests/client/function-overload.test.ripple +0 -1
  79. package/tests/client/head.test.ripple +7 -7
  80. package/tests/client/html.test.ripple +2 -2
  81. package/tests/client/input-value.test.ripple +174 -176
  82. package/tests/client/map.test.ripple +21 -21
  83. package/tests/client/media-query.test.ripple +4 -4
  84. package/tests/client/object.test.ripple +12 -12
  85. package/tests/client/portal.test.ripple +4 -4
  86. package/tests/client/ref.test.ripple +5 -5
  87. package/tests/client/return.test.ripple +17 -17
  88. package/tests/client/set.test.ripple +16 -16
  89. package/tests/client/svg.test.ripple +6 -7
  90. package/tests/client/switch.test.ripple +10 -10
  91. package/tests/client/tracked-expression.test.ripple +1 -3
  92. package/tests/client/try.test.ripple +33 -4
  93. package/tests/client/url/url.derived.test.ripple +10 -9
  94. package/tests/client/url/url.parsing.test.ripple +10 -10
  95. package/tests/client/url/url.partial-removal.test.ripple +10 -10
  96. package/tests/client/url/url.reactivity.test.ripple +17 -17
  97. package/tests/client/url/url.serialization.test.ripple +4 -4
  98. package/tests/client/url-search-params/url-search-params.derived.test.ripple +11 -10
  99. package/tests/client/url-search-params/url-search-params.initialization.test.ripple +5 -7
  100. package/tests/client/url-search-params/url-search-params.iteration.test.ripple +13 -13
  101. package/tests/client/url-search-params/url-search-params.mutation.test.ripple +19 -19
  102. package/tests/client/url-search-params/url-search-params.retrieval.test.ripple +17 -17
  103. package/tests/client/url-search-params/url-search-params.serialization.test.ripple +5 -5
  104. package/tests/client/url-search-params/url-search-params.tracked-url.test.ripple +5 -5
  105. package/tests/hydration/compiled/client/events.js +8 -11
  106. package/tests/hydration/compiled/client/for.js +20 -23
  107. package/tests/hydration/compiled/client/head.js +17 -19
  108. package/tests/hydration/compiled/client/hmr.js +1 -3
  109. package/tests/hydration/compiled/client/html.js +1 -15
  110. package/tests/hydration/compiled/client/if-children.js +7 -9
  111. package/tests/hydration/compiled/client/if.js +5 -7
  112. package/tests/hydration/compiled/client/mixed-control-flow.js +3 -5
  113. package/tests/hydration/compiled/client/portal.js +1 -1
  114. package/tests/hydration/compiled/client/reactivity.js +9 -11
  115. package/tests/hydration/compiled/client/return.js +11 -13
  116. package/tests/hydration/compiled/client/switch.js +4 -6
  117. package/tests/hydration/compiled/server/basic.js +0 -1
  118. package/tests/hydration/compiled/server/composite.js +0 -3
  119. package/tests/hydration/compiled/server/events.js +8 -12
  120. package/tests/hydration/compiled/server/for.js +20 -23
  121. package/tests/hydration/compiled/server/head.js +17 -19
  122. package/tests/hydration/compiled/server/hmr.js +1 -4
  123. package/tests/hydration/compiled/server/html.js +1 -35
  124. package/tests/hydration/compiled/server/if-children.js +7 -11
  125. package/tests/hydration/compiled/server/if.js +5 -7
  126. package/tests/hydration/compiled/server/mixed-control-flow.js +3 -5
  127. package/tests/hydration/compiled/server/portal.js +1 -9
  128. package/tests/hydration/compiled/server/reactivity.js +9 -11
  129. package/tests/hydration/compiled/server/return.js +11 -13
  130. package/tests/hydration/compiled/server/switch.js +4 -6
  131. package/tests/hydration/components/events.ripple +8 -9
  132. package/tests/hydration/components/for.ripple +20 -21
  133. package/tests/hydration/components/head.ripple +6 -8
  134. package/tests/hydration/components/hmr.ripple +1 -2
  135. package/tests/hydration/components/html.ripple +1 -3
  136. package/tests/hydration/components/if-children.ripple +7 -8
  137. package/tests/hydration/components/if.ripple +5 -6
  138. package/tests/hydration/components/mixed-control-flow.ripple +4 -6
  139. package/tests/hydration/components/portal.ripple +1 -1
  140. package/tests/hydration/components/reactivity.ripple +9 -10
  141. package/tests/hydration/components/return.ripple +11 -12
  142. package/tests/hydration/components/switch.ripple +6 -8
  143. package/tests/server/await.test.ripple +2 -2
  144. package/tests/server/basic.attributes.test.ripple +19 -21
  145. package/tests/server/basic.components.test.ripple +13 -7
  146. package/tests/server/basic.test.ripple +20 -21
  147. package/tests/server/compiler.test.ripple +5 -5
  148. package/tests/server/composite.props.test.ripple +6 -7
  149. package/tests/server/composite.test.ripple +4 -4
  150. package/tests/server/context.test.ripple +1 -3
  151. package/tests/server/dynamic-elements.test.ripple +24 -24
  152. package/tests/server/head.test.ripple +5 -7
  153. package/tests/server/style-identifier.test.ripple +16 -17
  154. package/types/index.d.ts +266 -62
  155. package/types/server.d.ts +6 -6
@@ -239,7 +239,7 @@ const visitors = {
239
239
  if (context.path.at(-1)?.type !== 'Program') {
240
240
  // fatal since we don't have a transformation defined for this case
241
241
  error(
242
- '`#server` block can only be declared at the module level.',
242
+ '`#ripple.server` block can only be declared at the module level.',
243
243
  context.state.analysis.module.filename,
244
244
  node,
245
245
  );
@@ -303,6 +303,45 @@ const visitors = {
303
303
  }
304
304
  }
305
305
 
306
+ // Validate #ripple namespace usage
307
+ const source_name = node.metadata?.source_name;
308
+ if (typeof source_name === 'string' && source_name.startsWith('#ripple.')) {
309
+ // Cannot assign to a #ripple namespace identifier (left side)
310
+ if (
311
+ (parent?.type === 'AssignmentExpression' && parent.left === node) ||
312
+ parent?.type === 'UpdateExpression'
313
+ ) {
314
+ error(
315
+ `Cannot assign to \`${source_name}\`. The \`#ripple\` namespace is read-only.`,
316
+ context.state.analysis.module.filename,
317
+ node,
318
+ context.state.loose ? context.state.analysis.errors : undefined,
319
+ context.state.analysis.comments,
320
+ );
321
+ return context.next();
322
+ }
323
+
324
+ // Valid: callee of a CallExpression
325
+ if (parent?.type === 'CallExpression' && parent.callee === node) {
326
+ return context.next();
327
+ }
328
+
329
+ // Valid: object of a MemberExpression (further validated in MemberExpression visitor)
330
+ if (parent?.type === 'MemberExpression' && parent.object === node) {
331
+ return context.next();
332
+ }
333
+
334
+ // Everything else is an invalid bare reference
335
+ error(
336
+ `\`${source_name}\` must be called as a function, e.g., \`${source_name}(...)\`.`,
337
+ context.state.analysis.module.filename,
338
+ node,
339
+ context.state.loose ? context.state.analysis.errors : undefined,
340
+ context.state.analysis.comments,
341
+ );
342
+ return context.next();
343
+ }
344
+
306
345
  context.next();
307
346
  },
308
347
 
@@ -319,13 +358,13 @@ const visitors = {
319
358
  context.state.metadata.tracking = true;
320
359
  }
321
360
 
322
- // Track #style.className or #style['className'] references
361
+ // Track #ripple.style.className or #ripple.style['className'] references
323
362
  if (node.object.type === 'StyleIdentifier') {
324
363
  const component = is_inside_component(context, true);
325
364
 
326
365
  if (!component) {
327
366
  error(
328
- '`#style` can only be used within a component',
367
+ '`#ripple.style` can only be used within a component',
329
368
  context.state.analysis.module.filename,
330
369
  node,
331
370
  context.state.loose ? context.state.analysis.errors : undefined,
@@ -339,19 +378,19 @@ const visitors = {
339
378
  let className = null;
340
379
 
341
380
  if (!node.computed && node.property.type === 'Identifier') {
342
- // #style.test
381
+ // #ripple.style.test
343
382
  className = node.property.name;
344
383
  } else if (
345
384
  node.computed &&
346
385
  node.property.type === 'Literal' &&
347
386
  typeof node.property.value === 'string'
348
387
  ) {
349
- // #style['test']
388
+ // #ripple.style['test']
350
389
  className = node.property.value;
351
390
  } else {
352
- // #style[expression] - dynamic, not allowed
391
+ // #ripple.style[expression] - dynamic, not allowed
353
392
  error(
354
- '`#style` property access must use a dot property or static string for css class name, not a dynamic expression',
393
+ '`#ripple.style` property access must use a dot property or static string for css class name, not a dynamic expression',
355
394
  context.state.analysis.module.filename,
356
395
  node.property,
357
396
  context.state.loose ? context.state.analysis.errors : undefined,
@@ -368,10 +407,75 @@ const visitors = {
368
407
  context.state.analysis.metadata.serverIdentifierPresent = true;
369
408
  }
370
409
 
410
+ // Validate #ripple namespace member access
411
+ if (
412
+ node.object.type === 'Identifier' &&
413
+ typeof node.object.metadata?.source_name === 'string' &&
414
+ node.object.metadata.source_name.startsWith('#ripple.')
415
+ ) {
416
+ const ripple_source = node.object.metadata.source_name;
417
+ const member_parent = context.path.at(-1);
418
+
419
+ // No computed property access on #ripple namespace
420
+ if (node.computed) {
421
+ error(
422
+ `Computed property access is not allowed on \`${ripple_source}\`. Use dot notation instead.`,
423
+ context.state.analysis.module.filename,
424
+ node,
425
+ context.state.loose ? context.state.analysis.errors : undefined,
426
+ context.state.analysis.comments,
427
+ );
428
+ return context.next();
429
+ }
430
+
431
+ if (ripple_source === '#ripple.array') {
432
+ // Only .from, .of, and .fromAsync are allowed on #ripple.array
433
+ const allowed_methods = new Set(['from', 'of', 'fromAsync']);
434
+ const prop_name = node.property.type === 'Identifier' ? node.property.name : null;
435
+
436
+ if (prop_name === null || !allowed_methods.has(prop_name)) {
437
+ error(
438
+ `Only \`.from\`, \`.of\`, and \`.fromAsync\` are allowed on \`#ripple.array\`.${prop_name ? ` Got \`.${prop_name}\`.` : ''}`,
439
+ context.state.analysis.module.filename,
440
+ node.property,
441
+ context.state.loose ? context.state.analysis.errors : undefined,
442
+ context.state.analysis.comments,
443
+ );
444
+ return context.next();
445
+ }
446
+ } else {
447
+ // No member access allowed for other #ripple namespaces
448
+ error(
449
+ `Member access is not allowed on \`${ripple_source}\`. Use \`${ripple_source}(...)\` to call it directly.`,
450
+ context.state.analysis.module.filename,
451
+ node,
452
+ context.state.loose ? context.state.analysis.errors : undefined,
453
+ context.state.analysis.comments,
454
+ );
455
+ return context.next();
456
+ }
457
+
458
+ // All #ripple member expressions must be called as a function
459
+ if (!(member_parent?.type === 'CallExpression' && member_parent.callee === node)) {
460
+ const prop_name = node.property.type === 'Identifier' ? node.property.name : null;
461
+ const full_name = prop_name ? `${ripple_source}.${prop_name}` : ripple_source;
462
+ error(
463
+ `\`${full_name}\` must be called as a function, e.g., \`${full_name}(...)\`.`,
464
+ context.state.analysis.module.filename,
465
+ node,
466
+ context.state.loose ? context.state.analysis.errors : undefined,
467
+ context.state.analysis.comments,
468
+ );
469
+ return context.next();
470
+ }
471
+
472
+ return context.next();
473
+ }
474
+
371
475
  if (node.object.type === 'Identifier' && !node.object.tracked) {
372
476
  const binding = context.state.scope.get(node.object.name);
373
477
 
374
- if (binding && binding.metadata?.is_tracked_object) {
478
+ if (binding && binding.metadata?.is_ripple_object) {
375
479
  const internalProperties = new Set(['__v', 'a', 'b', 'c', 'f']);
376
480
 
377
481
  let propertyName = null;
@@ -440,6 +544,38 @@ const visitors = {
440
544
  },
441
545
 
442
546
  NewExpression(node, context) {
547
+ const callee = node.callee;
548
+
549
+ // Cannot use `new` with #ripple namespace
550
+ if (
551
+ callee.type === 'Identifier' &&
552
+ typeof callee.metadata?.source_name === 'string' &&
553
+ callee.metadata.source_name.startsWith('#ripple.')
554
+ ) {
555
+ error(
556
+ `Cannot use \`new\` with \`${callee.metadata.source_name}\`. Use \`${callee.metadata.source_name}(...)\` instead.`,
557
+ context.state.analysis.module.filename,
558
+ node,
559
+ context.state.loose ? context.state.analysis.errors : undefined,
560
+ context.state.analysis.comments,
561
+ );
562
+ }
563
+
564
+ if (
565
+ callee.type === 'MemberExpression' &&
566
+ callee.object.type === 'Identifier' &&
567
+ typeof callee.object.metadata?.source_name === 'string' &&
568
+ callee.object.metadata.source_name.startsWith('#ripple.')
569
+ ) {
570
+ error(
571
+ `Cannot use \`new\` with the \`#ripple\` namespace.`,
572
+ context.state.analysis.module.filename,
573
+ node,
574
+ context.state.loose ? context.state.analysis.errors : undefined,
575
+ context.state.analysis.comments,
576
+ );
577
+ }
578
+
443
579
  context.next();
444
580
  },
445
581
 
@@ -470,7 +606,7 @@ const visitors = {
470
606
  callee.property.type === 'Identifier' &&
471
607
  (callee.property.name === 'track' || callee.property.name === 'tracked'))
472
608
  ) {
473
- binding.metadata = { ...binding.metadata, is_tracked_object: true };
609
+ binding.metadata = { ...binding.metadata, is_ripple_object: true };
474
610
  }
475
611
  }
476
612
  visit(declarator, state);
@@ -496,6 +632,38 @@ const visitors = {
496
632
  }
497
633
  },
498
634
 
635
+ StyleIdentifier(node, context) {
636
+ const parent = context.path.at(-1);
637
+
638
+ // #ripple.style must only be used for property access (e.g., #ripple.style.className)
639
+ if (!parent || parent.type !== 'MemberExpression' || parent.object !== node) {
640
+ error(
641
+ '`#ripple.style` can only be used for property access, e.g., `#ripple.style.className`.',
642
+ context.state.analysis.module.filename,
643
+ node,
644
+ context.state.loose ? context.state.analysis.errors : undefined,
645
+ context.state.analysis.comments,
646
+ );
647
+ }
648
+ context.next();
649
+ },
650
+
651
+ ServerIdentifier(node, context) {
652
+ const parent = context.path.at(-1);
653
+
654
+ // #ripple.server must only be used for member access (e.g., #ripple.server.functionName(...))
655
+ if (!parent || parent.type !== 'MemberExpression' || parent.object !== node) {
656
+ error(
657
+ '`#ripple.server` can only be used for member access, e.g., `#ripple.server.functionName(...)`.',
658
+ context.state.analysis.module.filename,
659
+ node,
660
+ context.state.loose ? context.state.analysis.errors : undefined,
661
+ context.state.analysis.comments,
662
+ );
663
+ }
664
+ context.next();
665
+ },
666
+
499
667
  ArrowFunctionExpression(node, context) {
500
668
  visit_function(node, context);
501
669
  },
@@ -834,7 +1002,7 @@ const visitors = {
834
1002
  },
835
1003
 
836
1004
  TSTypeReference(node, context) {
837
- // bug in our acorn pasrer: it uses typeParameters instead of typeArguments
1005
+ // bug in our acorn parser: it uses typeParameters instead of typeArguments
838
1006
  // @ts-expect-error
839
1007
  if (node.typeParameters) {
840
1008
  // @ts-expect-error
@@ -1235,7 +1403,7 @@ const visitors = {
1235
1403
  attr.value.object.type === 'StyleIdentifier'
1236
1404
  ) {
1237
1405
  error(
1238
- '`#style` cannot be used directly on DOM elements. Pass the class to a child component instead.',
1406
+ '`#ripple.style` cannot be used directly on DOM elements. Pass the class to a child component instead.',
1239
1407
  state.analysis.module.filename,
1240
1408
  attr.value.object,
1241
1409
  context.state.loose ? context.state.analysis.errors : undefined,
@@ -1411,8 +1579,16 @@ const visitors = {
1411
1579
  */
1412
1580
  export function analyze(ast, filename, options = {}) {
1413
1581
  const scope_root = new ScopeRoot();
1414
-
1415
- const { scope, scopes } = create_scopes(ast, scope_root, null);
1582
+ const errors = options.errors ?? [];
1583
+ const comments = options.comments ?? [];
1584
+ const loose = options.loose ?? false;
1585
+
1586
+ const { scope, scopes } = create_scopes(ast, scope_root, null, {
1587
+ loose,
1588
+ errors,
1589
+ filename,
1590
+ comments,
1591
+ });
1416
1592
 
1417
1593
  const analysis = /** @type {AnalysisResult} */ ({
1418
1594
  module: { ast, scope, scopes, filename },
@@ -1423,8 +1599,8 @@ export function analyze(ast, filename, options = {}) {
1423
1599
  metadata: {
1424
1600
  serverIdentifierPresent: false,
1425
1601
  },
1426
- errors: options.errors ?? [],
1427
- comments: options.comments ?? [],
1602
+ errors,
1603
+ comments,
1428
1604
  });
1429
1605
 
1430
1606
  walk(
@@ -1437,7 +1613,7 @@ export function analyze(ast, filename, options = {}) {
1437
1613
  inside_head: false,
1438
1614
  ancestor_server_block: undefined,
1439
1615
  to_ts: options.to_ts ?? false,
1440
- loose: options.loose ?? false,
1616
+ loose,
1441
1617
  metadata: {},
1442
1618
  mode: options.mode,
1443
1619
  },
@@ -1078,11 +1078,11 @@ export function prune_css(css, element, styleClasses, topScopedClasses) {
1078
1078
  node.metadata.used = true;
1079
1079
  }
1080
1080
 
1081
- // Populate top_scoped_classes for truly standalone class selectors (for #style support).
1081
+ // Populate top_scoped_classes for truly standalone class selectors (for #ripple.style support).
1082
1082
  // A class is standalone only when the entire effective selector chain (after resolving
1083
1083
  // nesting and stripping :global) is a single RelativeSelector with a single ClassSelector.
1084
1084
  // This prevents classes from compound selectors like `.wrapper .nested` or selectors
1085
- // inside :global() from being treated as valid #style targets.
1085
+ // inside :global() from being treated as valid #ripple.style targets.
1086
1086
  if (selectors.length === 1) {
1087
1087
  const sole_selector = selectors[0];
1088
1088
  if (