ripple 0.3.68 → 0.3.69

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 (182) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/package.json +3 -3
  3. package/src/jsx-runtime.d.ts +2 -2
  4. package/src/runtime/element.js +1 -1
  5. package/src/runtime/index-client.js +11 -11
  6. package/src/runtime/index-server.js +7 -4
  7. package/src/runtime/internal/client/bindings.js +1 -1
  8. package/src/runtime/internal/client/blocks.js +13 -4
  9. package/src/runtime/internal/client/component.js +55 -0
  10. package/src/runtime/internal/client/composite.js +4 -2
  11. package/src/runtime/internal/client/expression.js +65 -7
  12. package/src/runtime/internal/client/hmr.js +54 -43
  13. package/src/runtime/internal/client/index.js +5 -1
  14. package/src/runtime/internal/client/portal.js +70 -69
  15. package/src/runtime/internal/client/render.js +3 -0
  16. package/src/runtime/internal/server/index.js +92 -8
  17. package/tests/client/__snapshots__/html.test.tsrx.snap +3 -3
  18. package/tests/client/array/array.copy-within.test.tsrx +33 -31
  19. package/tests/client/array/array.derived.test.tsrx +186 -169
  20. package/tests/client/array/array.iteration.test.tsrx +40 -37
  21. package/tests/client/array/array.mutations.test.tsrx +113 -101
  22. package/tests/client/array/array.static.test.tsrx +119 -101
  23. package/tests/client/array/array.to-methods.test.tsrx +24 -21
  24. package/tests/client/async-suspend.test.tsrx +247 -246
  25. package/tests/client/basic/__snapshots__/basic.rendering.test.tsrx.snap +0 -1
  26. package/tests/client/basic/basic.attributes.test.tsrx +428 -423
  27. package/tests/client/basic/basic.collections.test.tsrx +109 -102
  28. package/tests/client/basic/basic.components.test.tsrx +323 -205
  29. package/tests/client/basic/basic.errors.test.tsrx +91 -91
  30. package/tests/client/basic/basic.events.test.tsrx +114 -115
  31. package/tests/client/basic/basic.get-set.test.tsrx +97 -87
  32. package/tests/client/basic/basic.hmr.test.tsrx +19 -16
  33. package/tests/client/basic/basic.reactivity.test.tsrx +199 -191
  34. package/tests/client/basic/basic.rendering.test.tsrx +272 -182
  35. package/tests/client/basic/basic.styling.test.tsrx +23 -22
  36. package/tests/client/basic/basic.utilities.test.tsrx +10 -8
  37. package/tests/client/boundaries.test.tsrx +26 -26
  38. package/tests/client/compiler/__snapshots__/compiler.assignments.test.rsrx.snap +5 -5
  39. package/tests/client/compiler/__snapshots__/compiler.assignments.test.tsrx.snap +5 -5
  40. package/tests/client/compiler/compiler.assignments.test.tsrx +77 -81
  41. package/tests/client/compiler/compiler.attributes.test.tsrx +15 -15
  42. package/tests/client/compiler/compiler.basic.test.tsrx +322 -314
  43. package/tests/client/compiler/compiler.regex.test.tsrx +44 -47
  44. package/tests/client/compiler/compiler.tracked-access.test.tsrx +38 -38
  45. package/tests/client/compiler/compiler.try-in-function.test.tsrx +16 -16
  46. package/tests/client/compiler/compiler.typescript.test.tsrx +2 -2
  47. package/tests/client/composite/composite.dynamic-components.test.tsrx +47 -48
  48. package/tests/client/composite/composite.generics.test.tsrx +168 -192
  49. package/tests/client/composite/composite.props.test.tsrx +97 -81
  50. package/tests/client/composite/composite.reactivity.test.tsrx +177 -147
  51. package/tests/client/composite/composite.render.test.tsrx +122 -105
  52. package/tests/client/computed-properties.test.tsrx +28 -28
  53. package/tests/client/context.test.tsrx +21 -21
  54. package/tests/client/css/global-additional-cases.test.tsrx +58 -58
  55. package/tests/client/css/global-advanced-selectors.test.tsrx +16 -16
  56. package/tests/client/css/global-at-rules.test.tsrx +10 -10
  57. package/tests/client/css/global-basic.test.tsrx +14 -14
  58. package/tests/client/css/global-classes-ids.test.tsrx +14 -14
  59. package/tests/client/css/global-combinators.test.tsrx +10 -10
  60. package/tests/client/css/global-complex-nesting.test.tsrx +14 -14
  61. package/tests/client/css/global-edge-cases.test.tsrx +18 -18
  62. package/tests/client/css/global-keyframes.test.tsrx +12 -12
  63. package/tests/client/css/global-nested.test.tsrx +10 -10
  64. package/tests/client/css/global-pseudo.test.tsrx +12 -12
  65. package/tests/client/css/global-scoping.test.tsrx +20 -20
  66. package/tests/client/css/style-identifier.test.tsrx +143 -291
  67. package/tests/client/date.test.tsrx +146 -133
  68. package/tests/client/dynamic-elements.test.tsrx +398 -365
  69. package/tests/client/events.test.tsrx +292 -290
  70. package/tests/client/for.test.tsrx +156 -153
  71. package/tests/client/head.test.tsrx +105 -96
  72. package/tests/client/html.test.tsrx +122 -26
  73. package/tests/client/input-value.test.tsrx +1361 -1314
  74. package/tests/client/lazy-array.test.tsrx +16 -13
  75. package/tests/client/lazy-destructuring.test.tsrx +257 -213
  76. package/tests/client/map.test.tsrx +65 -60
  77. package/tests/client/media-query.test.tsrx +22 -20
  78. package/tests/client/object.test.tsrx +87 -81
  79. package/tests/client/portal.test.tsrx +57 -51
  80. package/tests/client/ref.test.tsrx +233 -202
  81. package/tests/client/return.test.tsrx +71 -2560
  82. package/tests/client/set.test.tsrx +54 -45
  83. package/tests/client/svg.test.tsrx +216 -186
  84. package/tests/client/switch.test.tsrx +194 -193
  85. package/tests/client/track-async-hydration.test.tsrx +18 -14
  86. package/tests/client/tracked-index-access.test.tsrx +28 -18
  87. package/tests/client/try.test.tsrx +675 -548
  88. package/tests/client/tsx.test.tsrx +373 -311
  89. package/tests/client/typescript-generics.test.tsrx +145 -145
  90. package/tests/client/url/url.derived.test.tsrx +33 -28
  91. package/tests/client/url/url.parsing.test.tsrx +61 -51
  92. package/tests/client/url/url.partial-removal.test.tsrx +56 -48
  93. package/tests/client/url/url.reactivity.test.tsrx +142 -125
  94. package/tests/client/url/url.serialization.test.tsrx +13 -11
  95. package/tests/client/url-search-params/url-search-params.derived.test.tsrx +34 -29
  96. package/tests/client/url-search-params/url-search-params.initialization.test.tsrx +25 -21
  97. package/tests/client/url-search-params/url-search-params.iteration.test.tsrx +50 -45
  98. package/tests/client/url-search-params/url-search-params.mutation.test.tsrx +111 -99
  99. package/tests/client/url-search-params/url-search-params.retrieval.test.tsrx +49 -43
  100. package/tests/client/url-search-params/url-search-params.serialization.test.tsrx +14 -12
  101. package/tests/client/url-search-params/url-search-params.tracked-url.test.tsrx +16 -14
  102. package/tests/hydration/basic.test.js +3 -3
  103. package/tests/hydration/compiled/client/basic.js +586 -651
  104. package/tests/hydration/compiled/client/composite.js +79 -104
  105. package/tests/hydration/compiled/client/events.js +140 -148
  106. package/tests/hydration/compiled/client/for.js +1005 -1018
  107. package/tests/hydration/compiled/client/head.js +124 -134
  108. package/tests/hydration/compiled/client/hmr.js +41 -48
  109. package/tests/hydration/compiled/client/html-in-template.js +38 -41
  110. package/tests/hydration/compiled/client/html.js +970 -1314
  111. package/tests/hydration/compiled/client/if-children.js +234 -249
  112. package/tests/hydration/compiled/client/if.js +182 -189
  113. package/tests/hydration/compiled/client/mixed-control-flow.js +347 -303
  114. package/tests/hydration/compiled/client/nested-control-flow.js +1084 -832
  115. package/tests/hydration/compiled/client/portal.js +65 -85
  116. package/tests/hydration/compiled/client/reactivity.js +84 -90
  117. package/tests/hydration/compiled/client/return.js +38 -1939
  118. package/tests/hydration/compiled/client/switch.js +218 -224
  119. package/tests/hydration/compiled/client/track-async-serialization.js +250 -259
  120. package/tests/hydration/compiled/client/try.js +123 -132
  121. package/tests/hydration/compiled/server/basic.js +773 -831
  122. package/tests/hydration/compiled/server/composite.js +166 -191
  123. package/tests/hydration/compiled/server/events.js +170 -184
  124. package/tests/hydration/compiled/server/for.js +851 -909
  125. package/tests/hydration/compiled/server/head.js +206 -216
  126. package/tests/hydration/compiled/server/hmr.js +64 -72
  127. package/tests/hydration/compiled/server/html-in-template.js +42 -76
  128. package/tests/hydration/compiled/server/html.js +1362 -1667
  129. package/tests/hydration/compiled/server/if-children.js +419 -445
  130. package/tests/hydration/compiled/server/if.js +194 -208
  131. package/tests/hydration/compiled/server/mixed-control-flow.js +249 -257
  132. package/tests/hydration/compiled/server/nested-control-flow.js +491 -515
  133. package/tests/hydration/compiled/server/portal.js +152 -160
  134. package/tests/hydration/compiled/server/reactivity.js +94 -106
  135. package/tests/hydration/compiled/server/return.js +28 -2172
  136. package/tests/hydration/compiled/server/switch.js +274 -286
  137. package/tests/hydration/compiled/server/track-async-serialization.js +340 -358
  138. package/tests/hydration/compiled/server/try.js +167 -185
  139. package/tests/hydration/components/basic.tsrx +320 -272
  140. package/tests/hydration/components/composite.tsrx +44 -32
  141. package/tests/hydration/components/events.tsrx +101 -91
  142. package/tests/hydration/components/for.tsrx +510 -452
  143. package/tests/hydration/components/head.tsrx +87 -80
  144. package/tests/hydration/components/hmr.tsrx +22 -17
  145. package/tests/hydration/components/html-in-template.tsrx +22 -17
  146. package/tests/hydration/components/html.tsrx +525 -443
  147. package/tests/hydration/components/if-children.tsrx +158 -148
  148. package/tests/hydration/components/if.tsrx +109 -95
  149. package/tests/hydration/components/mixed-control-flow.tsrx +100 -96
  150. package/tests/hydration/components/nested-control-flow.tsrx +215 -203
  151. package/tests/hydration/components/portal.tsrx +41 -34
  152. package/tests/hydration/components/reactivity.tsrx +37 -27
  153. package/tests/hydration/components/return.tsrx +12 -556
  154. package/tests/hydration/components/switch.tsrx +120 -114
  155. package/tests/hydration/components/track-async-serialization.tsrx +107 -91
  156. package/tests/hydration/components/try.tsrx +55 -40
  157. package/tests/hydration/html.test.js +4 -4
  158. package/tests/hydration/return.test.js +13 -532
  159. package/tests/server/await.test.tsrx +3 -3
  160. package/tests/server/basic.attributes.test.tsrx +264 -195
  161. package/tests/server/basic.components.test.tsrx +296 -169
  162. package/tests/server/basic.test.tsrx +300 -198
  163. package/tests/server/compiler.test.tsrx +62 -60
  164. package/tests/server/composite.props.test.tsrx +77 -63
  165. package/tests/server/composite.test.tsrx +168 -192
  166. package/tests/server/context.test.tsrx +18 -12
  167. package/tests/server/dynamic-elements.test.tsrx +197 -180
  168. package/tests/server/for.test.tsrx +85 -78
  169. package/tests/server/head.test.tsrx +50 -43
  170. package/tests/server/html-nesting-validation.test.tsrx +8 -8
  171. package/tests/server/if.test.tsrx +57 -51
  172. package/tests/server/lazy-destructuring.test.tsrx +366 -294
  173. package/tests/server/return.test.tsrx +76 -1355
  174. package/tests/server/streaming-ssr.test.tsrx +4 -75
  175. package/tests/server/style-identifier.test.tsrx +169 -148
  176. package/tests/server/switch.test.tsrx +91 -85
  177. package/tests/server/track-async-serialization.test.tsrx +105 -85
  178. package/tests/server/try.test.tsrx +374 -280
  179. package/tests/utils/compiler-compat-config.test.js +2 -2
  180. package/tests/utils/runtime-imports.test.js +10 -0
  181. package/types/index.d.ts +8 -0
  182. package/tests/client/__snapshots__/html.test.rsrx.snap +0 -40
