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.
- package/package.json +3 -2
- package/shims/rollup-estree-types.d.ts +2 -0
- package/src/compiler/index.d.ts +30 -8
- package/src/compiler/index.js +10 -7
- package/src/compiler/phases/1-parse/index.js +117 -18
- package/src/compiler/phases/1-parse/style.js +4 -4
- package/src/compiler/phases/2-analyze/index.js +1 -0
- package/src/compiler/phases/3-transform/client/index.js +200 -18
- package/src/compiler/phases/3-transform/segments.js +193 -124
- package/src/compiler/source-map-utils.js +7 -3
- package/src/compiler/types/index.d.ts +23 -0
- package/src/runtime/internal/client/events.js +18 -1
- package/src/utils/builders.js +49 -24
- package/tests/client/events.test.ripple +109 -1
- package/tests/client/media-query.test.ripple +5 -2
- package/tests/setup-client.js +4 -0
- package/tsconfig.json +2 -0
|
@@ -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
|
|
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
|
-
|
|
1972
|
-
|
|
1981
|
+
opening_name_element = type_expression;
|
|
1982
|
+
closing_name_element = node.selfClosing || node.unclosed ? undefined : type_expression;
|
|
1973
1983
|
} else {
|
|
1974
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2050
|
-
|
|
2059
|
+
let jsxElement = b.jsx_element(
|
|
2060
|
+
opening_name_element,
|
|
2061
|
+
node.loc,
|
|
2051
2062
|
attributes,
|
|
2052
2063
|
children,
|
|
2053
2064
|
node.selfClosing,
|
|
2054
|
-
|
|
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
|
-
|
|
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('(');
|