ripple 0.2.199 → 0.2.201

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 (55) hide show
  1. package/package.json +5 -4
  2. package/src/compiler/index.d.ts +1 -5
  3. package/src/compiler/phases/1-parse/index.js +145 -11
  4. package/src/compiler/phases/2-analyze/index.js +24 -8
  5. package/src/compiler/phases/2-analyze/prune.js +5 -3
  6. package/src/compiler/phases/3-transform/client/index.js +312 -165
  7. package/src/compiler/phases/3-transform/segments.js +220 -70
  8. package/src/compiler/phases/3-transform/server/index.js +227 -77
  9. package/src/compiler/source-map-utils.js +74 -10
  10. package/src/compiler/types/index.d.ts +63 -21
  11. package/src/compiler/types/parse.d.ts +3 -1
  12. package/src/compiler/utils.js +34 -0
  13. package/src/helpers.d.ts +5 -0
  14. package/src/runtime/index-server.js +27 -47
  15. package/src/runtime/internal/client/composite.js +5 -0
  16. package/src/runtime/internal/client/events.js +1 -9
  17. package/src/runtime/internal/client/for.js +6 -4
  18. package/src/runtime/internal/client/hydration.js +2 -2
  19. package/src/runtime/internal/client/index.js +1 -1
  20. package/src/runtime/internal/client/operations.js +4 -4
  21. package/src/runtime/internal/client/render.js +0 -2
  22. package/src/runtime/internal/client/template.js +9 -1
  23. package/src/runtime/internal/client/types.d.ts +18 -0
  24. package/src/runtime/internal/client/utils.js +1 -1
  25. package/src/runtime/internal/server/index.js +106 -3
  26. package/src/utils/builders.js +25 -5
  27. package/tests/client/basic/basic.attributes.test.ripple +1 -1
  28. package/tests/client/basic/basic.components.test.ripple +47 -0
  29. package/tests/client/basic/basic.rendering.test.ripple +1 -1
  30. package/tests/client/composite/composite.props.test.ripple +49 -4
  31. package/tests/client/dynamic-elements.test.ripple +44 -0
  32. package/tests/client/switch.test.ripple +40 -0
  33. package/tests/client/tsconfig.json +11 -0
  34. package/tests/client.d.ts +5 -22
  35. package/tests/common.d.ts +24 -0
  36. package/tests/hydration/compiled/server/basic.js +109 -24
  37. package/tests/hydration/compiled/server/events.js +161 -72
  38. package/tests/hydration/compiled/server/for.js +202 -102
  39. package/tests/hydration/compiled/server/if.js +130 -50
  40. package/tests/hydration/compiled/server/reactivity.js +51 -12
  41. package/tests/server/__snapshots__/compiler.test.ripple.snap +11 -4
  42. package/tests/server/basic.attributes.test.ripple +459 -0
  43. package/tests/server/basic.components.test.ripple +237 -0
  44. package/tests/server/basic.test.ripple +25 -0
  45. package/tests/server/compiler.test.ripple +2 -3
  46. package/tests/server/composite.props.test.ripple +161 -0
  47. package/tests/server/dynamic-elements.test.ripple +438 -0
  48. package/tests/server/head.test.ripple +102 -0
  49. package/tests/server/switch.test.ripple +40 -0
  50. package/tests/server/tsconfig.json +11 -0
  51. package/tests/server.d.ts +7 -0
  52. package/tests/setup-client.js +6 -2
  53. package/tests/setup-server.js +16 -0
  54. package/types/index.d.ts +2 -2
  55. package/types/server.d.ts +4 -3
@@ -8,9 +8,11 @@
8
8
  VisitorClientContext,
9
9
  TransformClientState,
10
10
  ScopeInterface,
11
- Visitors
11
+ Visitors,
12
+ Binding,
12
13
  } from '#compiler';
13
14
  @import { RippleCompileError } from 'ripple/compiler';
15
+ @import { RequiredPresent } from '#helpers';
14
16
  */
15
17
 
16
18
  /**
@@ -51,6 +53,7 @@ import {
51
53
  build_getter,
52
54
  determine_namespace_for_children,
53
55
  index_to_key,
56
+ is_element_dynamic,
54
57
  } from '../../../utils.js';
55
58
  import {
56
59
  CSS_HASH_IDENTIFIER,
@@ -68,6 +71,7 @@ import {
68
71
  } from '../../../../utils/events.js';
69
72
  import { createHash } from 'node:crypto';
70
73
  import { should_preserve_comment, format_comment } from '../../../comment-utils.js';
74
+ import { set_location } from '../../../../utils/builders.js';
71
75
 
72
76
  /**
73
77
  *
@@ -175,8 +179,8 @@ function visit_head_element(node, context) {
175
179
  * @param {TransformClientState} state
176
180
  */