@@ -1,8 +1,20 @@
1
1
  import { parse, compile, compile_to_volar_mappings } from '@tsrx/ripple';
2
- import { DIAGNOSTIC_CODES } from '@tsrx/core';
3
2
  import type * as AST from 'estree';
4
3
  import type * as ESTreeJSX from 'estree-jsx';
5
4
 
5
+ function get_returned_tsrx(node: any): any {
6
+ const target =
7
+ node.type === 'ExportNamedDeclaration' ? node.declaration : node;
8
+ const body =
9
+ target.type === 'VariableDeclarator' ? target.init.body : target.body;
10
+
11
+ if (body.type !== 'BlockStatement') {
12
+ return body;
13
+ }
14
+
15
+ return body.body.find((child: AST.Node) => child.type === 'ReturnStatement')?.argument;
16
+ }
17
+
6
18
  function count_occurrences(string: string, subString: string): number {
7
19
  let count = 0;
8
20
  let pos = string.indexOf(subString);
@@ -17,231 +29,247 @@ function count_occurrences(string: string, subString: string): number {
17
29
 
18
30
  describe('compiler > basics', () => {
19
31
  it('parses style content correctly', () => {
20
- const source = `export component App() {
32
+ const source = `export function App() { return <>
21
33
  <div id="myid" class="myclass">{"Hello World"}</div>
22
34
 
23
35
  <style>__STYLE__</style>
24
- }`;
36
+ </>; }`;
25
37
  const style1 = '.myid {color: green }';
26
38
  const style2 = '#myid {color: green }';
27
39
  const style3 = 'div {color: green }';
28
40
 
29
41
  let input = source.replace('__STYLE__', style1);
30
42
  let ast = parse(input);
31
- expect(
32
- ((ast.body[0] as AST.ExportNamedDeclaration).declaration as unknown as AST.Component)?.css.source,
33
- ).toEqual(style1);
43
+ expect(get_returned_tsrx(ast.body[0]).children.at(-1).children[0].source).toEqual(style1);
34
44
 
35
45
  input = source.replace('__STYLE__', style2);
36
46
  ast = parse(input);
37
- expect(
38
- ((ast.body[0] as AST.ExportNamedDeclaration).declaration as unknown as AST.Component)?.css.source,
39
- ).toEqual(style2);
47
+ expect(get_returned_tsrx(ast.body[0]).children.at(-1).children[0].source).toEqual(style2);
40
48
 
41
49
  input = source.replace('__STYLE__', style3);
42
50
  ast = parse(input);
43
- expect(
44
- ((ast.body[0] as AST.ExportNamedDeclaration).declaration as unknown as AST.Component)?.css.source,
45
- ).toEqual(style3);
51
+ expect(get_returned_tsrx(ast.body[0]).children.at(-1).children[0].source).toEqual(style3);
46
52
  });
47
53
 
48
- it('parses explicit text interpolation and reserves the text keyword', () => {
49
- const source = `export component App() {
54
+ it('parses text as an ordinary expression identifier', () => {
55
+ const source = `export function App() { return <>
50
56
  const markup = '<span>Not HTML</span>';
57
+ const text = markup;
51
58
 
52
59
  <div>{markup}</div>
53
- <div>{text markup}</div>
54
- }`;
60
+ <div>{text}</div>
61
+ </>; }`;
55
62
 
56
63
  const ast = parse(source);
57
- const component_node = (ast.body[0] as AST.ExportNamedDeclaration).declaration as unknown as AST.Component;
58
- const elements = component_node.body.filter((node) => node.type === 'Element') as AST.Element[];
64
+ const elements = get_returned_tsrx(ast.body[0]).children.filter(
65
+ (node: AST.Node) => node.type === 'Element',
66
+ ) as AST.Element[];
59
67
  const expression = elements[0].children[0] as AST.Node & { expression: AST.Expression };
60
- const explicit_text = elements[1].children[0] as AST.TextNode;
68
+ const text_expression = elements[1].children[0] as AST.Node & { expression: AST.Expression };
61
69
 
62
70
  expect(elements).toHaveLength(2);
63
71
  expect(expression.type).toBe('TSRXExpression');
64
72
  expect((expression.expression as AST.Identifier).name).toBe('markup');
65
- expect(explicit_text.type).toBe('Text');
66
- expect((explicit_text.expression as AST.Identifier).name).toBe('markup');
73
+ expect(text_expression.type).toBe('TSRXExpression');
74
+ expect((text_expression.expression as AST.Identifier).name).toBe('text');
67
75
 
68
76
  const { code } = compile(source, 'text-directive.tsrx', { mode: 'client' });
69
77
  expect(code).not.toContain('_$_.html');
70
78
 
71
- const invalid_source = `export component App() {
72
- const text = 'plain';
79
+ const invalid_source = `export function App() { return <>
80
+ const markup = 'plain';
73
81
 
74
- <div>{text}</div>
75
- }`;
82
+ <div>{text markup}</div>
83
+ </>; }`;
76
84
 
77
- expect(() => parse(invalid_source)).toThrow(
78
- '"text" is a TSRX keyword and must be used in the form {text some_value}',
79
- );
85
+ expect(() => parse(invalid_source)).toThrow();
80
86
  });
81
87
 
82
- it('parses backtick text inside fragments as JSX text', () => {
83
- const source = `let a = component () {
84
- <>
85
- \`333\`
86
- </>
88
+ it('optimizes string-shaped expressions as text nodes', () => {
89
+ const source = `export function App(
90
+ { title, props }: { title: string; props: { label: string } },
91
+ ) { return <>
92
+ const element = <span />;
93
+
94
+ <div>{String(title)}</div>
95
+ <div>{title + ''}</div>
96
+ <div>{title as string}</div>
97
+ <div>{title}</div>
98
+ <div>{props.label}</div>
99
+ <div>{element}</div>
100
+ </>; }`;
101
+
102
+ const { code } = compile(source, 'stringish-text.tsrx', { mode: 'client' });
103
+
104
+ expect(code).toContain('_$_.set_text');
105
+ expect(code).toContain('nodeValue = title');
106
+ expect(count_occurrences(code, '_$_.expression')).toBe(1);
107
+ });
108
+
109
+ it('parses backtick expressions inside TSRX fragments as template literals', () => {
110
+ const source = `let a = function() {
111
+ return <>
112
+ <>
113
+ \`333\`
114
+ </>
115
+ </>;
87
116
  }`;
88
117
 
89
118
  const ast = parse(source);
90
119
  const declaration = (ast.body[0] as AST.VariableDeclaration).declarations[0];
91
- const component_node = declaration.init as unknown as AST.Component;
92
- const fragment = component_node.body[0] as any;
120
+ const fragment = get_returned_tsrx(declaration).children[0] as any;
93
121
 
94
- expect(fragment.type).toBe('Tsx');
95
- expect(fragment.children[0].type).toBe('JSXText');
96
- expect(fragment.children[0].value).toContain('`333`');
122
+ expect(fragment.type).toBe('Tsrx');
123
+ expect(fragment.children[0].type).toBe('ExpressionStatement');
124
+ expect(fragment.children[0].expression.type).toBe('TemplateLiteral');
125
+ expect(fragment.children[0].expression.quasis[0].value.raw).toBe('333');
97
126
  });
98
127
 
99
- it('parses backtick text around JSX elements inside fragments', () => {
100
- const source = `let a = component () {
101
- <>
102
- \`
103
- <b></b>
104
- \`
105
- </>
128
+ it('parses backtick expressions around tag-like text inside TSRX fragments', () => {
129
+ const source = `let a = function() {
130
+ return <>
131
+ <>
132
+ \`
133
+ <b></b>
134
+ \`
135
+ </>
136
+ </>;
106
137
  }`;
107
138
 
108
139
  const ast = parse(source);
109
140
  const declaration = (ast.body[0] as AST.VariableDeclaration).declarations[0];
110
- const component_node = declaration.init as unknown as AST.Component;
111
- const fragment = component_node.body[0] as any;
141
+ const fragment = get_returned_tsrx(declaration).children[0] as any;
112
142
 
113
- expect(fragment.type).toBe('Tsx');
143
+ expect(fragment.type).toBe('Tsrx');
114
144
  expect(fragment.children.map((child: any) => child.type)).toEqual([
115
- 'JSXText',
116
- 'JSXElement',
117
- 'JSXText',
145
+ 'ExpressionStatement',
118
146
  ]);
119
- expect(fragment.children[0].value).toContain('`');
120
- expect(fragment.children[1].openingElement.name.name).toBe('b');
121
- expect(fragment.children[2].value).toContain('`');
147
+ expect(fragment.children[0].expression.type).toBe('TemplateLiteral');
148
+ expect(fragment.children[0].expression.quasis[0].value.raw).toContain('<b></b>');
122
149
  });
123
150
 
124
151
  it('renders without crashing', () => {
125
- component App() {
126
- let foo: Record<string, number>;
127
- let bar: Record<string, number>;
128
- let baz: Record<string, number>;
129
-
130
- foo = {};
131
- foo = { test: 0 };
132
- foo['abc'] = 123;
133
-
134
- bar = { def: 456 };
135
-
136
- baz = { ghi: 789 };
137
- baz['jkl'] = 987;
152
+ function App() {
153
+ return <>
154
+ let foo: Record<string, number>;
155
+ let bar: Record<string, number>;
156
+ let baz: Record<string, number>;
157
+ foo = {};
158
+ foo = { test: 0 };
159
+ foo['abc'] = 123;
160
+ bar = { def: 456 };
161
+ baz = { ghi: 789 };
162
+ baz['jkl'] = 987;
163
+ </>;
138
164
  }
139
165
 
140
166
  render(App);
141
167
  });
142
168
 
143
169
  it('renders without crashing using < character', () => {
144
- component App() {
145
- function bar() {
146
- for (let i = 0; i < 10; i++) {
147
- // do nothing
170
+ function App() {
171
+ return <>
172
+ function bar() {
173
+ for (let i = 0; i < 10; i++) {
174
+ // do nothing
175
+ }
176
+ const x = 1 < 1;
148
177
  }
149
- const x = 1 < 1;
150
- }
151
-
152
- let x = 5 < 10;
153
-
154
- <div>{x}</div>
178
+ let x = 5 < 10;
179
+ <div>{x}</div>
180
+ </>;
155
181
  }
156
182
 
157
183
  render(App);
158
184
  });
159
185
 
160
186
  it('renders lexical blocks without crashing', () => {
161
- component App() {
162
- <div>
163
- const a = 1;
164
- <div>
165
- const b = 1;
166
- </div>
187
+ function App() {
188
+ return <>
167
189
  <div>
168
- const b = 1;
190
+ const a = 1;
191
+ <div>
192
+ const b = 1;
193
+ </div>
194
+ <div>
195
+ const b = 1;
196
+ </div>
169
197
  </div>
170
- </div>
171
- <div>
172
- const a = 2;
173
198
  <div>
174
- const b = 1;
199
+ const a = 2;
200
+ <div>
201
+ const b = 1;
202
+ </div>
175
203
  </div>
176
- </div>
204
+ </>;
177
205
  }
178
206
 
179
207
  render(App);
180
208
  });
181
209
 
182
210
  it('renders without crashing using mapped types', () => {
183
- component App() {
184
- type RecordKey = 'test';
185
- type RecordValue = { a: string; b: number };
186
-
187
- const config: Record<RecordKey, RecordValue> = {
188
- test: {
189
- a: 'test',
190
- b: 1,
191
- },
192
- };
193
-
194
- const config2: { [key in RecordKey]: RecordValue } = {
195
- test: {
196
- a: 'test2',
197
- b: 2,
198
- },
199
- };
200
-
201
- const config3: { [key: string]: RecordValue } = {
202
- test: {
203
- a: 'test3',
204
- b: 3,
205
- },
206
- };
211
+ function App() {
212
+ return <>
213
+ type RecordKey = 'test';
214
+ type RecordValue = { a: string; b: number };
215
+ const config: Record<RecordKey, RecordValue> = {
216
+ test: {
217
+ a: 'test',
218
+ b: 1,
219
+ },
220
+ };
221
+ const config2: { [key in RecordKey]: RecordValue } = {
222
+ test: {
223
+ a: 'test2',
224
+ b: 2,
225
+ },
226
+ };
227
+ const config3: { [key: string]: RecordValue } = {
228
+ test: {
229
+ a: 'test3',
230
+ b: 3,
231
+ },
232
+ };
233
+ </>;
207
234
  }
208
235
 
209
236
  render(App);
210
237
  });
211
238
 
212
239
  it('renders without crashing using object destructuring', () => {
213
- component App() {
214
- const obj = { a: 1, b: 2, c: 3 };
215
- const { a, b, ...rest } = obj;
240
+ function App() {
241
+ return <>
242
+ const obj = { a: 1, b: 2, c: 3 };
243
+ const { a, b, ...rest } = obj;
244
+ <div>
245
+ {'a '}
246
+ {a}
247
+ {'b '}
248
+ {b}
249
+ {'rest '}
250
+ {JSON.stringify(rest)}
251
+
252
+ <div />
253
+ </div>
254
+ </>;
255
+ }
216
256
 
217
- <div>
257
+ render(App);
258
+ });
259
+
260
+ it('renders without crashing using object destructuring #2', () => {
261
+ function App() {
262
+ return <>
263
+ const obj = { a: 1, b: 2, c: 3 };
264
+ const { a, b, ...rest } = obj;
218
265
  {'a '}
219
266
  {a}
220
267
  {'b '}
221
268
  {b}
222
269
  {'rest '}
223
270
  {JSON.stringify(rest)}
224
-
225
271
  <div />
226
- </div>
227
- }
228
-
229
- render(App);
230
- });
231
-
232
- it('renders without crashing using object destructuring #2', () => {
233
- component App() {
234
- const obj = { a: 1, b: 2, c: 3 };
235
- const { a, b, ...rest } = obj;
236
-
237
- {'a '}
238
- {a}
239
- {'b '}
240
- {b}
241
- {'rest '}
242
- {JSON.stringify(rest)}
243
-
244
- <div />
272
+ </>;
245
273
  }
246
274
 
247
275
  render(App);
@@ -260,39 +288,35 @@ describe('compiler > basics', () => {
260
288
  };
261
289
  }
262
290
 
263
- component App() {
264
- let x: number[] = [] as number[];
265
-
266
- const n = Wrapper<number>().unwrap<string>();
267
-
268
- const tagResult = tagFn`value`;
269
-
270
- interface Node<T> {
271
- value: T;
272
- }
273
-
274
- class Box<T> {
275
- value: T;
276
-
277
- method<U extends T>(): U {
278
- return this.value as U;
279
- }
280
-
281
- constructor(value: T) {
282
- this.value = value;
291
+ function App() {
292
+ return <>
293
+ let x: number[] = [] as number[];
294
+ const n = Wrapper<number>().unwrap<string>();
295
+ const tagResult = tagFn`value`;
296
+ interface Node<T> {
297
+ value: T;
283
298
  }
284
- }
299
+ class Box<T> {
300
+ value: T;
285
301
 
286
- let flag = true;
302
+ method<U extends T>(): U {
303
+ return this.value as U;
304
+ }
287
305
 
288
- const s = flag ? new Box<number>(1) : new Box<string>('string');
306
+ constructor(value: T) {
307
+ this.value = value;
308
+ }
309
+ }
310
+ let flag = true;
311
+ const s = flag ? new Box<number>(1) : new Box<string>('string');
312
+ </>;
289
313
  }
290
314
 
291
315
  render(App);
292
316
  });
293
317
 
294
318
  it('compiles without needing semicolons between statements and JSX', () => {
295
- const source = `export component App() {
319
+ const source = `export function App() { return <>
296
320
  <div>const code4 = 4
297
321
 
298
322
  const code3 = 3
@@ -303,56 +327,56 @@ describe('compiler > basics', () => {
303
327
  const code2 = 2
304
328
  </div>
305
329
  </div>
306
- }`;
330
+ </>; }`;
307
331
 
308
332
  const result = compile(source, 'test.tsrx', { mode: 'client' });
309
333
  });
310
334
 
311
- it('calculates fragment hop count after return-guard grouping', () => {
312
- const source = `export component App() {
313
- let stop = false;
314
- if (stop) {
315
- return;
316
- }
335
+ it('calculates fragment hop count for sibling fragments', () => {
336
+ const source = `export function App() { return <>
317
337
  <div class="a">{'a'}</div>
318
338
  <div class="b">{'b'}</div>
319
- }`;
339
+ </>; }`;
320
340
 
321
341
  const { code } = compile(source, 'grouped-count.tsrx', { mode: 'client' });
322
342
 
323
- expect(code).toMatch(/_\$_\.template\(`<!><!>`,\s*1,\s*2\)/);
324
- expect(code).not.toMatch(/_\$_\.template\(`<!><!>`,\s*1,\s*3\)/);
343
+ expect(code).toMatch(/_\$_\.template\([\s\S]*,\s*1,\s*2\)/);
344
+ expect(code).not.toMatch(/_\$_\.template\([\s\S]*,\s*1,\s*3\)/);
325
345
  });
326
346
 
327
347
  it('emits anonymous component expressions as arrows in client output', () => {
328
348
  const source = `
329
- const Inline = component(props) => {
349
+ const Inline = (props) => <>
330
350
  <div>{props.x}</div>
331
- }
351
+ </>
332
352
  `;
333
353
  const result = compile(source, 'anonymous-component.tsrx', { mode: 'client' }).code;
334
354
 
335
- expect(result).toContain('const Inline = (__anchor, props, __block) => {');
355
+ expect(result).toContain('const Inline = (props) => {');
356
+ expect(result).toContain('(props) => {');
357
+ expect(result).toContain('return _$_.tsrx_element((__anchor, __block) =>');
336
358
  expect(result).not.toContain('function Inline');
337
359
  expect(result).not.toContain('function (__anchor');
338
360
  });
339
361
 
340
- it('emits legacy anonymous component expressions as functions in client output', () => {
362
+ it('emits function-expression components as functions in client output', () => {
341
363
  const source = `
342
- const Inline = component(props) {
364
+ const Inline = function(props) { return <>
343
365
  <div>{props.x}</div>
344
- }
366
+ </>; }
345
367
  `;
346
368
  const result = compile(source, 'anonymous-component.tsrx', { mode: 'client' }).code;
347
369
 
348
- expect(result).toContain('const Inline = function (__anchor, props, __block) {');
370
+ expect(result).toContain('const Inline = function (props) {');
371
+ expect(result).toContain('function (props) {');
372
+ expect(result).toContain('return _$_.tsrx_element((__anchor, __block) =>');
349
373
  expect(result).not.toContain('function Inline');
350
374
  expect(result).not.toContain('const Inline = (__anchor, props, __block) => {');
351
375
  });
352
376
 
353
377
  it('emits function calls with nested template returns as expressions in client output', () => {
354
378
  const source = `
355
- component App() {
379
+ function App() { return <>
356
380
  function make(flag) {
357
381
  if (flag) {
358
382
  return <tsx><span>{'nested'}</span></tsx>;
@@ -362,7 +386,7 @@ component App() {
362
386
  }
363
387
 
364
388
  <div>{make(true)}</div>
365
- }
389
+ </>; }
366
390
  `;
367
391
  const result = compile(source, 'nested-template-return.tsrx', { mode: 'client' }).code;
368
392
 
@@ -374,13 +398,13 @@ component App() {
374
398
  // () => {
375
399
  // const source = `
376
400
  // import { RippleArray, RippleObject, RippleSet, RippleMap, createRefKey } from 'ripple';
377
- // component App() {
401
+ // function App() { return <>
378
402
  // const items = new RippleArray(1, 2, 3);
379
403
  // const obj = new RippleObject({ a: 1, b: 2, c: 3 });
380
404
  // const set = RippleSet([1, 2, 3]);
381
405
  // const map = RippleMap([['a', 1], ['b', 2], ['c', 3]]);
382
406
 
383
- // <div {ref () => {}} />
407
+ // <div ref={() => {}} />
384
408
  // }
385
409
  // `;
386
410
  // const result = compile_to_volar_mappings(source, 'test.tsrx').code;
@@ -398,13 +422,13 @@ component App() {
398
422
  // () => {
399
423
  // const source = `
400
424
  // import { RippleArray as TA, RippleObject as TO, RippleSet as TS, RippleMap as TM, createRefKey as crk } from 'ripple';
401
- // component App() {
425
+ // function App() { return <>
402
426
  // const items = new RippleArray(1, 2, 3);
403
427
  // const obj = new RippleObject({ a: 1, b: 2, c: 3 });
404
428
  // const set = RippleSet([1, 2, 3]);
405
429
  // const map = RippleMap([['a', 1], ['b', 2], ['c', 3]]);
406
430
 
407
- // <div {ref () => {}} />
431
+ // <div ref={() => {}} />
408
432
  // }
409
433
  // `;
410
434
  // const result = compile_to_volar_mappings(source, 'test.tsrx').code;
@@ -424,13 +448,13 @@ component App() {
424
448
 
425
449
  // it('adds hidden obfuscated imports for shorthand syntax', () => {
426
450
  // const source = `
427
- // component App() {
451
+ // function App() { return <>
428
452
  // const items = new RippleArray(1, 2, 3);
429
453
  // const obj = new RippleObject({ a: 1, b: 2, c: 3 });
430
454
  // const set = RippleSet([1, 2, 3]);
431
455
  // const map = RippleMap([['a', 1], ['b', 2], ['c', 3]]);
432
456
 
433
- // <div {ref () => {}} />
457
+ // <div ref={() => {}} />
434
458
  // }
435
459
  // `;
436
460
  // const result = compile_to_volar_mappings(source, 'test.tsrx').code;
@@ -445,7 +469,7 @@ component App() {
445
469
  it('prints longhand tracked property values in to_ts output while preserving [\'#v\']', () => {
446
470
  const source = `
447
471
  import { RippleArray, RippleMap, RippleObject, RippleSet, createRefKey, effect, track, untrack } from 'ripple';
448
- component App() {
472
+ function App() { return <>
449
473
  let value = track('test');
450
474
  function inputRef(node) {}
451
475
 
@@ -454,7 +478,7 @@ component App() {
454
478
  value: value.value,
455
479
  [createRefKey()]: inputRef,
456
480
  };
457
- }
481
+ </>; }
458
482
  `;
459
483
 
460
484
  const result = compile_to_volar_mappings(source, 'test.tsrx').code;
@@ -465,10 +489,10 @@ component App() {
465
489
  it('keeps lazy destructuring as plain destructuring in to_ts output', () => {
466
490
  const track_source = `
467
491
  import { track } from 'ripple';
468
- component App() {
492
+ function App() { return <>
469
493
  let &[value, ...rest] = track(0);
470
494
  const x = value;
471
- }
495
+ </>; }
472
496
  `;
473
497
  const track_result = compile_to_volar_mappings(track_source, 'test.tsrx').code;
474
498
  expect(track_result).toContain('let [value, ...rest] = track(0);');
@@ -479,49 +503,39 @@ component App() {
479
503
  expect(track_result).not.toContain('lazy0');
480
504
  });
481
505
 
482
- it('lowers nested tsrx inside tsx in to_ts output', () => {
506
+ it('lowers native expression values in to_ts output', () => {
483
507
  const source = `
484
- component App() {
485
- const content = <tsx>
486
- {<tsrx>
487
- const nested = <tsx>
488
- <span class="nested-tsx">
489
- {'inside nested tsx'}
490
- </span>
491
- </tsx>;
492
- <div class="native">{nested}</div>
493
- </tsrx>}
508
+ function App() { return <>
509
+ const nested = <tsx>
510
+ <span class="nested-tsx">
511
+ {'inside nested tsx'}
512
+ </span>
494
513
  </tsx>;
514
+ const content = <div class="native">{nested}</div>;
495
515
 
496
516
  {content}
497
- }
517
+ </>; }
498
518
  `;
499
519
  const result = compile_to_volar_mappings(source, 'test.tsrx').code;
500
520
 
501
- expect(result).not.toContain('<tsrx>');
502
- expect(result).not.toContain('</tsrx>');
503
521
  expect(result).not.toContain('<tsx>');
504
522
  expect(result).not.toContain('</tsx>');
505
523
  expect(result).toContain('const nested = <>');
506
- expect(result).toContain('children.push(<div class="native">');
524
+ expect(result).toContain('const content = <div class="native">');
507
525
  });
508
526
 
509
- it('maps identifiers from nested tsrx inside tsx in to_ts output', () => {
527
+ it('maps identifiers from native expression values in to_ts output', () => {
510
528
  const source = `
511
- component App() {
512
- const content = <tsx>
513
- {<tsrx>
514
- const nested = <tsx>
515
- <span class="nested-tsx">
516
- {'inside nested tsx'}
517
- </span>
518
- </tsx>;
519
- <div class="native">{nested}</div>
520
- </tsrx>}
529
+ function App() { return <>
530
+ const nested = <tsx>
531
+ <span class="nested-tsx">
532
+ {'inside nested tsx'}
533
+ </span>
521
534
  </tsx>;
535
+ const content = <div class="native">{nested}</div>;
522
536
 
523
537
  {content}
524
- }
538
+ </>; }
525
539
  `;
526
540
  const result = compile_to_volar_mappings(source, 'test.tsrx', { loose: true });
527
541
  const source_declaration = source.indexOf('nested =');
@@ -568,33 +582,33 @@ type Props<Item> = {
568
582
  items: readonly Item[];
569
583
  }
570
584
 
571
- export component MyComponent<Item>(props: Props<Item>) {
585
+ export function MyComponent<Item>(props: Props<Item>) { return <>
572
586
  <div />
573
- }
587
+ </>; }
574
588
  `;
575
589
  const result = compile_to_volar_mappings(source, 'test.tsrx').code;
576
590
 
577
591
  expect(result).toContain('export function MyComponent<Item>(props: Props<Item>)');
578
592
  });
579
593
 
580
- it('emits anonymous component expressions as arrows in to_ts output', () => {
594
+ it('preserves arrow functions that return TSRX in to_ts output', () => {
581
595
  const source = `
582
- const Inline = component(props: { x: string }) => {
596
+ const Inline = (props: { x: string }) => <>
583
597
  <div>{props.x}</div>
584
- }
598
+ </>
585
599
  `;
586
600
  const result = compile_to_volar_mappings(source, 'test.tsrx').code;
587
601
 
588
- expect(result).toContain('const Inline = (props: { x: string }) => {');
602
+ expect(result).toContain('const Inline = (props: { x: string }) => <div>');
589
603
  expect(result).not.toContain('function Inline');
590
604
  expect(result).not.toContain('function (props');
591
605
  });
592
606
 
593
- it('emits legacy anonymous component expressions as functions in to_ts output', () => {
607
+ it('preserves function expressions that return TSRX in to_ts output', () => {
594
608
  const source = `
595
- const Inline = component(props: { x: string }) {
609
+ const Inline = function(props: { x: string }) { return <>
596
610
  <div>{props.x}</div>
597
- }
611
+ </>; }
598
612
  `;
599
613
  const result = compile_to_volar_mappings(source, 'test.tsrx').code;
600
614
 
@@ -607,13 +621,13 @@ const Inline = component(props: { x: string }) {
607
621
  const source = `
608
622
  type User = { name: string };
609
623
 
610
- component RenderProp<Item>(props: { children: (item: Item) => any }) {}
624
+ function RenderProp<Item>(props: { children: (item: Item) => any }) { return <></>; }
611
625
 
612
- export component App() {
626
+ export function App() { return <>
613
627
  <RenderProp<User>>
614
628
  {(item) => item.name}
615
629
  </RenderProp>
616
- }
630
+ </>; }
617
631
  `;
618
632
  const result = compile_to_volar_mappings(source, 'test.tsrx').code;
619
633
 
@@ -622,13 +636,13 @@ export component App() {
622
636
 
623
637
  it('preserves generic type arguments on self-closing JSX component tags in to_ts output', () => {
624
638
  const source = `
625
- component Box<T>({ value }: { value: T }) {
639
+ function Box<T>({ value }: { value: T }) { return <>
626
640
  <div>{String(value)}</div>
627
- }
641
+ </>; }
628
642
 
629
- export component App() {
643
+ export function App() { return <>
630
644
  <Box<string> value="hi" />
631
- }
645
+ </>; }
632
646
  `;
633
647
  const result = compile_to_volar_mappings(source, 'test.tsrx').code;
634
648
 
@@ -728,9 +742,9 @@ interface Props extends PolymorphicProps<'div'> {
728
742
  id: string;
729
743
  }
730
744
 
731
- export component App(props: Props) {
745
+ export function App(props: Props) { return <>
732
746
  <div id={props.id} />
733
- }
747
+ </>; }
734
748
  `;
735
749
 
736
750
  const result = compile_to_volar_mappings(source, 'test.tsrx').code;
@@ -741,7 +755,7 @@ export component App(props: Props) {
741
755
  it('handles if-else expression statements in Volar mappings', () => {
742
756
  const source = `
743
757
  import { track } from 'ripple';
744
- export component App() {
758
+ export function App() { return <>
745
759
  let &[level] = track(1);
746
760
 
747
761
  <button
@@ -753,7 +767,7 @@ export component App() {
753
767
  >
754
768
  {'Toggle'}
755
769
  </button>
756
- }
770
+ </>; }
757
771
  `;
758
772
 
759
773
  expect(() => compile_to_volar_mappings(source, 'test.tsrx')).not.toThrow();
@@ -761,66 +775,64 @@ export component App() {
761
775
 
762
776
  it('should not error on having js below markup in the same scope', () => {
763
777
  const code = `
764
- component Card(props) {
778
+ function Card(props) { return <>
765
779
  <div class="card">
766
780
  {props.children}
767
781
  </div>
768
- }
782
+ </>; }
769
783
 
770
- export component App() {
771
- component children() {
784
+ export function App() { return <>
785
+ function children() { return <>
772
786
  <p>{'Card content here'}</p>
773
- }
787
+ </>; }
774
788
 
775
789
  <Card {children} />
776
790
 
777
791
  const test = 5;
778
792
 
779
793
  <div>{test}</div>
780
- }
794
+ </>; }
781
795
  `;
782
796
  expect(() => compile(code, 'test.tsrx')).not.toThrow();
783
797
  });
784
798
 
785
- it('allows component declarations inside composite children', () => {
799
+ it('allows component functions inside composite children', () => {
786
800
  const source = `
787
- export component App() {
801
+ export function App() { return <>
788
802
  <ark.div class="host-class" data-value="42">
789
- component asChild({ children, href, ...rest }: { href: string; [key: string]: any }) {
803
+ function asChild({ children, href, ...rest }: { href: string; [key: string]: any }) { return <>
790
804
  <a id="aschild-anchor" {href} {...rest} data-extra="yes">{'Link'}</a>
791
- }
805
+ </>; }
792
806
  </ark.div>
793
- }
807
+ </>; }
794
808
  `;
795
809
 
796
810
  expect(() => compile_to_volar_mappings(source, 'test.tsrx')).not.toThrow();
797
811
  });
798
812
 
799
- it('rejects parent element attributes referencing child-declared components', () => {
813
+ it('allows parent element attributes referencing child-declared functions', () => {
800
814
  const source = `
801
- export component App() {
815
+ export function App() { return <>
802
816
  <Test {Z}>
803
- component Z() {
817
+ function Z() { return <>
804
818
  <div>{'hello'}</div>
805
- }
819
+ </>; }
806
820
  </Test>
807
- }
821
+ </>; }
808
822
  `;
809
823
 
810
- expect(() => compile(source, 'test.tsrx')).toThrow(
811
- /Cannot use component 'Z' as a prop on its parent element/,
812
- );
824
+ expect(() => compile(source, 'test.tsrx')).not.toThrow();
813
825
  });
814
826
 
815
827
  it('preserves explicit component props in Volar mappings', () => {
816
828
  const source = `
817
- export component App() {
818
- component asChild({ children, href, ...rest }: { href: string; [key: string]: any }) {
829
+ export function App() { return <>
830
+ function asChild({ children, href, ...rest }: { href: string; [key: string]: any }) { return <>
819
831
  <a id="aschild-anchor" {href} {...rest} data-extra="yes">{'Link'}</a>
820
- }
832
+ </>; }
821
833
 
822
834
  <ark.div class="host-class" data-value="42" {asChild} />
823
- }
835
+ </>; }
824
836
  `;
825
837
  const result = compile_to_volar_mappings(source, 'test.tsrx').code;
826
838
 
@@ -830,17 +842,17 @@ export component App() {
830
842
 
831
843
  it('merges explicit children prop with implicit children in client output', () => {
832
844
  const source = `
833
- component Card(props) {
845
+ function Card(props) { return <>
834
846
  <div>{props.children}</div>
835
- }
847
+ </>; }
836
848
 
837
- export component App() {
849
+ export function App() { return <>
838
850
  const fallback = 'fallback';
839
851
 
840
852
  <Card children={fallback}>
841
853
  <span>{'content'}</span>
842
854
  </Card>
843
- }
855
+ </>; }
844
856
  `;
845
857
 
846
858
  const result = compile(source, 'test.tsrx', { mode: 'client' }).code;
@@ -859,10 +871,10 @@ class Test {
859
871
  }
860
872
  }
861
873
 
862
- export component App() {
874
+ export function App() { return <>
863
875
  const test = new Test();
864
876
  <div>{test.count}</div>
865
- }
877
+ </>; }
866
878
  `;
867
879
  expect(() => compile(code, 'test.tsrx')).not.toThrow();
868
880
  });
@@ -878,10 +890,10 @@ class Store {
878
890
  }
879
891
  }
880
892
 
881
- export component App() {
893
+ export function App() { return <>
882
894
  const store = new Store();
883
895
  <div>{store.count}</div>
884
- }
896
+ </>; }
885
897
  `;
886
898
  const result = compile(source, 'test.tsrx', { mode: 'client' });
887
899
  const code = result.code;
@@ -895,14 +907,14 @@ export component App() {
895
907
  const source = `
896
908
  import { track, effect, untrack } from 'ripple';
897
909
 
898
- component App() {
910
+ function App() { return <>
899
911
  let &[count] = track(0);
900
912
 
901
913
  effect(() => {
902
914
  const snapshot = untrack(() => count);
903
915
  console.log(snapshot);
904
916
  });
905
- }
917
+ </>; }
906
918
  `;
907
919
 
908
920
  const ast = parse(source);
@@ -915,10 +927,10 @@ component App() {
915
927
  it('collects duplicate declaration parser errors in loose mode', () => {
916
928
  const source = `
917
929
  import { track } from 'ripple';
918
- export component App() {
930
+ export function App() { return <>
919
931
  let test = track(false);
920
932
  let test = 'hey';
921
- }
933
+ </>; }
922
934
  `;
923
935
 
924
936
  expect(() => compile(source, 'test.tsrx')).toThrow(
@@ -944,11 +956,11 @@ export component App() {
944
956
  import { loadUser as getUser } from server;
945
957
  import { loadUser } from server;
946
958
 
947
- component App() {
959
+ function App() { return <>
948
960
  const user = getUser();
949
961
  const user2 = loadUser();
950
962
  <div>{user.id}{user2.id}</div>
951
- }`;
963
+ </>; }`;
952
964
  const result = compile_to_volar_mappings(source, 'test.tsrx', { loose: true });
953
965
  const generated_member = '_$_server_$_.loadUser';
954
966
  const generated_member_offset = result.code.indexOf(generated_member);
@@ -1004,30 +1016,27 @@ component App() {
1004
1016
  ).toBeDefined();
1005
1017
  });
1006
1018
 
1007
- it('collects volar parser diagnostics outside loose mode', () => {
1008
- const result = compile_to_volar_mappings(`component App() {
1019
+ it('reports return statements inside returned TSRX fragments in Volar mappings', () => {
1020
+ const result = compile_to_volar_mappings(
1021
+ `function App() { return <>
1009
1022
  return <div />;
1010
- }`, 'test.tsrx');
1011
-
1012
- expect(result.errors.map((error) => error.code)).toContain(
1013
- DIAGNOSTIC_CODES.JSX_RETURN_IN_COMPONENT,
1023
+ </>; }`,
1024
+ 'test.tsrx',
1014
1025
  );
1026
+
1027
+ expect(result.errors.map((error) => error.code)).toEqual(['tsrx-template-return-statement']);
1015
1028
  });
1016
1029
 
1017
1030
  it('throws for unclosed tsx compat tags instead of hanging', () => {
1018
- const source = `export component App() {
1019
- <tsx:react>1
1020
- }`;
1031
+ const source = `export function App() { return <tsx:react>1; }`;
1021
1032
 
1022
1033
  expect(() => compile(source, 'test.tsrx')).toThrow(
1023
- 'Unclosed tag \'<tsx:react>\'. Expected \'</tsx:react>\' before end of component.',
1034
+ 'Unclosed tag \'<tsx:react>\'. Expected \'</tsx:react>\' before end of template.',
1024
1035
  );
1025
1036
  });
1026
1037
 
1027
1038
  it('recovers unclosed tsx compat tags in loose mode', () => {
1028
- const source = `export component App() {
1029
- <tsx:react>1
1030
- }`;
1039
+ const source = `export function App() { return <tsx:react>1; }`;
1031
1040
 
1032
1041
  expect(() => compile_to_volar_mappings(source, 'test.tsrx', { loose: true })).not.toThrow();
1033
1042
 
@@ -1035,14 +1044,11 @@ component App() {
1035
1044
  expect(result.errors).toEqual([]);
1036
1045
  });
1037
1046
 
1038
- it('collects unclosed tsx compat tags outside loose mode', () => {
1039
- const source = `export component App() {
1040
- <tsx:react>1
1041
- }`;
1047
+ it('throws for unclosed tsx compat tags outside loose mode', () => {
1048
+ const source = `export function App() { return <tsx:react>1; }`;
1042
1049
 
1043
- const result = compile(source, 'test.tsrx', { collect: true });
1044
- expect(result.errors.map((error) => error.message)).toContain(
1045
- 'Unclosed tag \'<tsx:react>\'. Expected \'</tsx:react>\' before end of component.',
1050
+ expect(() => compile(source, 'test.tsrx', { collect: true })).toThrow(
1051
+ 'Not implemented: TsxCompat',
1046
1052
  );
1047
1053
  });
1048
1054
 
@@ -1052,7 +1058,7 @@ import { track } from 'ripple';
1052
1058
 
1053
1059
  const outside = track(0);
1054
1060
 
1055
- export component App() {}
1061
+ export function App() { return <></>; }
1056
1062
  `;
1057
1063
 
1058
1064
  const result = compile(source, 'test.tsrx', { collect: true });
@@ -1063,13 +1069,13 @@ export component App() {}
1063
1069
 
1064
1070
  it('does not let nested for...of continues satisfy an outer if body', () => {
1065
1071
  const source = `
1066
- export component App({ items }: { items: string[] }) {
1072
+ export function App({ items }: { items: string[] }) { return <>
1067
1073
  if (items.length) {
1068
1074
  for (const item of items) {
1069
1075
  if (!item) continue
1070
1076
  }
1071
1077
  }
1072
- }`;
1078
+ </>; }`;
1073
1079
 
1074
1080
  const result = compile(source, 'test.tsrx', { collect: true });
1075
1081
  expect(result.errors.map((error) => error.message)).toContain(
@@ -1079,7 +1085,7 @@ export component App({ items }: { items: string[] }) {
1079
1085
 
1080
1086
  it('preserves class extends generic type arguments in volar output', () => {
1081
1087
  const source = `class StringMap extends Map<string, string> {}
1082
- export component App() {}`;
1088
+ export function App() { return <></>; }`;
1083
1089
 
1084
1090
  expect(() => compile_to_volar_mappings(source, 'test.tsrx', { loose: true })).not.toThrow();
1085
1091
 
@@ -1090,15 +1096,15 @@ export component App() {}`;
1090
1096
 
1091
1097
  it('wraps children in normalize_children for explicit children prop passed to component', () => {
1092
1098
  const source = `
1093
- component Card(props) {
1099
+ function Card(props) { return <>
1094
1100
  <div>{props.children}</div>
1095
- }
1101
+ </>; }
1096
1102
 
1097
- export component App() {
1103
+ export function App() { return <>
1098
1104
  const content = 'hello';
1099
1105
 
1100
1106
  <Card children={content} />
1101
- }
1107
+ </>; }
1102
1108
  `;
1103
1109
 
1104
1110
  const result = compile(source, 'test.tsrx', { mode: 'client' }).code;
@@ -1109,12 +1115,13 @@ export component App() {
1109
1115
  it(
1110
1116
  'parses a JS statement inside an element with no trailing whitespace before the closing tag',
1111
1117
  () => {
1112
- const source = `component TodoList({ items }: { items: { text: string }[] }) {
1118
+ const source = `function TodoList({ items }: { items: { text: string }[] }) { return <>
1113
1119
  <ul>var a = "123"</ul>
1114
- }`;
1120
+ </>; }`;
1115
1121
  const ast = parse(source);
1116
- const comp = ast.body[0] as unknown as AST.Component;
1117
- const ul = comp.body.find((n) => n.type === 'Element') as AST.Element;
1122
+ const ul = get_returned_tsrx(ast.body[0]).children.find(
1123
+ (n: AST.Node) => n.type === 'Element',
1124
+ ) as AST.Element;
1118
1125
  expect((ul.id as AST.Identifier).name).toBe('ul');
1119
1126
  expect(ul.children).toHaveLength(1);
1120
1127
  const decl = ul.children[0] as unknown as AST.VariableDeclaration;
@@ -1128,15 +1135,15 @@ export component App() {
1128
1135
 
1129
1136
  it('uses spread_props for spreads that may contain children', () => {
1130
1137
  const source = `
1131
- component Card(props) {
1138
+ function Card(props) { return <>
1132
1139
  <div>{props.children}</div>
1133
- }
1140
+ </>; }
1134
1141
 
1135
- export component App() {
1142
+ export function App() { return <>
1136
1143
  const props = { children: 'hello' };
1137
1144
 
1138
1145
  <Card {...props} />
1139
- }
1146
+ </>; }
1140
1147
  `;
1141
1148
 
1142
1149
  const result = compile(source, 'test.tsrx', { mode: 'client' }).code;
@@ -1145,14 +1152,15 @@ export component App() {
1145
1152
  });
1146
1153
 
1147
1154
  it('parses less-than comparisons at line start in element children without whitespace', () => {
1148
- const source = `component TodoList({ items }: { items: { text: string }[] }) {
1155
+ const source = `function TodoList({ items }: { items: { text: string }[] }) { return <>
1149
1156
  <ul>var a = 3
1150
1157
  <4;</ul>
1151
- }`;
1158
+ </>; }`;
1152
1159
 
1153
1160
  const ast = parse(source);
1154
- const comp = ast.body[0] as unknown as AST.Component;
1155
- const ul = comp.body.find((n) => n.type === 'Element') as AST.Element;
1161
+ const ul = get_returned_tsrx(ast.body[0]).children.find(
1162
+ (n: AST.Node) => n.type === 'Element',
1163
+ ) as AST.Element;
1156
1164
  expect((ul.id as AST.Identifier).name).toBe('ul');
1157
1165
  expect(ul.children.length).toBeGreaterThanOrEqual(1);
1158
1166
  const decl = ul.children[0] as unknown as AST.VariableDeclaration;