ripple 0.3.9 → 0.3.11

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 (70) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/package.json +2 -2
  3. package/src/compiler/errors.js +1 -1
  4. package/src/compiler/index.d.ts +3 -1
  5. package/src/compiler/phases/1-parse/index.js +195 -23
  6. package/src/compiler/phases/2-analyze/index.js +266 -108
  7. package/src/compiler/phases/2-analyze/prune.js +13 -5
  8. package/src/compiler/phases/3-transform/client/index.js +304 -80
  9. package/src/compiler/phases/3-transform/server/index.js +108 -43
  10. package/src/compiler/types/index.d.ts +28 -3
  11. package/src/compiler/types/parse.d.ts +3 -1
  12. package/src/compiler/utils.js +275 -1
  13. package/src/runtime/element.js +39 -0
  14. package/src/runtime/index-client.js +14 -4
  15. package/src/runtime/internal/client/composite.js +10 -6
  16. package/src/runtime/internal/client/expression.js +280 -0
  17. package/src/runtime/internal/client/index.js +4 -0
  18. package/src/runtime/internal/client/portal.js +12 -6
  19. package/src/runtime/internal/server/index.js +26 -1
  20. package/src/utils/builders.js +30 -0
  21. package/tests/client/basic/__snapshots__/basic.rendering.test.ripple.snap +1 -0
  22. package/tests/client/basic/basic.components.test.ripple +85 -87
  23. package/tests/client/basic/basic.errors.test.ripple +4 -8
  24. package/tests/client/basic/basic.rendering.test.ripple +27 -10
  25. package/tests/client/capture-error.js +12 -0
  26. package/tests/client/compiler/compiler.basic.test.ripple +76 -6
  27. package/tests/client/composite/composite.props.test.ripple +1 -3
  28. package/tests/client/composite/composite.render.test.ripple +91 -13
  29. package/tests/client/css/global-additional-cases.test.ripple +3 -3
  30. package/tests/client/return.test.ripple +101 -0
  31. package/tests/client/svg.test.ripple +4 -4
  32. package/tests/client/tsx.test.ripple +486 -0
  33. package/tests/hydration/basic.test.js +23 -0
  34. package/tests/hydration/compiled/client/basic.js +111 -75
  35. package/tests/hydration/compiled/client/composite.js +81 -46
  36. package/tests/hydration/compiled/client/events.js +18 -63
  37. package/tests/hydration/compiled/client/for.js +90 -183
  38. package/tests/hydration/compiled/client/head.js +10 -25
  39. package/tests/hydration/compiled/client/hmr.js +10 -13
  40. package/tests/hydration/compiled/client/html.js +251 -380
  41. package/tests/hydration/compiled/client/if-children.js +35 -45
  42. package/tests/hydration/compiled/client/if.js +2 -2
  43. package/tests/hydration/compiled/client/mixed-control-flow.js +24 -72
  44. package/tests/hydration/compiled/client/nested-control-flow.js +115 -391
  45. package/tests/hydration/compiled/client/portal.js +8 -20
  46. package/tests/hydration/compiled/client/reactivity.js +14 -47
  47. package/tests/hydration/compiled/client/return.js +2 -5
  48. package/tests/hydration/compiled/client/try.js +4 -4
  49. package/tests/hydration/compiled/server/basic.js +64 -31
  50. package/tests/hydration/compiled/server/composite.js +62 -29
  51. package/tests/hydration/compiled/server/hmr.js +24 -37
  52. package/tests/hydration/compiled/server/html.js +472 -611
  53. package/tests/hydration/compiled/server/if-children.js +77 -103
  54. package/tests/hydration/compiled/server/portal.js +8 -8
  55. package/tests/hydration/components/basic.ripple +15 -5
  56. package/tests/hydration/components/composite.ripple +13 -1
  57. package/tests/hydration/components/hmr.ripple +1 -3
  58. package/tests/hydration/components/html.ripple +13 -35
  59. package/tests/hydration/components/if-children.ripple +4 -8
  60. package/tests/hydration/composite.test.js +11 -0
  61. package/tests/server/basic.attributes.test.ripple +50 -0
  62. package/tests/server/basic.components.test.ripple +22 -28
  63. package/tests/server/basic.test.ripple +12 -0
  64. package/tests/server/compiler.test.ripple +25 -8
  65. package/tests/server/composite.props.test.ripple +1 -3
  66. package/tests/server/style-identifier.test.ripple +2 -4
  67. package/tests/utils/compiler-compat-config.test.js +38 -0
  68. package/tests/utils/vite-plugin-config.test.js +113 -0
  69. package/tsconfig.typecheck.json +2 -1
  70. package/types/index.d.ts +8 -11