177
181
  function apply_updates(init, update, state) {
178
- if (update?.length === 1 && !update[0].needsPrevTracking) {
179
- init?.push(
182
+ if (update.length === 1 && !update[0].needsPrevTracking) {
183
+ init.push(
180
184
  b.stmt(
181
185
  b.call(
182
186
  '_$_.render',
@@ -195,18 +199,32 @@ function apply_updates(init, update, state) {
195
199
  ),
196
200
  );
197
201
  } else {
202
+ /** @type {AST.Property[]} */
198
203
  const initial = [];
204
+ /** @type {AST.Statement[]} */
199
205
  const render_statements = [];
200
206
  let index = 0;
201
207
 
208
+ /**
209
+ @type {
210
+ Map<
211
+ AST.Identifier | AST.Expression,
212
+ RequiredPresent<
213
+ NonNullable<TransformClientState['update']>[number],
214
+ 'initial' | 'identity' | 'expression'
215
+ >[]
216
+ >
217
+ }
218
+ */
202
219
  const grouped_updates = new Map();
203
220
 
204
221
  for (const u of update) {
205
222
  if (u.initial) {
206
- const id =
207
- u.identity?.type === 'Identifier'
208
- ? state.scope.get(u.identity.name)?.initial
209
- : u.identity;
223
+ const id = /** @type {AST.Identifier | AST.Expression} */ (
224
+ u.identity.type === 'Identifier'
225
+ ? /** @type {Binding} */ (state.scope.get(u.identity.name)).node
226
+ : u.identity
227
+ );
210
228
  let updates = grouped_updates.get(id);
211
229
 
212
230
  if (updates === undefined) {
@@ -715,7 +733,11 @@ const visitors = {
715
733
  context.state.metadata.tracking = true;
716
734
  }
717
735
 
718
- if (node.tracked || (node.property.type === 'Identifier' && node.property.tracked)) {
736
+ if (
737
+ node.tracked ||
738
+ ((node.property.type === 'Identifier' || node.property.type === 'Literal') &&
739
+ node.property.tracked)
740
+ ) {
719
741
  if (context.state.to_ts) {
720
742
  // In TypeScript mode, transform @user.@name or @user.@['name'] or @user?.@name
721
743
  // to user['#v'].name['#v'] or user['#v']['name']['#v'] or user['#v']?.name['#v']
@@ -723,10 +745,22 @@ const visitors = {
723
745
  const visited_property = /** @type {AST.Expression} */ (context.visit(node.property));
724
746
 
725
747
  // Build the member access: object.property or object[property]
726
- const member = b.member(visited_object, visited_property, node.computed, node.optional);
748
+ const member = b.member(
749
+ visited_object,
750
+ visited_property,
751
+ node.computed,
752
+ node.optional,
753
+ /** @type {AST.NodeWithLocation} */ (node),
754
+ );
727
755
 
728
756
  // Wrap with ['#v'] access
729
- return b.member(member, b.literal('#v'), true);
757
+ return b.member(
758
+ member,
759
+ b.literal('#v'),
760
+ true,
761
+ undefined,
762
+ /** @type {AST.NodeWithLocation} */ (node),
763
+ );
730
764
  } else {
731
765
  if (!context.state.to_ts) {
732
766
  return b.call(
@@ -1081,7 +1115,10 @@ const visitors = {
1081
1115
  /** @type {(AST.Property | AST.SpreadElement)[] | null} */
1082
1116
  const spread_attributes = is_spreading ? [] : null;
1083
1117
  const child_namespace = is_dom_element
1084
- ? determine_namespace_for_children(node.id.name, state.namespace)
1118
+ ? determine_namespace_for_children(
1119
+ /** @type {AST.Identifier} */ (node.id).name,
1120
+ state.namespace,
1121
+ )
1085
1122
  : state.namespace;
1086
1123
 
1087
1124
  /**
@@ -1110,37 +1147,21 @@ const visitors = {
1110
1147
  };
1111
1148
 
1112
1149
  if (is_dom_element) {
1150
+ /** @type {AST.Attribute | null} */
1113
1151
  let class_attribute = null;
1152
+ /** @type {AST.Attribute | null} */
1114
1153
  let style_attribute = null;
1115
- const component = /** @type {AST.Component} */ (state.component);
1116
1154
  /** @type {TransformClientState['update']} */
1117
1155
  const local_updates = [];
1118
- const is_void = is_void_element(node.id.name);
1119
-
1120
- let scoping_hash = null;
1121
- if (node.metadata?.scoped && component.css) {
1122
- scoping_hash = component.css.hash;
1123
- } else {
1124
- let inside_dynamic_children = false;
1125
- for (let i = context.path.length - 1; i >= 0; i--) {
1126
- const anc = context.path[i];
1127
- if (anc && anc.type === 'Component' && anc.metadata && anc.metadata.inherited_css) {
1128
- inside_dynamic_children = true;
1129
- break;
1130
- }
1131
- }
1132
- if (inside_dynamic_children) {
1133
- for (let i = context.path.length - 1; i >= 0; i--) {
1134
- const anc = context.path[i];
1135
- if (anc && anc.type === 'Component' && anc.css) {
1136
- scoping_hash = anc.css.hash;
1137
- break;
1138
- }
1139
- }
1140
- }
1141
- }
1156
+ const is_void = is_void_element(/** @type {AST.Identifier} */ (node.id).name);
1157
+ /** @type {AST.CSS.StyleSheet['hash'] | null} */
1158
+ const scoping_hash =
1159
+ state.applyParentCssScope ??
1160
+ (node.metadata.scoped && state.component?.css
1161
+ ? /** @type {AST.CSS.StyleSheet} */ (state.component?.css).hash
1162
+ : null);
1142
1163
 
1143
- state.template?.push(`<${node.id.name}`);
1164
+ state.template?.push(`<${/** @type {AST.Identifier} */ (node.id).name}`);
1144
1165
 
1145
1166
  for (const attr of node.attributes) {
1146
1167
  if (attr.type === 'Attribute') {
@@ -1353,7 +1374,9 @@ const visitors = {
1353
1374
  );
1354
1375
 
1355
1376
  const hash_arg = scoping_hash ? b.literal(scoping_hash) : undefined;
1356
- const is_html = context.state.namespace === 'html' && node.id.name !== 'svg';
1377
+ const is_html =
1378
+ context.state.namespace === 'html' &&
1379
+ /** @type {AST.Identifier} */ (node.id).name !== 'svg';
1357
1380
 
1358
1381
  if (metadata.tracking) {
1359
1382
  local_updates.push({
@@ -1430,7 +1453,7 @@ const visitors = {
1430
1453
  root: false,
1431
1454
  }),
1432
1455
  );
1433
- state.template?.push(`</${node.id.name}>`);
1456
+ state.template?.push(`</${/** @type {AST.Identifier} */ (node.id).name}>`);
1434
1457
 
1435
1458
  // We need to check if any child nodes are dynamic to determine
1436
1459
  // if we need to pop the hydration stack to the parent node
@@ -1472,6 +1495,12 @@ const visitors = {
1472
1495
 
1473
1496
  state.template?.push('<!>');
1474
1497
 
1498
+ if (state.applyParentCssScope) {
1499
+ // We're inside a component, don't continue applying css hash to class
1500
+ state.applyParentCssScope = undefined;
1501
+ }
1502
+
1503
+ const is_dynamic_element = is_element_dynamic(node);
1475
1504
  const is_spreading = node.attributes.some((attr) => attr.type === 'SpreadAttribute');
1476
1505
  /** @type {(AST.Property | AST.SpreadElement)[]} */
1477
1506
  const props = [];
@@ -1487,7 +1516,7 @@ const visitors = {
1487
1516
  ? b.literal(true)
1488
1517
  : /** @type {AST.Expression} */ (visit(attr.value, { ...state, metadata }));
1489
1518
 
1490
- if (attr.name.name === 'class' && node.metadata?.scoped && state.component?.css) {
1519
+ if (attr.name.name === 'class' && node.metadata.scoped && state.component?.css) {
1491
1520
  if (property.type === 'Literal') {
1492
1521
  property = b.literal(`${state.component.css.hash} ${property.value}`);
1493
1522
  } else {
@@ -1543,7 +1572,7 @@ const visitors = {
1543
1572
  }
1544
1573
  }
1545
1574
 
1546
- if (node.metadata?.scoped && state.component?.css) {
1575
+ if (node.metadata.scoped && state.component?.css) {
1547
1576
  const hasClassAttr = node.attributes.some(
1548
1577
  (attr) =>
1549
1578
  attr.type === 'Attribute' &&
@@ -1579,17 +1608,19 @@ const visitors = {
1579
1608
  }
1580
1609
 
1581
1610
  if (children_filtered.length > 0) {
1582
- const component_scope = context.state.scopes.get(node);
1611
+ const component_scope = state.scopes.get(node);
1583
1612
  const children_component = b.component(b.id('children'), [], children_filtered);
1584
1613
 
1585
- children_component.metadata = {
1586
- ...(children_component.metadata || {}),
1587
- inherited_css: true,
1588
- };
1589
-
1590
1614
  const children = /** @type {AST.Expression} */ (
1591
1615
  visit(children_component, {
1592
- ...context.state,
1616
+ ...state,
1617
+ ...(state.applyParentCssScope ||
1618
+ (is_dynamic_element && node.metadata.scoped && state.component?.css)
1619
+ ? {
1620
+ applyParentCssScope: /** @type {AST.CSS.StyleSheet} */ (state.component?.css)
1621
+ .hash,
1622
+ }
1623
+ : {}),
1593
1624
  scope: /** @type {ScopeInterface} */ (component_scope),
1594
1625
  namespace: child_namespace,
1595
1626
  })
@@ -1721,13 +1752,6 @@ const visitors = {
1721
1752
  }),
1722
1753
  ];
1723
1754
 
1724
- /** @type {AST.NodeWithLocation} */
1725
- const loc_node = {
1726
- start: /** @type {AST.NodeWithLocation } */ (node).start,
1727
- end: /** @type {AST.NodeWithLocation } */ (node).end,
1728
- loc: /** @type {AST.SourceLocation} */ (node.loc),
1729
- };
1730
-
1731
1755
  const func = b.function(
1732
1756
  node.id,
1733
1757
  node.params.map(
@@ -1736,14 +1760,22 @@ const visitors = {
1736
1760
  ),
1737
1761
  b.block([...style_statements, ...body_statements]),
1738
1762
  false,
1739
- loc_node,
1763
+ /** @type {AST.NodeWithLocation} */ (node),
1740
1764
  );
1741
1765
  // Mark that this function was originally a component
1742
1766
  func.metadata = /** @type {AST.FunctionExpression['metadata']} */ ({
1743
- ...func.metadata,
1744
- was_component: true,
1767
+ ...node.metadata,
1768
+ is_component: true,
1745
1769
  });
1746
1770
 
1771
+ if (func.id) {
1772
+ // metadata should be there as func.id === node.id
1773
+ func.id.metadata = /** @type {AST.Identifier['metadata']} */ ({
1774
+ ...func.id.metadata,
1775
+ is_component: true,
1776
+ });
1777
+ }
1778
+
1747
1779
  return func;
1748
1780
  }
1749
1781
 
@@ -1760,11 +1792,18 @@ const visitors = {
1760
1792
  }
1761
1793
  }
1762
1794
 
1795
+ const component_scope = context.state.scopes.get(node) || context.state.scope;
1763
1796
  const body_statements = [
1764
1797
  b.stmt(b.call('_$_.push_component')),
1765
1798
  ...transform_body(node.body, {
1766
1799
  ...context,
1767
- state: { ...context.state, component: node, metadata },
1800
+ state: {
1801
+ ...context.state,
1802
+ flush_node: null,
1803
+ component: node,
1804
+ metadata,
1805
+ scope: component_scope,
1806
+ },
1768
1807
  }),
1769
1808
  b.stmt(b.call('_$_.pop_component')),
1770
1809
  ];
@@ -1786,12 +1825,7 @@ const visitors = {
1786
1825
  : body_statements),
1787
1826
  ]),
1788
1827
  );
1789
- // Mark that this function was originally a component
1790
- func.metadata = /** @type {AST.FunctionExpression['metadata']} */ ({
1791
- ...func.metadata,
1792
- was_component: true,
1793
- });
1794
- func.loc = node.loc; // Copy source location for Volar mappings
1828
+
1795
1829
  return func;
1796
1830
  },
1797
1831
 
@@ -1945,7 +1979,12 @@ const visitors = {
1945
1979
  b.block(
1946
1980
  transform_body(/** @type {AST.BlockStatement} */ (node.body).body, {
1947
1981
  ...context,
1948
- state: { ...context.state, scope: body_scope, namespace: context.state.namespace },
1982
+ state: {
1983
+ ...context.state,
1984
+ scope: body_scope,
1985
+ namespace: context.state.namespace,
1986
+ flush_node: null,
1987
+ },
1949
1988
  }),
1950
1989
  ),
1951
1990
  ),
@@ -1986,7 +2025,7 @@ const visitors = {
1986
2025
 
1987
2026
  const block = transform_body(consequent, {
1988
2027
  ...context,
1989
- state: { ...context.state, scope: consequent_scope },
2028
+ state: { ...context.state, scope: consequent_scope, flush_node: null },
1990
2029
  });
1991
2030
  const has_break = consequent.some((stmt) => stmt.type === 'BreakStatement');
1992
2031
  const is_last = counter === node.cases.length - 1;
@@ -2057,7 +2096,7 @@ const visitors = {
2057
2096
  const consequent = b.block(
2058
2097
  transform_body(consequent_body, {
2059
2098
  ...context,
2060
- state: { ...context.state, scope: consequent_scope },
2099
+ state: { ...context.state, flush_node: null, scope: consequent_scope },
2061
2100
  }),
2062
2101
  );
2063
2102
  const consequent_id = context.state.scope.generate('consequent');
@@ -2074,7 +2113,7 @@ const visitors = {
2074
2113
  const alternate_block = b.block(
2075
2114
  transform_body(alternate_body, {
2076
2115
  ...context,
2077
- state: { ...context.state, scope: alternate_scope },
2116
+ state: { ...context.state, flush_node: null, scope: alternate_scope },
2078
2117
  }),
2079
2118
  );
2080
2119
  alternate_id = context.state.scope.generate('alternate');
@@ -2499,7 +2538,7 @@ function transform_ts_child(node, context) {
2499
2538
  const is_dom_element = is_element_dom_element(node);
2500
2539
  const component_scope = /** @type {ScopeInterface} */ (context.state.scopes.get(node));
2501
2540
  const thunk =
2502
- node.id.name === 'style'
2541
+ /** @type {AST.Identifier} */ (node.id).name === 'style'
2503
2542
  ? null
2504
2543
  : b.thunk(
2505
2544
  b.block(
@@ -2508,7 +2547,10 @@ function transform_ts_child(node, context) {
2508
2547
  state: {
2509
2548
  ...state,
2510
2549
  scope: component_scope,
2511
- inside_head: node.id.name === 'head' ? true : state.inside_head,
2550
+ inside_head:
2551
+ /** @type {AST.Identifier} */ (node.id).name === 'head'
2552
+ ? true
2553
+ : state.inside_head,
2512
2554
  },
2513
2555
  }),
2514
2556
  ),
@@ -2541,6 +2583,20 @@ function transform_ts_child(node, context) {
2541
2583
  }
2542
2584
  }
2543
2585
 
2586
+ if (node.id.type === 'MemberExpression') {
2587
+ const member = /** @type {AST.MemberExpression} */ (visit(node.id, { ...state }));
2588
+
2589
+ node.id = member;
2590
+ /** @type {ESTreeJSX.RippleJSXOpeningElement} */ (node.openingElement).name = member;
2591
+ if (node.closingElement) {
2592
+ /** @type {ESTreeJSX.RippleJSXClosingElement} */ (node.closingElement).name = set_location(
2593
+ { ...member },
2594
+ /** @type {AST.NodeWithLocation} */ (node.closingElement.name),
2595
+ true,
2596
+ );
2597
+ }
2598
+ }
2599
+
2544
2600
  /** @type {ESTreeJSX.JSXElement} */
2545
2601
  const jsxElement = b.jsx_element(node, attributes, children);
2546
2602
 
@@ -2796,7 +2852,7 @@ function transform_children(children, context) {
2796
2852
  const get_id = (node) => {
2797
2853
  return b.id(
2798
2854
  node.type == 'Element' && is_element_dom_element(node)
2799
- ? state.scope.generate(node.id.name)
2855
+ ? state.scope.generate(/** @type {AST.Identifier} */ (node.id).name)
2800
2856
  : node.type == 'Text'
2801
2857
  ? state.scope.generate('text')
2802
2858
  : state.scope.generate('node'),
@@ -2837,15 +2893,15 @@ function transform_children(children, context) {
2837
2893
  let metadata;
2838
2894
  /** @type {AST.Expression | undefined} */
2839
2895
  let expression = undefined;
2840
- let isCreateTextOnly = false;
2896
+ let is_create_text_only = false;
2841
2897
  if (node.type === 'Text' || node.type === 'Html') {
2842
2898
  metadata = { tracking: false, await: false };
2843
2899
  expression = /** @type {AST.Expression} */ (visit(node.expression, { ...state, metadata }));
2844
- isCreateTextOnly =
2900
+ is_create_text_only =
2845
2901
  node.type === 'Text' && normalized.length === 1 && expression.type === 'Literal';
2846
2902
  }
2847
2903
 
2848
- if (initial === null && root && !isCreateTextOnly) {
2904
+ if (initial === null && root && !is_create_text_only) {
2849
2905
  create_initial(node);
2850
2906
  }
2851
2907
 
@@ -2882,7 +2938,7 @@ function transform_children(children, context) {
2882
2938
  cached = id;
2883
2939
  return id;
2884
2940
  } else {
2885
- debugger;
2941
+ return get_id(node);
2886
2942
  }
2887
2943
  };
2888
2944
 
@@ -2941,9 +2997,7 @@ function transform_children(children, context) {
2941
2997
  state.template?.push(escape_html(expr.value));
2942
2998
  } else {
2943
2999
  const id = flush_node(true);
2944
- state.init?.push(
2945
- b.var(/** @type {AST.Identifier} */ (id), b.call('_$_.create_text', expr)),
2946
- );
3000
+ state.init?.push(b.var(/** @type {AST.Identifier} */ (id), b.call('_$_.text', expr)));
2947
3001
  state.final?.push(b.stmt(b.call('_$_.append', b.id('__anchor'), id)));
2948
3002
  }
2949
3003
  } else {
@@ -3095,7 +3149,7 @@ function transform_body(body, { visit, state }) {
3095
3149
  }
3096
3150
 
3097
3151
  // NOTE: transform_children in `to_ts` mode does NOT add to body_state.update
3098
- // So, we skip adding doing any actions with body_state.update
3152
+ // So, we skip adding any actions with body_state.update
3099
3153
  }
3100
3154
 
3101
3155
  return [
@@ -3160,30 +3214,23 @@ function create_tsx_with_typescript_support(comments) {
3160
3214
  * @param {TransformClientContext} context
3161
3215
  */
3162
3216
  const handle_function = (node, context) => {
3163
- let is_method = false;
3164
- const parent = node.metadata.path.at(-1);
3165
- if (
3166
- node.type === 'FunctionExpression' &&
3167
- parent &&
3168
- ((parent.type === 'Property' && parent.method === true) || parent.type === 'MethodDefinition')
3169
- ) {
3170
- is_method = true;
3171
- }
3172
3217
  const loc = /** @type {AST.SourceLocation} */ (node.loc);
3218
+ const start_pos = /** @type {AST.Position} */ ({
3219
+ line: loc.start.line,
3220
+ column: loc.start.column,
3221
+ });
3173
3222
 
3174
3223
  if (node.async) {
3175
3224
  context.location(loc.start.line, loc.start.column);
3176
3225
  context.write('async ');
3177
- if (!is_method) {
3178
- context.location(loc.start.line, loc.start.column + 6);
3179
- context.write('function');
3180
- }
3181
- } else {
3182
- if (!is_method) {
3183
- context.write('function', node);
3184
- }
3226
+ context.location(loc.start.line, loc.start.column + 'async '.length);
3227
+ start_pos.column += 'async '.length;
3185
3228
  }
3186
3229
 
3230
+ context.location(start_pos.line, start_pos.column);
3231
+ context.write('function');
3232
+ context.location(start_pos.line, start_pos.column + 'function'.length);
3233
+
3187
3234
  if (node.generator) {
3188
3235
  context.write('*');
3189
3236
  }
@@ -3244,6 +3291,46 @@ function create_tsx_with_typescript_support(comments) {
3244
3291
  base_tsx.ExpressionStatement?.(node, context);
3245
3292
  context.location(loc.end.line, loc.end.column);
3246
3293
  },
3294
+ UpdateExpression(node, context) {
3295
+ if (!node.loc) {
3296
+ base_tsx.UpdateExpression?.(node, context);
3297
+ return;
3298
+ }
3299
+ const loc = /** @type {AST.SourceLocation} */ (node.loc);
3300
+ context.location(loc.start.line, loc.start.column);
3301
+ base_tsx.UpdateExpression?.(node, context);
3302
+ context.location(loc.end.line, loc.end.column);
3303
+ },
3304
+ UnaryExpression(node, context) {
3305
+ if (!node.loc) {
3306
+ base_tsx.UnaryExpression?.(node, context);
3307
+ return;
3308
+ }
3309
+ const loc = /** @type {AST.SourceLocation} */ (node.loc);
3310
+ context.location(loc.start.line, loc.start.column);
3311
+ base_tsx.UnaryExpression?.(node, context);
3312
+ context.location(loc.end.line, loc.end.column);
3313
+ },
3314
+ YieldExpression(node, context) {
3315
+ if (!node.loc) {
3316
+ base_tsx.YieldExpression?.(node, context);
3317
+ return;
3318
+ }
3319
+ const loc = /** @type {AST.SourceLocation} */ (node.loc);
3320
+ context.location(loc.start.line, loc.start.column);
3321
+ base_tsx.YieldExpression?.(node, context);
3322
+ context.location(loc.end.line, loc.end.column);
3323
+ },
3324
+ CallExpression(node, context) {
3325
+ if (!node.loc) {
3326
+ base_tsx.CallExpression?.(node, context);
3327
+ return;
3328
+ }
3329
+ const loc = /** @type {AST.SourceLocation} */ (node.loc);
3330
+ context.location(loc.start.line, loc.start.column);
3331
+ base_tsx.CallExpression?.(node, context);
3332
+ context.location(loc.end.line, loc.end.column);
3333
+ },
3247
3334
  Literal(node, context) {
3248
3335
  if (!node.loc || node.raw === undefined) {
3249
3336
  base_tsx.Literal?.(node, context);
@@ -3346,61 +3433,99 @@ function create_tsx_with_typescript_support(comments) {
3346
3433
  context.location(loc.end.line, loc.end.column);
3347
3434
  },
3348
3435
  Property(node, context) {
3349
- // Check if the value is a function that was originally a component
3350
- const isComponent =
3351
- node.value?.type === 'FunctionExpression' && node.value.metadata?.was_component;
3352
-
3353
- if (isComponent) {
3354
- // Manually print as non-method property to preserve 'function' keyword
3355
- // This ensures esrap creates proper source map entries for the component->function transformation
3356
- if (node.computed) {
3357
- context.write('[');
3358
- context.visit(node.key);
3359
- context.write(']');
3360
- } else {
3361
- context.visit(node.key);
3362
- }
3363
- context.write(': ');
3364
- context.visit(node.value);
3365
- } else if (!node.shorthand) {
3366
- // If property is already longhand in source, keep it longhand
3367
- // to prevent source map issues when parts of the syntax disappear in shorthand conversion
3368
- // This applies to:
3369
- // - { media: media } -> would become { media } (value identifier disappears)
3370
- // - { fn: function() {} } -> would become { fn() {} } ('function' keyword disappears)
3371
- const value = node.value.type === 'AssignmentPattern' ? node.value.left : node.value;
3372
-
3373
- // Check if esrap would convert this to shorthand property or method
3374
- const wouldBeShorthand =
3375
- !node.computed &&
3376
- node.kind === 'init' &&
3377
- node.key.type === 'Identifier' &&
3378
- value.type === 'Identifier' &&
3379
- node.key.name === value.name;
3380
-
3381
- const wouldBeMethodShorthand =
3382
- !node.computed &&
3383
- node.value.type === 'FunctionExpression' &&
3384
- node.kind !== 'get' &&
3385
- node.kind !== 'set';
3386
-
3387
- if (wouldBeShorthand || wouldBeMethodShorthand) {
3388
- let colon_str = ': ';
3389
- if (node.method === true && node.value.type === 'FunctionExpression') {
3390
- colon_str = '';
3391
- }
3392
- // Force longhand: write key: value explicitly to preserve source positions
3393
- if (node.computed) context.write('[');
3394
- context.visit(node.key);
3395
- context.write(node.computed ? ']' + colon_str : colon_str);
3396
- context.visit(node.value);
3397
- } else {
3398
- base_tsx.Property?.(node, context);
3436
+ let start_pos = node.loc?.start;
3437
+ if (node.loc) {
3438
+ start_pos = /** @type {AST.Position} */ ({
3439
+ line: node.loc.start.line,
3440
+ column: node.loc.start.column,
3441
+ });
3442
+ }
3443
+
3444
+ const is_method = node.method || node.kind === 'get' || node.kind === 'set';
3445
+
3446
+ // Handle getters/setters
3447
+ if (node.kind === 'get') {
3448
+ context.write('get ');
3449
+ if (start_pos) {
3450
+ start_pos.column += 'get '.length;
3451
+ }
3452
+ } else if (node.kind === 'set') {
3453
+ if (start_pos) {
3454
+ start_pos.column += 'set '.length;
3455
+ }
3456
+ context.write('set ');
3457
+ }
3458
+
3459
+ // Write async keyword (before *)
3460
+ if (is_method && /** @type {AST.FunctionExpression} */ (node.value).async) {
3461
+ // If not a method, async should be a part of the value e.g. { prop: async function }
3462
+ if (start_pos) {
3463
+ context.location(start_pos.line, start_pos.column);
3464
+ }
3465
+ context.write('async ');
3466
+ if (start_pos) {
3467
+ context.location(start_pos.line, start_pos.column + 'async '.length);
3468
+ start_pos.column += 'async '.length;
3469
+ }
3470
+ }
3471
+
3472
+ // Write * for generator methods
3473
+ if (/** @type {AST.FunctionExpression} */ (node.value).generator) {
3474
+ context.write('*');
3475
+ }
3476
+
3477
+ // Write the key
3478
+ if (node.computed) {
3479
+ if (node.key.loc) {
3480
+ context.location(node.key.loc.start.line, node.key.loc.start.column - 1);
3481
+ }
3482
+ context.write('[');
3483
+ context.visit(node.key);
3484
+ context.write(']');
3485
+ if (node.key.loc) {
3486
+ context.location(node.key.loc.end.line, node.key.loc.end.column + 1);
3399
3487
  }
3400
3488
  } else {
3401
- // Use default handler for non-component properties
3402
- base_tsx.Property?.(node, context);
3489
+ if (node.shorthand) {
3490
+ // only visit value since key and value are the same
3491
+ // or the value will contain the key like in AssignmentPattern: { foo = 1 }
3492
+ context.visit(node.value);
3493
+ return;
3494
+ }
3495
+
3496
+ context.visit(node.key);
3403
3497
  }
3498
+
3499
+ // Method shorthand: { foo() {} } or getters/setters - print params and body directly
3500
+ if (is_method) {
3501
+ const fn = /** @type {AST.FunctionExpression} */ (node.value);
3502
+
3503
+ fn.metadata.is_method = true;
3504
+
3505
+ // Type parameters: { foo<T>() {} }
3506
+ if (fn.typeParameters) {
3507
+ context.visit(fn.typeParameters);
3508
+ }
3509
+
3510
+ context.write('(');
3511
+ for (let i = 0; i < fn.params.length; i++) {
3512
+ if (i > 0) context.write(', ');
3513
+ context.visit(fn.params[i]);
3514
+ }
3515
+ context.write(')');
3516
+
3517
+ if (fn.returnType) {
3518
+ context.visit(fn.returnType);
3519
+ }
3520
+
3521
+ context.write(' ');
3522
+ context.visit(fn.body);
3523
+ return;
3524
+ }
3525
+
3526
+ // Regular property: { key: value }
3527
+ context.write(': ');
3528
+ context.visit(node.value);
3404
3529
  },
3405
3530
  JSXOpeningElement(node, context) {
3406
3531
  // Set location for '<'
@@ -3444,49 +3569,71 @@ function create_tsx_with_typescript_support(comments) {
3444
3569
  context.location(loc.end.line, loc.end.column);
3445
3570
  },
3446
3571
  MethodDefinition(node, context) {
3447
- // Check if there are type parameters to handle
3448
- const hasTypeParams = node.typeParameters || node.value?.typeParameters;
3449
-
3450
- if (!hasTypeParams) {
3451
- // No type parameters, use default handler
3452
- return base_tsx.MethodDefinition?.(node, context);
3572
+ node.value.metadata.is_method = true;
3573
+ /** @type {AST.Position | undefined} */
3574
+ let start_pos;
3575
+ if (node.loc) {
3576
+ start_pos = /** @type {AST.Position} */ ({
3577
+ line: node.loc.start.line,
3578
+ column: node.loc.start.column,
3579
+ });
3453
3580
  }
3454
3581
 
3455
- // Has type parameters - we need to manually handle to ensure they're visited
3456
3582
  // Write modifiers (static, async, etc.)
3457
3583
  if (node.static) {
3458
3584
  context.write('static ');
3585
+ if (start_pos) {
3586
+ start_pos.column += 'static '.length;
3587
+ }
3459
3588
  }
3460
3589
 
3461
- // Handle getters/setters
3462
3590
  if (node.kind === 'get') {
3463
3591
  context.write('get ');
3592
+ if (start_pos) {
3593
+ start_pos.column += 'get '.length;
3594
+ }
3464
3595
  } else if (node.kind === 'set') {
3596
+ if (start_pos) {
3597
+ start_pos.column += 'set '.length;
3598
+ }
3465
3599
  context.write('set ');
3466
3600
  } else if (node.kind === 'constructor') {
3467
- context.write('constructor ');
3601
+ // skip as it's covered by the key
3468
3602
  }
3469
3603
 
3470
- // Write * for generator methods
3471
- if (node.value?.generator) {
3472
- context.write('*');
3604
+ // Write async keyword (before *)
3605
+ if (/** @type {AST.FunctionExpression} */ (node.value).async) {
3606
+ if (start_pos) {
3607
+ context.location(start_pos.line, start_pos.column);
3608
+ }
3609
+ context.write('async ');
3610
+ if (start_pos) {
3611
+ context.location(start_pos.line, start_pos.column + 'async '.length);
3612
+ start_pos.column += 'async '.length;
3613
+ }
3473
3614
  }
3474
3615
 
3475
- // Write async keyword
3476
- if (node.value?.async) {
3477
- context.write('async ');
3616
+ // Write * for generator methods
3617
+ if (node.value.generator) {
3618
+ context.write('*');
3478
3619
  }
3479
3620
 
3480
3621
  // Write the method key
3481
3622
  if (node.computed) {
3623
+ if (node.key.loc) {
3624
+ context.location(node.key.loc.start.line, node.key.loc.start.column - 1);
3625
+ }
3482
3626
  context.write('[');
3483
3627
  context.visit(node.key);
3484
3628
  context.write(']');
3629
+ if (node.key.loc) {
3630
+ context.location(node.key.loc.end.line, node.key.loc.end.column + 1);
3631
+ }
3485
3632
  } else {
3486
3633
  context.visit(node.key);
3487
3634
  }
3488
3635
 
3489
- // Visit typeParameters if present (THIS IS THE FIX)
3636
+ // Visit typeParameters
3490
3637
  // TypeParameters can be on either the MethodDefinition or its value (FunctionExpression)
3491
3638
  if (node.typeParameters) {
3492
3639
  context.visit(node.typeParameters);