ripple 0.2.174 → 0.2.176

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.
@@ -736,6 +736,17 @@ const visitors = {
736
736
  return context.visit(node.expression);
737
737
  },
738
738
 
739
+ JSXEmptyExpression(node, context) {
740
+ // JSX comments like {/* ... */} are represented as JSXEmptyExpression
741
+ // In TypeScript mode, preserve them as-is for prettier
742
+ // In JavaScript mode, they're removed (which is correct since they're comments)
743
+ if (context.state.to_ts) {
744
+ return context.next();
745
+ }
746
+ // In JS mode, return empty - comments are stripped
747
+ return b.empty;
748
+ },
749
+
739
750
  JSXFragment(node, context) {
740
751
  if (context.state.to_ts) {
741
752
  return context.next();
@@ -1901,7 +1912,6 @@ function transform_ts_child(node, context) {
1901
1912
  const children = [];
1902
1913
  let has_children_props = false;
1903
1914
 
1904
- const ref_attributes = [];
1905
1915
  const attributes = node.attributes.map((attr) => {
1906
1916
  if (attr.type === 'Attribute') {
1907
1917
  const metadata = { await: false };
@@ -1943,7 +1953,7 @@ function transform_ts_child(node, context) {
1943
1953
  }
1944
1954
  });
1945
1955
 
1946
- if (!node.selfClosing && !has_children_props && node.children.length > 0) {
1956
+ if (!node.selfClosing && !node.unclosed && !has_children_props && node.children.length > 0) {
1947
1957
  const is_dom_element = is_element_dom_element(node);
1948
1958
 
1949
1959
  const component_scope = context.state.scopes.get(node);
@@ -1963,20 +1973,20 @@ function transform_ts_child(node, context) {
1963
1973
  }
1964
1974
  }
1965
1975
 
1966
- let opening_type, closing_type;
1976
+ let opening_name_element, closing_name_element;
1967
1977
 
1968
1978
  if (type_is_expression) {
1969
1979
  // For dynamic/expression-based components (e.g., props.children),
1970
1980
  // use JSX expression instead of identifier
1971
- opening_type = type_expression;
1972
- closing_type = node.selfClosing ? undefined : type_expression;
1981
+ opening_name_element = type_expression;
1982
+ closing_name_element = node.selfClosing || node.unclosed ? undefined : type_expression;
1973
1983
  } else {
1974
- opening_type = b.jsx_id(type_expression);
1984
+ opening_name_element = b.jsx_id(type_expression);
1975
1985
  // For tracked identifiers (dynamic components), adjust the loc to skip the '@' prefix
1976
1986
  // and add metadata for mapping
1977
1987
  if (node.id.tracked && node.id.loc) {
1978
1988
  // The original identifier loc includes the '@', so we need to skip it
1979
- opening_type.loc = {
1989
+ opening_name_element.loc = {
1980
1990
  start: {
1981
1991
  line: node.id.loc.start.line,
1982
1992
  column: node.id.loc.start.column + 1, // Skip '@'
@@ -1985,14 +1995,14 @@ function transform_ts_child(node, context) {
1985
1995
  };
1986
1996
  // Add metadata if this was capitalized
1987
1997
  if (node.metadata?.ts_name && node.metadata?.original_name) {
1988
- opening_type.metadata = {
1998
+ opening_name_element.metadata = {
1989
1999
  original_name: node.metadata.original_name,
1990
2000
  is_capitalized: true,
1991
2001
  };
1992
2002
  }
1993
2003
  } else {
1994
2004
  // Use node.id.loc if available, otherwise create a loc based on the element's position
1995
- opening_type.loc = node.id.loc || {
2005
+ opening_name_element.loc = node.id.loc || {
1996
2006
  start: {
1997
2007
  line: node.loc.start.line,
1998
2008
  column: node.loc.start.column + 2, // After "<@"
@@ -2004,14 +2014,14 @@ function transform_ts_child(node, context) {
2004
2014
  };
2005
2015
  }
2006
2016
 
2007
- if (!node.selfClosing) {
2008
- closing_type = b.jsx_id(type_expression);
2017
+ if (!node.selfClosing && !node.unclosed) {
2018
+ closing_name_element = b.jsx_id(type_expression);
2009
2019
  // For tracked identifiers, also adjust closing tag location
2010
2020
  if (node.id.tracked && node.id.loc) {
2011
2021
  // Calculate position relative to closing tag
2012
2022
  // Format: </@identifier>
2013
2023
  const closing_tag_start = node.loc.end.column - type_expression.length - 3; // </@
2014
- closing_type.loc = {
2024
+ closing_name_element.loc = {
2015
2025
  start: {
2016
2026
  line: node.loc.end.line,
2017
2027
  column: closing_tag_start + 3, // Skip '</@'
@@ -2026,13 +2036,13 @@ function transform_ts_child(node, context) {
2026
2036
  };
2027
2037
  // Add metadata if this was capitalized
2028
2038
  if (node.metadata?.ts_name && node.metadata?.original_name) {
2029
- closing_type.metadata = {
2039
+ closing_name_element.metadata = {
2030
2040
  original_name: node.metadata.original_name,
2031
2041
  is_capitalized: true,
2032
2042
  };
2033
2043
  }
2034
2044
  } else {
2035
- closing_type.loc = {
2045
+ closing_name_element.loc = {
2036
2046
  start: {
2037
2047
  line: node.loc.end.line,
2038
2048
  column: node.loc.end.column - type_expression.length - 1,
@@ -2046,13 +2056,38 @@ function transform_ts_child(node, context) {
2046
2056
  }
2047
2057
  }
2048
2058
 
2049
- const jsxElement = b.jsx_element(
2050
- opening_type,
2059
+ let jsxElement = b.jsx_element(
2060
+ opening_name_element,
2061
+ node.loc,
2051
2062
  attributes,
2052
2063
  children,
2053
2064
  node.selfClosing,
2054
- closing_type,
2065
+ node.unclosed,
2066
+ closing_name_element,
2055
2067
  );
2068
+
2069
+ // Calculate the location for the entire JSXClosingElement (including </ and >)
2070
+ if (jsxElement.closingElement && !node.selfClosing && !node.unclosed) {
2071
+ // The closing element starts with '</' and ends with '>'
2072
+ // For a tag like </div>, if node.loc.end is right after '>', then:
2073
+ // - '<' is at node.loc.end.column - type_expression.length - 3
2074
+ // - '>' is at node.loc.end.column - 1
2075
+ const tag_name_length = node.id.tracked
2076
+ ? (node.metadata?.original_name?.length || type_expression.length) + 1 // +1 for '@'
2077
+ : type_expression.length;
2078
+
2079
+ jsxElement.closingElement.loc = {
2080
+ start: {
2081
+ line: node.loc.end.line,
2082
+ column: node.loc.end.column - tag_name_length - 2, // at '</'
2083
+ },
2084
+ end: {
2085
+ line: node.loc.end.line,
2086
+ column: node.loc.end.column, // at '>'
2087
+ },
2088
+ };
2089
+ }
2090
+
2056
2091
  // Preserve metadata from Element node for mapping purposes
2057
2092
  if (node.metadata && (node.metadata.ts_name || node.metadata.original_name)) {
2058
2093
  jsxElement.metadata = {
@@ -2060,7 +2095,13 @@ function transform_ts_child(node, context) {
2060
2095
  original_name: node.metadata.original_name,
2061
2096
  };
2062
2097
  }
2063
- state.init.push(b.stmt(jsxElement));
2098
+ // For unclosed elements, push the JSXElement directly without wrapping in ExpressionStatement
2099
+ // This keeps it in the AST for mappings but avoids adding a semicolon
2100
+ if (node.unclosed) {
2101
+ state.init.push(jsxElement);
2102
+ } else {
2103
+ state.init.push(b.stmt(jsxElement));
2104
+ }
2064
2105
  } else if (node.type === 'IfStatement') {
2065
2106
  const consequent_scope = context.state.scopes.get(node.consequent);
2066
2107
  const consequent = b.block(
@@ -2164,6 +2205,10 @@ function transform_ts_child(node, context) {
2164
2205
  .filter((child) => child.type !== 'JSXText' || child.value.trim() !== '');
2165
2206
 
2166
2207
  state.init.push(b.stmt(b.jsx_fragment(children)));
2208
+ } else if (node.type === 'JSXExpressionContainer') {
2209
+ // JSX comments {/* ... */} are JSXExpressionContainer with JSXEmptyExpression
2210
+ // These should be preserved in the output as-is for prettier to handle
2211
+ state.init.push(b.stmt(b.jsx_expression_container(visit(node.expression, state))));
2167
2212
  } else {
2168
2213
  throw new Error('TODO');
2169
2214
  }
@@ -2496,11 +2541,62 @@ function create_tsx_with_typescript_support() {
2496
2541
  }
2497
2542
  context.write(': ');
2498
2543
  context.visit(node.value);
2544
+ } else if (!node.shorthand) {
2545
+ // If property is already longhand in source, keep it longhand
2546
+ // to prevent source map issues when parts of the syntax disappear in shorthand conversion
2547
+ // This applies to:
2548
+ // - { media: media } -> would become { media } (value identifier disappears)
2549
+ // - { fn: function() {} } -> would become { fn() {} } ('function' keyword disappears)
2550
+ const value = node.value.type === 'AssignmentPattern' ? node.value.left : node.value;
2551
+
2552
+ // Check if esrap would convert this to shorthand property or method
2553
+ const wouldBeShorthand =
2554
+ !node.computed &&
2555
+ node.kind === 'init' &&
2556
+ node.key.type === 'Identifier' &&
2557
+ value.type === 'Identifier' &&
2558
+ node.key.name === value.name;
2559
+
2560
+ const wouldBeMethodShorthand =
2561
+ !node.computed &&
2562
+ node.value.type === 'FunctionExpression' &&
2563
+ node.kind !== 'get' &&
2564
+ node.kind !== 'set';
2565
+
2566
+ if (wouldBeShorthand || wouldBeMethodShorthand) {
2567
+ // Force longhand: write key: value explicitly to preserve source positions
2568
+ if (node.computed) context.write('[');
2569
+ context.visit(node.key);
2570
+ context.write(node.computed ? ']: ' : ': ');
2571
+ context.visit(node.value);
2572
+ } else {
2573
+ base_tsx.Property(node, context);
2574
+ }
2499
2575
  } else {
2500
2576
  // Use default handler for non-component properties
2501
2577
  base_tsx.Property(node, context);
2502
2578
  }
2503
2579
  },
2580
+ // Custom handler for JSXClosingElement to ensure closing tag brackets have source mappings
2581
+ JSXClosingElement(node, context) {
2582
+ // Set location for '<' then write '</'
2583
+ if (node.loc) {
2584
+ context.location(node.loc.start.line, node.loc.start.column);
2585
+ context.write('</');
2586
+ } else {
2587
+ context.write('</');
2588
+ }
2589
+
2590
+ context.visit(node.name);
2591
+
2592
+ // Set location for '>' then write it
2593
+ if (node.loc) {
2594
+ context.location(node.loc.end.line, node.loc.end.column - 1);
2595
+ context.write('>');
2596
+ } else {
2597
+ context.write('>');
2598
+ }
2599
+ },
2504
2600
  // Custom handler for ArrayPattern to ensure typeAnnotation is visited
2505
2601
  // esrap's TypeScript handler doesn't visit typeAnnotation for ArrayPattern (only for ObjectPattern)
2506
2602
  ArrayPattern(node, context) {
@@ -2527,6 +2623,92 @@ function create_tsx_with_typescript_support() {
2527
2623
  FunctionExpression(node, context) {
2528
2624
  handle_function(node, context);
2529
2625
  },
2626
+ // Custom handler for ImportDeclaration to ensure 'import' keyword has source mapping
2627
+ // This creates a source map entry at the start of the import statement
2628
+ // Esrap's default handler writes 'import' without passing the node, so no source map entry
2629
+ ImportDeclaration(node, context) {
2630
+ // Write 'import' keyword with node location for source mapping
2631
+ context.write('import', node);
2632
+ context.write(' ');
2633
+
2634
+ // Write specifiers - handle default, namespace, and named imports
2635
+ if (node.specifiers && node.specifiers.length > 0) {
2636
+ let default_specifier = null;
2637
+ let namespace_specifier = null;
2638
+ const named_specifiers = [];
2639
+
2640
+ for (const spec of node.specifiers) {
2641
+ if (spec.type === 'ImportDefaultSpecifier') {
2642
+ default_specifier = spec;
2643
+ } else if (spec.type === 'ImportNamespaceSpecifier') {
2644
+ namespace_specifier = spec;
2645
+ } else if (spec.type === 'ImportSpecifier') {
2646
+ named_specifiers.push(spec);
2647
+ }
2648
+ }
2649
+
2650
+ // Write default import
2651
+ if (default_specifier) {
2652
+ context.visit(default_specifier);
2653
+ if (namespace_specifier || named_specifiers.length > 0) {
2654
+ context.write(', ');
2655
+ }
2656
+ }
2657
+
2658
+ // Write namespace import
2659
+ if (namespace_specifier) {
2660
+ context.visit(namespace_specifier);
2661
+ if (named_specifiers.length > 0) {
2662
+ context.write(', ');
2663
+ }
2664
+ }
2665
+
2666
+ // Write named imports
2667
+ if (named_specifiers.length > 0) {
2668
+ context.write('{ ');
2669
+ for (let i = 0; i < named_specifiers.length; i++) {
2670
+ if (i > 0) context.write(', ');
2671
+ context.visit(named_specifiers[i]);
2672
+ }
2673
+ context.write(' }');
2674
+ }
2675
+
2676
+ context.write(' from ');
2677
+ }
2678
+
2679
+ // Write source
2680
+ context.visit(node.source);
2681
+ },
2682
+ // Custom handler for JSXOpeningElement to ensure '<' and '>' have source mappings
2683
+ // Esrap's default handler only maps the tag name, not the brackets
2684
+ // This creates mappings for the brackets so auto-close can find the cursor position
2685
+ JSXOpeningElement(node, context) {
2686
+ // Set location for '<'
2687
+ if (node.loc) {
2688
+ context.location(node.loc.start.line, node.loc.start.column);
2689
+ }
2690
+ context.write('<');
2691
+
2692
+ context.visit(node.name);
2693
+
2694
+ // Write attributes
2695
+ for (const attr of node.attributes || []) {
2696
+ context.write(' ');
2697
+ context.visit(attr);
2698
+ }
2699
+
2700
+ if (node.selfClosing) {
2701
+ context.write(' />');
2702
+ } else {
2703
+ // Set the source location for the '>'
2704
+ // node.loc.end points AFTER the '>', so subtract 1 to get the position OF the '>'
2705
+ if (node.loc) {
2706
+ // TODO: why do we need to subtract 1 from column here?
2707
+ context.location(node.loc.end.line, node.loc.end.column - 1);
2708
+ }
2709
+ context.write('>');
2710
+ }
2711
+ },
2530
2712
  // Custom handler for TSParenthesizedType: (Type)
2531
2713
  TSParenthesizedType(node, context) {
2532
2714
  context.write('(');