package/CHANGELOG.md CHANGED
@@ -1,5 +1,48 @@
1
1
  # ripple
2
2
 
3
+ ## 0.3.11
4
+
5
+ ### Patch Changes
6
+
7
+ - [#853](https://github.com/Ripple-TS/ripple/pull/853)
8
+ [`6792c70`](https://github.com/Ripple-TS/ripple/commit/6792c700db30ec0c25077bf8892753f18eddc5cc)
9
+ Thanks [@RazinShafayet2007](https://github.com/RazinShafayet2007)! -
10
+ fix(compiler): add `throw` statement support in `if` blocks
11
+
12
+ - [#858](https://github.com/Ripple-TS/ripple/pull/858)
13
+ [`f2624a6`](https://github.com/Ripple-TS/ripple/commit/f2624a6596479480c47317ea3030863214a6e2b3)
14
+ Thanks [@RazinShafayet2007](https://github.com/RazinShafayet2007)! - fix: scoped
15
+ styles apply correctly when child content is rendered through a parent component
16
+
17
+ - [#840](https://github.com/Ripple-TS/ripple/pull/840)
18
+ [`13323dd`](https://github.com/Ripple-TS/ripple/commit/13323dddbcb68e1e8e373142884a7c54fbb76cd7)
19
+ Thanks [@trueadm](https://github.com/trueadm)! - Remove the `compat` option from
20
+ `mount()` and `hydrate()`, and stop exporting the old public compat types from
21
+ `ripple`. Compat integrations are now expected to be provided by the Vite plugin
22
+ via `ripple.config.ts`, while direct runtime tests can seed the generated global
23
+ compat registry.
24
+
25
+ Also add the `reactCompat()` config-facing helper from `@ripple-ts/compat-react`
26
+ for use in `ripple.config.ts`.
27
+
28
+ - Updated dependencies
29
+ [[`6792c70`](https://github.com/Ripple-TS/ripple/commit/6792c700db30ec0c25077bf8892753f18eddc5cc),
30
+ [`f2624a6`](https://github.com/Ripple-TS/ripple/commit/f2624a6596479480c47317ea3030863214a6e2b3),
31
+ [`13323dd`](https://github.com/Ripple-TS/ripple/commit/13323dddbcb68e1e8e373142884a7c54fbb76cd7)]:
32
+ - ripple@0.3.11
33
+
34
+ ## 0.3.10
35
+
36
+ ### Patch Changes
37
+
38
+ - [`aef1253`](https://github.com/Ripple-TS/ripple/commit/aef1253dd79c067a8358172d502dc21d8a9a9085)
39
+ Thanks [@trueadm](https://github.com/trueadm)! - Replace `<children />` with
40
+ `{children}` expression syntax for rendering component children
41
+
42
+ - Updated dependencies
43
+ [[`aef1253`](https://github.com/Ripple-TS/ripple/commit/aef1253dd79c067a8358172d502dc21d8a9a9085)]:
44
+ - ripple@0.3.10
45
+
3
46
  ## 0.3.9
4
47
 
5
48
  ### Patch Changes
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Ripple is an elegant TypeScript UI framework",
4
4
  "license": "MIT",
5
5
  "author": "Dominic Gannaway",
6
- "version": "0.3.9",
6
+ "version": "0.3.11",
7
7
  "type": "module",
8
8
  "module": "src/runtime/index-client.js",
9
9
  "main": "src/runtime/index-client.js",
@@ -105,6 +105,6 @@
105
105
  "vscode-languageserver-types": "^3.17.5"
106
106
  },
107
107
  "peerDependencies": {
108
- "ripple": "0.3.9"
108
+ "ripple": "0.3.11"
109
109
  }
110
110
  }
@@ -59,7 +59,7 @@ function is_ripple_error_suppress_comment(comment) {
59
59
  }
60
60
 
61
61
  /**
62
- * @param {AST.Node} node
62
+ * @param {AST.Node | AST.NodeWithLocation} node
63
63
  * @param {AST.CommentWithLocation[]} comments
64
64
  */
65
65
  function is_ripple_error_suppressed(node, comments) {
@@ -98,6 +98,7 @@ interface SharedCompileOptions {
98
98
  export interface CompileOptions extends SharedCompileOptions {
99
99
  mode?: 'client' | 'server';
100
100
  hmr?: boolean;
101
+ compat_kinds?: string[];
101
102
  }
102
103
 
103
104
  export interface ParseOptions {
@@ -106,7 +107,8 @@ export interface ParseOptions {
106
107
  comments?: AST.CommentWithLocation[];
107
108
  }
108
109
 
109
- export interface AnalyzeOptions extends ParseOptions, Pick<CompileOptions, 'mode'> {
110
+ export interface AnalyzeOptions
111
+ extends ParseOptions, Pick<CompileOptions, 'mode' | 'compat_kinds'> {
110
112
  errors?: RippleCompileError[];
111
113
  to_ts?: boolean;
112
114
  }
@@ -1115,7 +1115,7 @@ function RipplePlugin(config) {
1115
1115
  let node = /** @type {ESTreeJSX.JSXExpressionContainer} */ (this.startNode());
1116
1116
  this.next();
1117
1117
 
1118
- if (this.value === 'html') {
1118
+ if (this.type === tt.name && this.value === 'html') {
1119
1119
  node.html = true;
1120
1120
  this.next();
1121
1121
  if (this.type === tt.braceR) {
@@ -1124,6 +1124,15 @@ function RipplePlugin(config) {
1124
1124
  '"html" is a Ripple keyword and must be used in the form {html some_content}',
1125
1125
  );
1126
1126
  }
1127
+ } else if (this.type === tt.name && this.value === 'text') {
1128
+ node.text = true;
1129
+ this.next();
1130
+ if (this.type === tt.braceR) {
1131
+ this.raise(
1132
+ this.start,
1133
+ '"text" is a Ripple keyword and must be used in the form {text some_value}',
1134
+ );
1135
+ }
1127
1136
  }
1128
1137
 
1129
1138
  node.expression =
@@ -1328,16 +1337,12 @@ function RipplePlugin(config) {
1328
1337
  const clause = /** @type {AST.CatchClause} */ (this.startNode());
1329
1338
  this.next();
1330
1339
  if (this.eat(tt.parenL)) {
1331
- clause.param = this.parseCatchClauseParam();
1340
+ clause.param = this.parseBindingAtom();
1341
+ this.expect(tt.parenR);
1332
1342
  } else {
1333
- if (this.options.ecmaVersion < 10) {
1334
- this.unexpected();
1335
- }
1336
1343
  clause.param = null;
1337
- this.enterScope(0);
1338
1344
  }
1339
- clause.body = this.parseBlock(false);
1340
- this.exitScope();
1345
+ clause.body = this.parseBlock();
1341
1346
  node.handler = this.finishNode(clause, 'CatchClause');
1342
1347
  }
1343
1348
  node.finalizer = this.eat(tt._finally) ? this.parseBlock() : null;
@@ -1353,7 +1358,9 @@ function RipplePlugin(config) {
1353
1358
 
1354
1359
  /** @type {Parse.Parser['jsx_readToken']} */
1355
1360
  jsx_readToken() {
1356
- const inside_tsx_compat = this.#path.findLast((n) => n.type === 'TsxCompat');
1361
+ const inside_tsx_compat = this.#path.findLast(
1362
+ (n) => n.type === 'TsxCompat' || n.type === 'Tsx',
1363
+ );
1357
1364
  if (inside_tsx_compat) {
1358
1365
  return super.jsx_readToken();
1359
1366
  }
@@ -1504,6 +1511,48 @@ function RipplePlugin(config) {
1504
1511
  }
1505
1512
  }
1506
1513
 
1514
+ /**
1515
+ * Override jsx_parseElement to intercept expression-level JSX.
1516
+ * This is called by acorn-jsx's parseExprAtom when it encounters <
1517
+ * in expression position. Only <tsx> and <tsx:*> are allowed.
1518
+ * @type {Parse.Parser['jsx_parseElement']}
1519
+ */
1520
+ jsx_parseElement() {
1521
+ const inside_tsx = this.#path.findLast((n) => n.type === 'TsxCompat' || n.type === 'Tsx');
1522
+ if (inside_tsx) {
1523
+ // Inside tsx/tsx:*, let acorn-jsx handle it normally
1524
+ return super.jsx_parseElement();
1525
+ }
1526
+
1527
+ // Check if the element being parsed IS a <tsx> or <tsx:*> tag
1528
+ // Current token is jsxTagStart, this.end is position after '<'
1529
+ const tag_name_start = this.end;
1530
+ const char_after_tsx = this.input.charCodeAt(tag_name_start + 3);
1531
+ const is_tsx_tag =
1532
+ this.input.startsWith('tsx', tag_name_start) &&
1533
+ (tag_name_start + 3 >= this.input.length ||
1534
+ char_after_tsx === 62 || // >
1535
+ char_after_tsx === 47 || // / (self-closing)
1536
+ char_after_tsx === 32 || // space
1537
+ char_after_tsx === 9 || // tab
1538
+ char_after_tsx === 10 || // newline
1539
+ char_after_tsx === 13 || // carriage return
1540
+ char_after_tsx === 58); // : (tsx:react)
1541
+
1542
+ if (is_tsx_tag) {
1543
+ // Use Ripple's parseElement to create a Tsx/TsxCompat node
1544
+ this.next();
1545
+ return /** @type {import('estree-jsx').JSXElement} */ (
1546
+ /** @type {unknown} */ (this.parseElement())
1547
+ );
1548
+ }
1549
+
1550
+ this.raise(
1551
+ this.start,
1552
+ 'JSX elements cannot be used as expressions. Wrap with `<tsx>...</tsx>` or use elements as statements within a component.',
1553
+ );
1554
+ }
1555
+
1507
1556
  /**
1508
1557
  * @type {Parse.Parser['parseElement']}
1509
1558
  */
@@ -1515,7 +1564,7 @@ function RipplePlugin(config) {
1515
1564
  const start = this.start - 1;
1516
1565
  const position = new acorn.Position(this.curLine, start - this.lineStart);
1517
1566
 
1518
- const element = /** @type {AST.Element | AST.TsxCompat} */ (this.startNode());
1567
+ const element = /** @type {AST.Element | AST.Tsx | AST.TsxCompat} */ (this.startNode());
1519
1568
  element.start = start;
1520
1569
  /** @type {AST.NodeWithLocation} */ (element).loc.start = position;
1521
1570
  element.metadata = { path: [] };
@@ -1530,6 +1579,8 @@ function RipplePlugin(config) {
1530
1579
 
1531
1580
  // Check if this is a namespaced element (tsx:react)
1532
1581
  const is_tsx_compat = open.name.type === 'JSXNamespacedName';
1582
+ const is_tsx =
1583
+ !is_tsx_compat && open.name.type === 'JSXIdentifier' && open.name.name === 'tsx';
1533
1584
 
1534
1585
  if (is_tsx_compat) {
1535
1586
  const namespace_node = /** @type {ESTreeJSX.JSXNamespacedName} */ (open.name);
@@ -1543,6 +1594,15 @@ function RipplePlugin(config) {
1543
1594
  `TSX compatibility elements cannot be self-closing. '<${tagName} />' must have a closing tag '</${tagName}>'.`,
1544
1595
  );
1545
1596
  }
1597
+ } else if (is_tsx) {
1598
+ /** @type {AST.Tsx} */ (element).type = 'Tsx';
1599
+
1600
+ if (open.selfClosing) {
1601
+ this.raise(
1602
+ open.start,
1603
+ `TSX elements cannot be self-closing. '<tsx />' must have a closing tag '</tsx>'.`,
1604
+ );
1605
+ }
1546
1606
  } else {
1547
1607
  element.type = 'Element';
1548
1608
  }
@@ -1570,7 +1630,7 @@ function RipplePlugin(config) {
1570
1630
  }
1571
1631
  }
1572
1632
 
1573
- if (!is_tsx_compat) {
1633
+ if (!is_tsx_compat && !is_tsx) {
1574
1634
  /** @type {AST.Element} */ (element).id = /** @type {AST.Identifier} */ (
1575
1635
  convert_from_jsx(/** @type {ESTreeJSX.JSXIdentifier} */ (open.name))
1576
1636
  );
@@ -1740,6 +1800,7 @@ function RipplePlugin(config) {
1740
1800
  const insideTemplate =
1741
1801
  parent?.type === 'Component' ||
1742
1802
  parent?.type === 'Element' ||
1803
+ parent?.type === 'Tsx' ||
1743
1804
  parent?.type === 'TsxCompat';
1744
1805
 
1745
1806
  if (curContext === tstc.tc_expr && !insideTemplate) {
@@ -1752,7 +1813,30 @@ function RipplePlugin(config) {
1752
1813
  this.parseTemplateBody(/** @type {AST.Element} */ (element).children);
1753
1814
  this.exitScope();
1754
1815
 
1755
- if (element.type === 'TsxCompat') {
1816
+ if (element.type === 'Tsx') {
1817
+ this.#path.pop();
1818
+
1819
+ if (!element.unclosed) {
1820
+ const raise_error = () => {
1821
+ this.raise(this.start, `Expected closing tag '</tsx>'`);
1822
+ };
1823
+
1824
+ this.next();
1825
+ // we should expect to see </tsx>
1826
+ if (this.value !== '/') {
1827
+ raise_error();
1828
+ }
1829
+ this.next();
1830
+ if (this.value !== 'tsx') {
1831
+ raise_error();
1832
+ }
1833
+ this.next();
1834
+ if (this.type !== tstt.jsxTagEnd) {
1835
+ raise_error();
1836
+ }
1837
+ this.next();
1838
+ }
1839
+ } else if (element.type === 'TsxCompat') {
1756
1840
  this.#path.pop();
1757
1841
 
1758
1842
  if (!element.unclosed) {
@@ -1808,6 +1892,7 @@ function RipplePlugin(config) {
1808
1892
  const insideTemplate =
1809
1893
  parent?.type === 'Component' ||
1810
1894
  parent?.type === 'Element' ||
1895
+ parent?.type === 'Tsx' ||
1811
1896
  parent?.type === 'TsxCompat';
1812
1897
 
1813
1898
  if (curContext === tstc.tc_expr && !insideTemplate) {
@@ -1815,7 +1900,7 @@ function RipplePlugin(config) {
1815
1900
  }
1816
1901
  }
1817
1902
 
1818
- if (element.closingElement && !is_tsx_compat) {
1903
+ if (element.closingElement && !is_tsx_compat && !is_tsx) {
1819
1904
  /** @type {unknown} */ (element.closingElement.name) = convert_from_jsx(
1820
1905
  element.closingElement.name,
1821
1906
  );
@@ -1831,6 +1916,7 @@ function RipplePlugin(config) {
1831
1916
  parseTemplateBody(body) {
1832
1917
  const inside_func =
1833
1918
  this.context.some((n) => n.token === 'function') || this.scopeStack.length > 1;
1919
+ const inside_tsx = this.#path.findLast((n) => n.type === 'Tsx');
1834
1920
  const inside_tsx_compat = this.#path.findLast((n) => n.type === 'TsxCompat');
1835
1921
 
1836
1922
  if (!inside_func) {
@@ -1842,6 +1928,75 @@ function RipplePlugin(config) {
1842
1928
  }
1843
1929
  }
1844
1930
 
1931
+ if (inside_tsx) {
1932
+ this.exprAllowed = true;
1933
+
1934
+ while (true) {
1935
+ if (this.type === tt.eof || this.pos >= this.input.length || this.type === tt.braceR) {
1936
+ if (!this.#loose) {
1937
+ this.raise(
1938
+ this.start,
1939
+ `Unclosed tag '<tsx>'. Expected '</tsx>' before end of component.`,
1940
+ );
1941
+ } else {
1942
+ inside_tsx.unclosed = true;
1943
+ /** @type {AST.NodeWithLocation} */ (inside_tsx).loc.end = {
1944
+ .../** @type {AST.SourceLocation} */ (inside_tsx.openingElement.loc).end,
1945
+ };
1946
+ inside_tsx.end = inside_tsx.openingElement.end;
1947
+ }
1948
+ return;
1949
+ }
1950
+
1951
+ if (this.input.slice(this.pos, this.pos + 4) === '/tsx') {
1952
+ const after = this.input.charCodeAt(this.pos + 4);
1953
+ // Make sure it's </tsx> and not </tsx:...>
1954
+ if (after === 62 /* > */) {
1955
+ return;
1956
+ }
1957
+ }
1958
+
1959
+ if (this.type === tt.braceL) {
1960
+ const node = this.jsx_parseExpressionContainer();
1961
+ body.push(node);
1962
+ } else if (this.type === tstt.jsxTagStart) {
1963
+ // Parse JSX element
1964
+ const node = super.parseExpression();
1965
+ body.push(node);
1966
+ } else {
1967
+ const start = this.start;
1968
+ this.pos = start;
1969
+ let text = '';
1970
+
1971
+ while (this.pos < this.input.length) {
1972
+ const ch = this.input.charCodeAt(this.pos);
1973
+
1974
+ // Stop at opening tag, expression, or the component-closing brace
1975
+ if (ch === 60 || ch === 123 || ch === 125) {
1976
+ // < or { or }
1977
+ break;
1978
+ }
1979
+
1980
+ text += this.input[this.pos];
1981
+ this.pos++;
1982
+ }
1983
+
1984
+ if (text) {
1985
+ const node = /** @type {ESTreeJSX.JSXText} */ ({
1986
+ type: 'JSXText',
1987
+ value: text,
1988
+ raw: text,
1989
+ start,
1990
+ end: this.pos,
1991
+ });
1992
+ body.push(node);
1993
+ }
1994
+
1995
+ // Always call next() to ensure parser makes progress
1996
+ this.next();
1997
+ }
1998
+ }
1999
+ }
1845
2000
  if (inside_tsx_compat) {
1846
2001
  this.exprAllowed = true;
1847
2002
 
@@ -1909,12 +2064,13 @@ function RipplePlugin(config) {
1909
2064
  if (this.type === tt.braceL) {
1910
2065
  const node = this.jsx_parseExpressionContainer();
1911
2066
  // Keep JSXEmptyExpression as-is (for prettier to handle comments)
1912
- // but convert other expressions to Text/Html nodes
2067
+ // but convert other expressions to Html/RippleExpression/Text nodes
1913
2068
  if (node.expression.type !== 'JSXEmptyExpression') {
1914
- /** @type {AST.Html | AST.TextNode} */ (/** @type {unknown} */ (node)).type = node.html
1915
- ? 'Html'
1916
- : 'Text';
2069
+ /** @type {AST.RippleExpression | AST.Html | AST.TextNode} */ (
2070
+ /** @type {unknown} */ (node)
2071
+ ).type = node.html ? 'Html' : node.text ? 'Text' : 'RippleExpression';
1917
2072
  delete node.html;
2073
+ delete node.text;
1918
2074
  }
1919
2075
  body.push(node);
1920
2076
  } else if (this.type === tt.braceR) {
@@ -1944,7 +2100,9 @@ function RipplePlugin(config) {
1944
2100
  const currentElement = this.#path[this.#path.length - 1];
1945
2101
  if (
1946
2102
  !currentElement ||
1947
- (currentElement.type !== 'Element' && currentElement.type !== 'TsxCompat')
2103
+ (currentElement.type !== 'Element' &&
2104
+ currentElement.type !== 'Tsx' &&
2105
+ currentElement.type !== 'TsxCompat')
1948
2106
  ) {
1949
2107
  this.raise(this.start, 'Unexpected closing tag');
1950
2108
  }
@@ -1960,6 +2118,12 @@ function RipplePlugin(config) {
1960
2118
  closingElement.name?.type === 'JSXNamespacedName'
1961
2119
  ? closingElement.name.namespace.name + ':' + closingElement.name.name.name
1962
2120
  : this.getElementName(closingElement.name);
2121
+ } else if (currentElement.type === 'Tsx') {
2122
+ openingTagName = 'tsx';
2123
+ closingTagName =
2124
+ closingElement.name?.type === 'JSXNamespacedName'
2125
+ ? closingElement.name.namespace.name + ':' + closingElement.name.name.name
2126
+ : this.getElementName(closingElement.name);
1963
2127
  } else {
1964
2128
  // Regular Element node
1965
2129
  openingTagName = this.getElementName(currentElement.id);
@@ -1981,12 +2145,16 @@ function RipplePlugin(config) {
1981
2145
  const elem = this.#path[this.#path.length - 1];
1982
2146
 
1983
2147
  // Stop at non-Element boundaries (Component, etc.)
1984
- if (elem.type !== 'Element' && elem.type !== 'TsxCompat') {
2148
+ if (elem.type !== 'Element' && elem.type !== 'Tsx' && elem.type !== 'TsxCompat') {
1985
2149
  break;
1986
2150
  }
1987
2151
 
1988
2152
  const elemName =
1989
- elem.type === 'TsxCompat' ? 'tsx:' + elem.kind : this.getElementName(elem.id);
2153
+ elem.type === 'TsxCompat'
2154
+ ? 'tsx:' + elem.kind
2155
+ : elem.type === 'Tsx'
2156
+ ? 'tsx'
2157
+ : this.getElementName(elem.id);
1990
2158
 
1991
2159
  // Found matching opening tag
1992
2160
  if (elemName === closingTagName) {
@@ -2050,12 +2218,16 @@ function RipplePlugin(config) {
2050
2218
  this.context.some((c) => c === tstc.tc_expr)
2051
2219
  ) {
2052
2220
  const node = this.jsx_parseExpressionContainer();
2053
- // Keep JSXEmptyExpression as-is (don't convert to Text)
2221
+ // Keep JSXEmptyExpression as-is (don't convert to RippleExpression/Text/Html)
2054
2222
  if (node.expression.type !== 'JSXEmptyExpression') {
2055
- /** @type {AST.TextNode} */ (/** @type {unknown} */ (node)).type = 'Text';
2223
+ /** @type {AST.RippleExpression | AST.Html | AST.TextNode} */ (
2224
+ /** @type {unknown} */ (node)
2225
+ ).type = node.html ? 'Html' : node.text ? 'Text' : 'RippleExpression';
2226
+ delete node.html;
2227
+ delete node.text;
2056
2228
  }
2057
2229
 
2058
- return /** @type {ESTreeJSX.JSXEmptyExpression | AST.TextNode | ESTreeJSX.JSXExpressionContainer} */ (
2230
+ return /** @type {ESTreeJSX.JSXEmptyExpression | AST.RippleExpression | AST.Html | AST.TextNode | ESTreeJSX.JSXExpressionContainer} */ (
2059
2231
  /** @type {unknown} */ (node)
2060
2232
  );
2061
2233
  }