ripple 0.3.72 → 0.3.76

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 (172) hide show
  1. package/CHANGELOG.md +116 -0
  2. package/package.json +3 -3
  3. package/src/jsx-runtime.d.ts +4 -10
  4. package/src/runtime/dynamic-client.js +33 -0
  5. package/src/runtime/dynamic-server.js +80 -0
  6. package/src/runtime/index-client.js +5 -13
  7. package/src/runtime/index-server.js +2 -0
  8. package/src/runtime/internal/client/blocks.js +6 -27
  9. package/src/runtime/internal/client/composite.js +11 -6
  10. package/src/runtime/internal/client/for.js +80 -5
  11. package/src/runtime/internal/client/index.js +0 -2
  12. package/src/runtime/internal/client/render.js +5 -2
  13. package/src/runtime/internal/client/types.d.ts +0 -10
  14. package/src/runtime/internal/server/index.js +8 -1
  15. package/tests/client/__snapshots__/computed-properties.test.tsrx.snap +8 -0
  16. package/tests/client/__snapshots__/for.test.tsrx.snap +22 -0
  17. package/tests/client/__snapshots__/html.test.tsrx.snap +4 -0
  18. package/tests/client/array/array.copy-within.test.tsrx +19 -19
  19. package/tests/client/array/array.derived.test.tsrx +97 -109
  20. package/tests/client/array/array.iteration.test.tsrx +28 -28
  21. package/tests/client/array/array.mutations.test.tsrx +68 -68
  22. package/tests/client/array/array.static.test.tsrx +82 -92
  23. package/tests/client/array/array.to-methods.test.tsrx +15 -15
  24. package/tests/client/async-suspend.test.tsrx +180 -179
  25. package/tests/client/basic/__snapshots__/basic.attributes.test.tsrx.snap +2 -0
  26. package/tests/client/basic/__snapshots__/basic.rendering.test.tsrx.snap +4 -0
  27. package/tests/client/basic/basic.attributes.test.tsrx +273 -317
  28. package/tests/client/basic/basic.collections.test.tsrx +55 -61
  29. package/tests/client/basic/basic.components.test.tsrx +198 -220
  30. package/tests/client/basic/basic.errors.test.tsrx +70 -76
  31. package/tests/client/basic/basic.events.test.tsrx +80 -85
  32. package/tests/client/basic/basic.get-set.test.tsrx +54 -64
  33. package/tests/client/basic/basic.hmr.test.tsrx +15 -19
  34. package/tests/client/basic/basic.reactivity.test.tsrx +121 -135
  35. package/tests/client/basic/basic.rendering.test.tsrx +273 -178
  36. package/tests/client/basic/basic.styling.test.tsrx +16 -14
  37. package/tests/client/basic/basic.utilities.test.tsrx +8 -10
  38. package/tests/client/boundaries.test.tsrx +18 -18
  39. package/tests/client/compiler/compiler.assignments.test.tsrx +77 -76
  40. package/tests/client/compiler/compiler.attributes.test.tsrx +18 -14
  41. package/tests/client/compiler/compiler.basic.test.tsrx +357 -288
  42. package/tests/client/compiler/compiler.regex.test.tsrx +40 -44
  43. package/tests/client/compiler/compiler.tracked-access.test.tsrx +57 -38
  44. package/tests/client/compiler/compiler.try-in-function.test.tsrx +16 -16
  45. package/tests/client/compiler/compiler.typescript.test.tsrx +4 -3
  46. package/tests/client/composite/composite.dynamic-components.test.tsrx +62 -47
  47. package/tests/client/composite/composite.generics.test.tsrx +165 -167
  48. package/tests/client/composite/composite.props.test.tsrx +66 -74
  49. package/tests/client/composite/composite.reactivity.test.tsrx +132 -166
  50. package/tests/client/composite/composite.render.test.tsrx +92 -101
  51. package/tests/client/computed-properties.test.tsrx +14 -18
  52. package/tests/client/context.test.tsrx +14 -18
  53. package/tests/client/css/global-additional-cases.test.tsrx +493 -439
  54. package/tests/client/css/global-advanced-selectors.test.tsrx +169 -153
  55. package/tests/client/css/global-at-rules.test.tsrx +71 -66
  56. package/tests/client/css/global-basic.test.tsrx +105 -98
  57. package/tests/client/css/global-classes-ids.test.tsrx +128 -114
  58. package/tests/client/css/global-combinators.test.tsrx +83 -78
  59. package/tests/client/css/global-complex-nesting.test.tsrx +134 -120
  60. package/tests/client/css/global-edge-cases.test.tsrx +138 -120
  61. package/tests/client/css/global-keyframes.test.tsrx +108 -96
  62. package/tests/client/css/global-nested.test.tsrx +88 -78
  63. package/tests/client/css/global-pseudo.test.tsrx +104 -98
  64. package/tests/client/css/global-scoping.test.tsrx +145 -125
  65. package/tests/client/css/style-identifier.test.tsrx +65 -72
  66. package/tests/client/date.test.tsrx +83 -83
  67. package/tests/client/dynamic-elements.test.tsrx +318 -299
  68. package/tests/client/events.test.tsrx +252 -266
  69. package/tests/client/for.test.tsrx +120 -127
  70. package/tests/client/head.test.tsrx +74 -48
  71. package/tests/client/html.test.tsrx +37 -49
  72. package/tests/client/input-value.test.tsrx +1125 -1354
  73. package/tests/client/lazy-array.test.tsrx +10 -16
  74. package/tests/client/lazy-destructuring.test.tsrx +169 -221
  75. package/tests/client/map.test.tsrx +39 -41
  76. package/tests/client/media-query.test.tsrx +15 -19
  77. package/tests/client/object.test.tsrx +46 -56
  78. package/tests/client/portal.test.tsrx +31 -37
  79. package/tests/client/ref.test.tsrx +173 -193
  80. package/tests/client/return.test.tsrx +62 -37
  81. package/tests/client/set.test.tsrx +33 -33
  82. package/tests/client/svg.test.tsrx +197 -216
  83. package/tests/client/switch.test.tsrx +201 -191
  84. package/tests/client/track-async-hydration.test.tsrx +14 -18
  85. package/tests/client/tracked-index-access.test.tsrx +18 -28
  86. package/tests/client/try.test.tsrx +494 -619
  87. package/tests/client/tsx.test.tsrx +286 -292
  88. package/tests/client/typescript-generics.test.tsrx +121 -129
  89. package/tests/client/url/url.derived.test.tsrx +21 -25
  90. package/tests/client/url/url.parsing.test.tsrx +35 -35
  91. package/tests/client/url/url.partial-removal.test.tsrx +32 -32
  92. package/tests/client/url/url.reactivity.test.tsrx +68 -72
  93. package/tests/client/url/url.serialization.test.tsrx +8 -8
  94. package/tests/client/url-search-params/url-search-params.derived.test.tsrx +21 -27
  95. package/tests/client/url-search-params/url-search-params.initialization.test.tsrx +16 -16
  96. package/tests/client/url-search-params/url-search-params.iteration.test.tsrx +37 -37
  97. package/tests/client/url-search-params/url-search-params.mutation.test.tsrx +56 -60
  98. package/tests/client/url-search-params/url-search-params.retrieval.test.tsrx +32 -34
  99. package/tests/client/url-search-params/url-search-params.serialization.test.tsrx +9 -9
  100. package/tests/client/url-search-params/url-search-params.tracked-url.test.tsrx +10 -10
  101. package/tests/hydration/compiled/client/basic.js +390 -319
  102. package/tests/hydration/compiled/client/composite.js +52 -44
  103. package/tests/hydration/compiled/client/for.js +734 -604
  104. package/tests/hydration/compiled/client/head.js +183 -103
  105. package/tests/hydration/compiled/client/html.js +93 -86
  106. package/tests/hydration/compiled/client/if-children.js +95 -71
  107. package/tests/hydration/compiled/client/if.js +113 -89
  108. package/tests/hydration/compiled/client/mixed-control-flow.js +225 -209
  109. package/tests/hydration/compiled/client/nested-control-flow.js +94 -98
  110. package/tests/hydration/compiled/client/reactivity.js +26 -24
  111. package/tests/hydration/compiled/client/return.js +8 -42
  112. package/tests/hydration/compiled/client/switch.js +208 -173
  113. package/tests/hydration/compiled/client/track-async-serialization.js +176 -128
  114. package/tests/hydration/compiled/client/try.js +29 -21
  115. package/tests/hydration/compiled/server/basic.js +210 -221
  116. package/tests/hydration/compiled/server/composite.js +13 -14
  117. package/tests/hydration/compiled/server/for.js +427 -444
  118. package/tests/hydration/compiled/server/head.js +199 -189
  119. package/tests/hydration/compiled/server/html.js +33 -41
  120. package/tests/hydration/compiled/server/if-children.js +114 -117
  121. package/tests/hydration/compiled/server/if.js +77 -83
  122. package/tests/hydration/compiled/server/mixed-control-flow.js +145 -150
  123. package/tests/hydration/compiled/server/nested-control-flow.js +10 -0
  124. package/tests/hydration/compiled/server/reactivity.js +24 -22
  125. package/tests/hydration/compiled/server/return.js +6 -18
  126. package/tests/hydration/compiled/server/switch.js +179 -176
  127. package/tests/hydration/compiled/server/track-async-serialization.js +88 -70
  128. package/tests/hydration/compiled/server/try.js +31 -35
  129. package/tests/hydration/components/basic.tsrx +216 -258
  130. package/tests/hydration/components/composite.tsrx +32 -42
  131. package/tests/hydration/components/events.tsrx +81 -101
  132. package/tests/hydration/components/for.tsrx +270 -336
  133. package/tests/hydration/components/head.tsrx +43 -39
  134. package/tests/hydration/components/hmr.tsrx +16 -22
  135. package/tests/hydration/components/html-in-template.tsrx +15 -21
  136. package/tests/hydration/components/html.tsrx +442 -526
  137. package/tests/hydration/components/if-children.tsrx +107 -125
  138. package/tests/hydration/components/if.tsrx +68 -90
  139. package/tests/hydration/components/mixed-control-flow.tsrx +65 -72
  140. package/tests/hydration/components/nested-control-flow.tsrx +202 -216
  141. package/tests/hydration/components/portal.tsrx +33 -41
  142. package/tests/hydration/components/reactivity.tsrx +26 -34
  143. package/tests/hydration/components/return.tsrx +4 -6
  144. package/tests/hydration/components/switch.tsrx +73 -78
  145. package/tests/hydration/components/track-async-serialization.tsrx +83 -93
  146. package/tests/hydration/components/try.tsrx +37 -51
  147. package/tests/hydration/switch.test.js +8 -8
  148. package/tests/server/await.test.tsrx +3 -3
  149. package/tests/server/basic.attributes.test.tsrx +117 -162
  150. package/tests/server/basic.components.test.tsrx +164 -194
  151. package/tests/server/basic.test.tsrx +299 -199
  152. package/tests/server/compiler.test.tsrx +142 -72
  153. package/tests/server/composite.props.test.tsrx +54 -58
  154. package/tests/server/composite.test.tsrx +165 -167
  155. package/tests/server/context.test.tsrx +13 -17
  156. package/tests/server/dynamic-elements.test.tsrx +147 -148
  157. package/tests/server/for.test.tsrx +115 -84
  158. package/tests/server/head.test.tsrx +54 -31
  159. package/tests/server/html-nesting-validation.test.tsrx +16 -8
  160. package/tests/server/if.test.tsrx +49 -59
  161. package/tests/server/lazy-destructuring.test.tsrx +288 -366
  162. package/tests/server/return.test.tsrx +58 -36
  163. package/tests/server/streaming-ssr.test.tsrx +4 -4
  164. package/tests/server/style-identifier.test.tsrx +61 -69
  165. package/tests/server/switch.test.tsrx +89 -97
  166. package/tests/server/track-async-serialization.test.tsrx +85 -103
  167. package/tests/server/try.test.tsrx +275 -360
  168. package/tests/utils/ref-types.test.js +72 -0
  169. package/tests/utils/vite-plugin-config.test.js +41 -74
  170. package/types/index.d.ts +29 -4
  171. package/src/runtime/internal/client/compat.js +0 -40
  172. package/tests/utils/compiler-compat-config.test.js +0 -38
@@ -8,6 +8,10 @@ function get_returned_tsrx(node: any): any {
8
8
  const body =
9
9
  target.type === 'VariableDeclarator' ? target.init.body : target.body;
10
10
 
11
+ if (body.type === 'JSXCodeBlock') {
12
+ return body.render;
13
+ }
14
+
11
15
  if (body.type !== 'BlockStatement') {
12
16
  return body;
13
17
  }
@@ -29,11 +33,12 @@ function count_occurrences(string: string, subString: string): number {
29
33
 
30
34
  describe('compiler > basics', () => {
31
35
  it('parses style content correctly', () => {
32
- const source = `export function App() { return <>
33
- <div id="myid" class="myclass">{"Hello World"}</div>
34
-
35
- <style>__STYLE__</style>
36
- </>; }`;
36
+ const source = `export function App() @{
37
+ <>
38
+ <div id="myid" class="myclass">{"Hello World"}</div>
39
+ <style>__STYLE__</style>
40
+ </>
41
+ }`;
37
42
  const style1 = '.myid {color: green }';
38
43
  const style2 = '#myid {color: green }';
39
44
  const style3 = 'div {color: green }';
@@ -52,13 +57,15 @@ describe('compiler > basics', () => {
52
57
  });
53
58
 
54
59
  it('parses text as an ordinary expression identifier', () => {
55
- const source = `export function App() { return <>
60
+ const source = `export function App() @{
56
61
  const markup = '<span>Not HTML</span>';
57
62
  const text = markup;
58
63
 
59
- <div>{markup}</div>
60
- <div>{text}</div>
61
- </>; }`;
64
+ <>
65
+ <div>{markup}</div>
66
+ <div>{text}</div>
67
+ </>
68
+ }`;
62
69
 
63
70
  const ast = parse(source);
64
71
  const elements = get_returned_tsrx(ast.body[0]).children.filter(
@@ -76,11 +83,11 @@ describe('compiler > basics', () => {
76
83
  const { code } = compile(source, 'text-directive.tsrx', { mode: 'client' });
77
84
  expect(code).not.toContain('_$_.html');
78
85
 
79
- const invalid_source = `export function App() { return <>
86
+ const invalid_source = `export function App() @{
80
87
  const markup = 'plain';
81
88
 
82
89
  <div>{text markup}</div>
83
- </>; }`;
90
+ }`;
84
91
 
85
92
  expect(() => parse(invalid_source)).toThrow();
86
93
  });
@@ -88,180 +95,234 @@ describe('compiler > basics', () => {
88
95
  it('optimizes string-shaped expressions as text nodes', () => {
89
96
  const source = `export function App(
90
97
  { title, props }: { title: string; props: { label: string } },
91
- ) { return <>
98
+ ) @{
92
99
  const element = <span />;
93
100
 
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
+ <div>{String(title)}</div>
103
+ <div>{title + ''}</div>
104
+ <div>{title as string}</div>
105
+ <div>{title}</div>
106
+ <div>{props.label}</div>
107
+ <div>{element}</div>
108
+ </>
109
+ }`;
101
110
 
102
111
  const { code } = compile(source, 'stringish-text.tsrx', { mode: 'client' });
103
112
 
104
113
  expect(code).toContain('_$_.set_text');
105
114
  expect(code).toContain('nodeValue = title');
106
- expect(count_occurrences(code, '_$_.expression')).toBe(1);
115
+ expect(count_occurrences(code, '_$_.expression')).toBe(2);
116
+ });
117
+
118
+ it('does not merge adjacent call-containing children into stringified text', () => {
119
+ const setup = `function child(label) @{
120
+ <span>{label}</span>
121
+ }
122
+ function Constructed(label) {
123
+ return child(label);
124
+ }
125
+ const factory = child;
126
+ function identity(value) {
127
+ return value;
128
+ }
129
+ function empty() {
130
+ return null;
131
+ }
132
+ let assigned;
133
+ const lookup = { member: child('member') };
134
+ function getKey() {
135
+ return 'member';
136
+ }
137
+ function touch() {
138
+ return null;
139
+ }
140
+ function tag() {
141
+ return child('tagged');
142
+ }
143
+ const values = { member: 0 };`;
144
+
145
+ const cases = [
146
+ { name: 'call expression', expression: 'child(\'call\')' },
147
+ { name: 'new expression', expression: 'new Constructed(\'new\')' },
148
+ { name: 'chain expression', expression: 'factory?.(\'chain\')' },
149
+ { name: 'parenthesized expression', expression: '(child(\'parenthesized\'))' },
150
+ { name: 'TS as expression', expression: 'child(\'as\') as any' },
151
+ { name: 'TS non-null expression', expression: 'child(\'non-null\')!' },
152
+ { name: 'TS satisfies expression', expression: 'child(\'satisfies\') satisfies any' },
153
+ {
154
+ name: 'TS instantiation expression',
155
+ expression: 'identity<typeof child>(child)(\'instantiation\')',
156
+ },
157
+ { name: 'array expression', expression: '[child(\'array\')]' },
158
+ { name: 'assignment expression', expression: 'assigned = child(\'assignment\')' },
159
+ { name: 'binary expression', expression: 'empty() + \'\'' },
160
+ { name: 'logical expression', expression: 'true && child(\'logical\')' },
161
+ { name: 'conditional expression', expression: 'true ? child(\'conditional\') : \'\'' },
162
+ { name: 'member expression', expression: 'lookup[getKey()]' },
163
+ {
164
+ name: 'object expression',
165
+ expression: '{ [Symbol.for(\'ripple.element\')]: true, render() { return child(\'object\'); } }',
166
+ },
167
+ { name: 'sequence expression', expression: '(touch(), child(\'sequence\'))' },
168
+ { name: 'tagged template expression', expression: 'tag`tagged`' },
169
+ { name: 'template literal', expression: '`${empty()}`' },
170
+ { name: 'unary expression', expression: 'void empty()' },
171
+ { name: 'update expression', expression: 'values[getKey()]++' },
172
+ ];
173
+
174
+ for (const test_case of cases) {
175
+ const source = `${setup}
176
+ export function App() @{
177
+ <div>{'${test_case.name}:'}{${test_case.expression}}</div>
178
+ }`;
179
+
180
+ const { code } = compile(source, `${test_case.name}.tsrx`, { mode: 'client' });
181
+
182
+ if (code.includes('String(')) {
183
+ throw new Error(`${test_case.name} was merged into a stringified text expression:\n${code}`);
184
+ }
185
+ }
107
186
  });
108
187
 
109
188
  it('parses backtick expressions inside TSRX fragments as template literals', () => {
110
- const source = `let a = function() {
111
- return <>
189
+ const source = `let a = function() @{
112
190
  <>
113
- \`333\`
191
+ {\`333\`}
114
192
  </>
115
- </>;
116
193
  }`;
117
194
 
118
195
  const ast = parse(source);
119
196
  const declaration = (ast.body[0] as AST.VariableDeclaration).declarations[0];
120
- const fragment = get_returned_tsrx(declaration).children[0] as any;
197
+ const fragment = get_returned_tsrx(declaration) as any;
121
198
 
122
199
  expect(fragment.type).toBe('TsrxFragment');
123
- expect(fragment.children[0].type).toBe('ExpressionStatement');
200
+ expect(fragment.children[0].type).toBe('TSRXExpression');
124
201
  expect(fragment.children[0].expression.type).toBe('TemplateLiteral');
125
202
  expect(fragment.children[0].expression.quasis[0].value.raw).toBe('333');
126
203
  });
127
204
 
128
205
  it('parses backtick expressions around tag-like text inside TSRX fragments', () => {
129
- const source = `let a = function() {
130
- return <>
206
+ const source = `let a = function() @{
131
207
  <>
132
- \`
208
+ {\`
133
209
  <b></b>
134
- \`
210
+ \`}
135
211
  </>
136
- </>;
137
212
  }`;
138
213
 
139
214
  const ast = parse(source);
140
215
  const declaration = (ast.body[0] as AST.VariableDeclaration).declarations[0];
141
- const fragment = get_returned_tsrx(declaration).children[0] as any;
216
+ const fragment = get_returned_tsrx(declaration) as any;
142
217
 
143
218
  expect(fragment.type).toBe('TsrxFragment');
144
219
  expect(fragment.children.map((child: any) => child.type)).toEqual([
145
- 'ExpressionStatement',
220
+ 'TSRXExpression',
146
221
  ]);
147
222
  expect(fragment.children[0].expression.type).toBe('TemplateLiteral');
148
223
  expect(fragment.children[0].expression.quasis[0].value.raw).toContain('<b></b>');
149
224
  });
150
225
 
151
226
  it('renders without crashing', () => {
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
- </>;
227
+ function App() @{
228
+ let foo: Record<string, number>;
229
+ let bar: Record<string, number>;
230
+ let baz: Record<string, number>;
231
+ foo = {};
232
+ foo = { test: 0 };
233
+ foo['abc'] = 123;
234
+ bar = { def: 456 };
235
+ baz = { ghi: 789 };
236
+ baz['jkl'] = 987;
164
237
  }
165
238
 
166
239
  render(App);
167
240
  });
168
241
 
169
242
  it('renders without crashing using < character', () => {
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;
243
+ function App() @{
244
+ function bar() {
245
+ for (let i = 0; i < 10; i++) {
246
+ // do nothing
177
247
  }
178
- let x = 5 < 10;
179
- <div>{x}</div>
180
- </>;
248
+ const x = 1 < 1;
249
+ }
250
+ let x = 5 < 10;
251
+ <div>{x}</div>
181
252
  }
182
253
 
183
254
  render(App);
184
255
  });
185
256
 
186
257
  it('renders lexical blocks without crashing', () => {
187
- function App() {
188
- return <>
258
+ function App() @{
259
+ <>
189
260
  <div>
190
261
  const a = 1;
191
- <div>
192
- const b = 1;
193
- </div>
194
- <div>
195
- const b = 1;
196
- </div>
262
+ <div>const b = 1;</div>
263
+ <div>const b = 1;</div>
197
264
  </div>
198
265
  <div>
199
266
  const a = 2;
200
- <div>
201
- const b = 1;
202
- </div>
267
+ <div>const b = 1;</div>
203
268
  </div>
204
- </>;
269
+ </>
205
270
  }
206
271
 
207
272
  render(App);
208
273
  });
209
274
 
210
275
  it('renders without crashing using mapped types', () => {
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
- </>;
276
+ function App() @{
277
+ type RecordKey = 'test';
278
+ type RecordValue = { a: string; b: number };
279
+ const config: Record<RecordKey, RecordValue> = {
280
+ test: {
281
+ a: 'test',
282
+ b: 1,
283
+ },
284
+ };
285
+ const config2: { [key in RecordKey]: RecordValue } = {
286
+ test: {
287
+ a: 'test2',
288
+ b: 2,
289
+ },
290
+ };
291
+ const config3: { [key: string]: RecordValue } = {
292
+ test: {
293
+ a: 'test3',
294
+ b: 3,
295
+ },
296
+ };
234
297
  }
235
298
 
236
299
  render(App);
237
300
  });
238
301
 
239
302
  it('renders without crashing using object destructuring', () => {
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
- </>;
303
+ function App() @{
304
+ const obj = { a: 1, b: 2, c: 3 };
305
+ const { a, b, ...rest } = obj;
306
+ <div>
307
+ {'a '}
308
+ {a}
309
+ {'b '}
310
+ {b}
311
+ {'rest '}
312
+ {JSON.stringify(rest)}
313
+
314
+ <div />
315
+ </div>
255
316
  }
256
317
 
257
318
  render(App);
258
319
  });
259
320
 
260
321
  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;
322
+ function App() @{
323
+ const obj = { a: 1, b: 2, c: 3 };
324
+ const { a, b, ...rest } = obj;
325
+ <>
265
326
  {'a '}
266
327
  {a}
267
328
  {'b '}
@@ -269,7 +330,7 @@ describe('compiler > basics', () => {
269
330
  {'rest '}
270
331
  {JSON.stringify(rest)}
271
332
  <div />
272
- </>;
333
+ </>
273
334
  }
274
335
 
275
336
  render(App);
@@ -288,35 +349,33 @@ describe('compiler > basics', () => {
288
349
  };
289
350
  }
290
351
 
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;
352
+ function App() @{
353
+ let x: number[] = [] as number[];
354
+ const n = Wrapper<number>().unwrap<string>();
355
+ const tagResult = tagFn`value`;
356
+ interface Node<T> {
357
+ value: T;
358
+ }
359
+ class Box<T> {
360
+ value: T;
361
+
362
+ method<U extends T>(): U {
363
+ return this.value as U;
298
364
  }
299
- class Box<T> {
300
- value: T;
301
365
 
302
- method<U extends T>(): U {
303
- return this.value as U;
304
- }
305
-
306
- constructor(value: T) {
307
- this.value = value;
308
- }
366
+ constructor(value: T) {
367
+ this.value = value;
309
368
  }
310
- let flag = true;
311
- const s = flag ? new Box<number>(1) : new Box<string>('string');
312
- </>;
369
+ }
370
+ let flag = true;
371
+ const s = flag ? new Box<number>(1) : new Box<string>('string');
313
372
  }
314
373
 
315
374
  render(App);
316
375
  });
317
376
 
318
377
  it('compiles without needing semicolons between statements and JSX', () => {
319
- const source = `export function App() { return <>
378
+ const source = `export function App() @{
320
379
  <div>const code4 = 4
321
380
 
322
381
  const code3 = 3
@@ -327,16 +386,18 @@ describe('compiler > basics', () => {
327
386
  const code2 = 2
328
387
  </div>
329
388
  </div>
330
- </>; }`;
389
+ }`;
331
390
 
332
391
  const result = compile(source, 'test.tsrx', { mode: 'client' });
333
392
  });
334
393
 
335
394
  it('calculates fragment hop count for sibling fragments', () => {
336
- const source = `export function App() { return <>
337
- <div class="a">{'a'}</div>
338
- <div class="b">{'b'}</div>
339
- </>; }`;
395
+ const source = `export function App() @{
396
+ <>
397
+ <div class="a">{'a'}</div>
398
+ <div class="b">{'b'}</div>
399
+ </>
400
+ }`;
340
401
 
341
402
  const { code } = compile(source, 'grouped-count.tsrx', { mode: 'client' });
342
403
 
@@ -346,9 +407,9 @@ describe('compiler > basics', () => {
346
407
 
347
408
  it('emits anonymous component expressions as arrows in client output', () => {
348
409
  const source = `
349
- const Inline = (props) => <>
410
+ const Inline = (props) => @{
350
411
  <div>{props.x}</div>
351
- </>
412
+ }
352
413
  `;
353
414
  const result = compile(source, 'anonymous-component.tsrx', { mode: 'client' }).code;
354
415
 
@@ -361,9 +422,9 @@ const Inline = (props) => <>
361
422
 
362
423
  it('emits function-expression components as functions in client output', () => {
363
424
  const source = `
364
- const Inline = function(props) { return <>
425
+ const Inline = function(props) @{
365
426
  <div>{props.x}</div>
366
- </>; }
427
+ }
367
428
  `;
368
429
  const result = compile(source, 'anonymous-component.tsrx', { mode: 'client' }).code;
369
430
 
@@ -376,17 +437,17 @@ const Inline = function(props) { return <>
376
437
 
377
438
  it('emits function calls with nested template returns as expressions in client output', () => {
378
439
  const source = `
379
- function App() { return <>
440
+ function App() @{
380
441
  function make(flag) {
381
442
  if (flag) {
382
- return <><span>{'nested'}</span></>;
443
+ return <span>{'nested'}</span>;
383
444
  }
384
445
 
385
446
  return null;
386
447
  }
387
448
 
388
449
  <div>{make(true)}</div>
389
- </>; }
450
+ }
390
451
  `;
391
452
  const result = compile(source, 'nested-template-return.tsrx', { mode: 'client' }).code;
392
453
 
@@ -398,11 +459,11 @@ function App() { return <>
398
459
  // () => {
399
460
  // const source = `
400
461
  // import { RippleArray, RippleObject, RippleSet, RippleMap, createRefKey } from 'ripple';
401
- // function App() { return <>
462
+ // function App() @{
402
463
  // const items = new RippleArray(1, 2, 3);
403
464
  // const obj = new RippleObject({ a: 1, b: 2, c: 3 });
404
- // const set = RippleSet([1, 2, 3]);
405
- // const map = RippleMap([['a', 1], ['b', 2], ['c', 3]]);
465
+ // const set = new RippleSet([1, 2, 3]);
466
+ // const map = new RippleMap([['a', 1], ['b', 2], ['c', 3]]);
406
467
 
407
468
  // <div ref={() => {}} />
408
469
  // }
@@ -422,11 +483,11 @@ function App() { return <>
422
483
  // () => {
423
484
  // const source = `
424
485
  // import { RippleArray as TA, RippleObject as TO, RippleSet as TS, RippleMap as TM, createRefKey as crk } from 'ripple';
425
- // function App() { return <>
486
+ // function App() @{
426
487
  // const items = new RippleArray(1, 2, 3);
427
488
  // const obj = new RippleObject({ a: 1, b: 2, c: 3 });
428
- // const set = RippleSet([1, 2, 3]);
429
- // const map = RippleMap([['a', 1], ['b', 2], ['c', 3]]);
489
+ // const set = new RippleSet([1, 2, 3]);
490
+ // const map = new RippleMap([['a', 1], ['b', 2], ['c', 3]]);
430
491
 
431
492
  // <div ref={() => {}} />
432
493
  // }
@@ -448,11 +509,11 @@ function App() { return <>
448
509
 
449
510
  // it('adds hidden obfuscated imports for shorthand syntax', () => {
450
511
  // const source = `
451
- // function App() { return <>
512
+ // function App() @{
452
513
  // const items = new RippleArray(1, 2, 3);
453
514
  // const obj = new RippleObject({ a: 1, b: 2, c: 3 });
454
- // const set = RippleSet([1, 2, 3]);
455
- // const map = RippleMap([['a', 1], ['b', 2], ['c', 3]]);
515
+ // const set = new RippleSet([1, 2, 3]);
516
+ // const map = new RippleMap([['a', 1], ['b', 2], ['c', 3]]);
456
517
 
457
518
  // <div ref={() => {}} />
458
519
  // }
@@ -469,7 +530,7 @@ function App() { return <>
469
530
  it('prints longhand tracked property values in to_ts output while preserving [\'#v\']', () => {
470
531
  const source = `
471
532
  import { RippleArray, RippleMap, RippleObject, RippleSet, createRefKey, effect, track, untrack } from 'ripple';
472
- function App() { return <>
533
+ function App() @{
473
534
  let value = track('test');
474
535
  function inputRef(node) {}
475
536
 
@@ -478,7 +539,7 @@ function App() { return <>
478
539
  value: value.value,
479
540
  [createRefKey()]: inputRef,
480
541
  };
481
- </>; }
542
+ }
482
543
  `;
483
544
 
484
545
  const result = compile_to_volar_mappings(source, 'test.tsrx').code;
@@ -489,10 +550,10 @@ function App() { return <>
489
550
  it('keeps lazy destructuring as plain destructuring in to_ts output', () => {
490
551
  const track_source = `
491
552
  import { track } from 'ripple';
492
- function App() { return <>
553
+ function App() @{
493
554
  let &[value, ...rest] = track(0);
494
555
  const x = value;
495
- </>; }
556
+ }
496
557
  `;
497
558
  const track_result = compile_to_volar_mappings(track_source, 'test.tsrx').code;
498
559
  expect(track_result).toContain('let [value, ...rest] = track(0);');
@@ -505,7 +566,7 @@ function App() { return <>
505
566
 
506
567
  it('lowers native expression values in to_ts output', () => {
507
568
  const source = `
508
- function App() { return <>
569
+ function App() @{
509
570
  const nested = <>
510
571
  <span class="nested-tsx">
511
572
  {'inside nested tsx'}
@@ -513,25 +574,25 @@ function App() { return <>
513
574
  </>;
514
575
  const content = <div class="native">{nested}</div>;
515
576
 
516
- {content}
517
- </>; }
577
+ <>{content}</>
578
+ }
518
579
  `;
519
580
  const result = compile_to_volar_mappings(source, 'test.tsrx').code;
520
581
 
521
582
  expect(result).toContain('const nested = <span class="nested-tsx">');
522
- expect(result).toContain('return <>{children}</>;');
583
+ expect(result).toContain('return content;');
523
584
  expect(result).toContain('const content = <div class="native">');
524
585
  });
525
586
 
526
587
  it('keeps assigned style blocks anchored in to_ts output', () => {
527
588
  const source = `
528
- function App() { return <>
589
+ function App() @{
529
590
  const styles = <style>
530
591
  .logo { display: block; }
531
592
  </style>;
532
593
 
533
594
  <div class={styles.logo} />
534
- </>; }
595
+ }
535
596
  `;
536
597
  const result = compile_to_volar_mappings(source, 'test.tsrx', { loose: true });
537
598
  const source_offset = source.indexOf('<style>') + 1;
@@ -555,7 +616,7 @@ function App() { return <>
555
616
 
556
617
  it('maps identifiers from native expression values in to_ts output', () => {
557
618
  const source = `
558
- function App() { return <>
619
+ function App() @{
559
620
  const nested = <>
560
621
  <span class="nested-tsx">
561
622
  {'inside nested tsx'}
@@ -563,14 +624,14 @@ function App() { return <>
563
624
  </>;
564
625
  const content = <div class="native">{nested}</div>;
565
626
 
566
- {content}
567
- </>; }
627
+ <>{content}</>
628
+ }
568
629
  `;
569
630
  const result = compile_to_volar_mappings(source, 'test.tsrx', { loose: true });
570
631
  const source_declaration = source.indexOf('nested =');
571
632
  const source_reference = source.indexOf('nested}</div>');
572
633
  const generated_declaration = result.code.indexOf('const nested') + 'const '.length;
573
- const generated_reference = result.code.indexOf('nested;', generated_declaration);
634
+ const generated_reference = result.code.indexOf('nested}</div>', generated_declaration);
574
635
 
575
636
  function find_mapping(source_offset: number, generated_offset: number) {
576
637
  return result.mappings.find(
@@ -611,9 +672,9 @@ type Props<Item> = {
611
672
  items: readonly Item[];
612
673
  }
613
674
 
614
- export function MyComponent<Item>(props: Props<Item>) { return <>
675
+ export function MyComponent<Item>(props: Props<Item>) @{
615
676
  <div />
616
- </>; }
677
+ }
617
678
  `;
618
679
  const result = compile_to_volar_mappings(source, 'test.tsrx').code;
619
680
 
@@ -622,22 +683,23 @@ export function MyComponent<Item>(props: Props<Item>) { return <>
622
683
 
623
684
  it('preserves arrow functions that return TSRX in to_ts output', () => {
624
685
  const source = `
625
- const Inline = (props: { x: string }) => <>
686
+ const Inline = (props: { x: string }) => @{
626
687
  <div>{props.x}</div>
627
- </>
688
+ }
628
689
  `;
629
690
  const result = compile_to_volar_mappings(source, 'test.tsrx').code;
630
691
 
631
- expect(result).toContain('const Inline = (props: { x: string }) => <div>');
692
+ expect(result).toContain('const Inline = (props: { x: string }) => {');
693
+ expect(result).toContain('return <div>');
632
694
  expect(result).not.toContain('function Inline');
633
695
  expect(result).not.toContain('function (props');
634
696
  });
635
697
 
636
698
  it('preserves function expressions that return TSRX in to_ts output', () => {
637
699
  const source = `
638
- const Inline = function(props: { x: string }) { return <>
700
+ const Inline = function(props: { x: string }) @{
639
701
  <div>{props.x}</div>
640
- </>; }
702
+ }
641
703
  `;
642
704
  const result = compile_to_volar_mappings(source, 'test.tsrx').code;
643
705
 
@@ -650,13 +712,13 @@ const Inline = function(props: { x: string }) { return <>
650
712
  const source = `
651
713
  type User = { name: string };
652
714
 
653
- function RenderProp<Item>(props: { children: (item: Item) => any }) { return <></>; }
715
+ function RenderProp<Item>(props: { children: (item: Item) => any }) @{}
654
716
 
655
- export function App() { return <>
717
+ export function App() @{
656
718
  <RenderProp<User>>
657
719
  {(item) => item.name}
658
720
  </RenderProp>
659
- </>; }
721
+ }
660
722
  `;
661
723
  const result = compile_to_volar_mappings(source, 'test.tsrx').code;
662
724
 
@@ -665,13 +727,13 @@ export function App() { return <>
665
727
 
666
728
  it('preserves generic type arguments on self-closing JSX component tags in to_ts output', () => {
667
729
  const source = `
668
- function Box<T>({ value }: { value: T }) { return <>
730
+ function Box<T>({ value }: { value: T }) @{
669
731
  <div>{String(value)}</div>
670
- </>; }
732
+ }
671
733
 
672
- export function App() { return <>
734
+ export function App() @{
673
735
  <Box<string> value="hi" />
674
- </>; }
736
+ }
675
737
  `;
676
738
  const result = compile_to_volar_mappings(source, 'test.tsrx').code;
677
739
 
@@ -771,9 +833,9 @@ interface Props extends PolymorphicProps<'div'> {
771
833
  id: string;
772
834
  }
773
835
 
774
- export function App(props: Props) { return <>
836
+ export function App(props: Props) @{
775
837
  <div id={props.id} />
776
- </>; }
838
+ }
777
839
  `;
778
840
 
779
841
  const result = compile_to_volar_mappings(source, 'test.tsrx').code;
@@ -784,7 +846,7 @@ export function App(props: Props) { return <>
784
846
  it('handles if-else expression statements in Volar mappings', () => {
785
847
  const source = `
786
848
  import { track } from 'ripple';
787
- export function App() { return <>
849
+ export function App() @{
788
850
  let &[level] = track(1);
789
851
 
790
852
  <button
@@ -796,58 +858,61 @@ export function App() { return <>
796
858
  >
797
859
  {'Toggle'}
798
860
  </button>
799
- </>; }
861
+ }
800
862
  `;
801
863
 
802
864
  expect(() => compile_to_volar_mappings(source, 'test.tsrx')).not.toThrow();
803
865
  });
804
866
 
805
- it('should not error on having js below markup in the same scope', () => {
867
+ it('errors on having js below markup in the same scope', () => {
806
868
  const code = `
807
- function Card(props) { return <>
869
+ function Card(props) @{
808
870
  <div class="card">
809
871
  {props.children}
810
872
  </div>
811
- </>; }
873
+ }
812
874
 
813
- export function App() { return <>
814
- function children() { return <>
875
+ export function App() @{
876
+ function children() @{
815
877
  <p>{'Card content here'}</p>
816
- </>; }
878
+ }
817
879
 
818
880
  <Card {children} />
819
881
 
820
882
  const test = 5;
821
883
 
822
884
  <div>{test}</div>
823
- </>; }
885
+ }
824
886
  `;
825
- expect(() => compile(code, 'test.tsrx')).not.toThrow();
887
+ expect(() => compile(code, 'test.tsrx')).toThrow(
888
+ /Code must be at the top of '@\{ \}'; statements cannot follow the rendered output/,
889
+ );
826
890
  });
827
891
 
828
- it('allows component functions inside composite children', () => {
892
+ it('allows component functions passed to composite children', () => {
829
893
  const source = `
830
- export function App() { return <>
831
- <ark.div class="host-class" data-value="42">
832
- function asChild({ children, href, ...rest }: { href: string; [key: string]: any }) { return <>
833
- <a id="aschild-anchor" {href} {...rest} data-extra="yes">{'Link'}</a>
834
- </>; }
835
- </ark.div>
836
- </>; }
894
+ export function App() @{
895
+ function asChild({ children, href, ...rest }: { href: string; [key: string]: any }) @{
896
+ <a id="aschild-anchor" {href} {...rest} data-extra="yes">{'Link'}</a>
897
+ }
898
+
899
+ <ark.div class="host-class" data-value="42" {asChild} />
900
+ }
837
901
  `;
838
902
 
839
903
  expect(() => compile_to_volar_mappings(source, 'test.tsrx')).not.toThrow();
840
904
  });
841
905
 
842
- it('allows parent element attributes referencing child-declared functions', () => {
906
+ it('allows parent element attributes referencing setup-declared functions', () => {
843
907
  const source = `
844
- export function App() { return <>
908
+ export function App() @{
909
+ function Z() @{
910
+ <div>{'hello'}</div>
911
+ }
912
+
845
913
  <Test {Z}>
846
- function Z() { return <>
847
- <div>{'hello'}</div>
848
- </>; }
849
914
  </Test>
850
- </>; }
915
+ }
851
916
  `;
852
917
 
853
918
  expect(() => compile(source, 'test.tsrx')).not.toThrow();
@@ -855,13 +920,13 @@ export function App() { return <>
855
920
 
856
921
  it('preserves explicit component props in Volar mappings', () => {
857
922
  const source = `
858
- export function App() { return <>
859
- function asChild({ children, href, ...rest }: { href: string; [key: string]: any }) { return <>
923
+ export function App() @{
924
+ function asChild({ children, href, ...rest }: { href: string; [key: string]: any }) @{
860
925
  <a id="aschild-anchor" {href} {...rest} data-extra="yes">{'Link'}</a>
861
- </>; }
926
+ }
862
927
 
863
928
  <ark.div class="host-class" data-value="42" {asChild} />
864
- </>; }
929
+ }
865
930
  `;
866
931
  const result = compile_to_volar_mappings(source, 'test.tsrx').code;
867
932
 
@@ -869,19 +934,58 @@ export function App() { return <>
869
934
  expect(result).not.toContain('children={() =>');
870
935
  });
871
936
 
937
+ it('uses direct TSRX render expressions for component children in Volar mappings', () => {
938
+ const source = `
939
+ function OtherComponent({ children }: { children: string }) @{
940
+ <div>{children}</div>
941
+ }
942
+
943
+ export const App = () => @{
944
+ <>
945
+ @try {
946
+ <ComponentThatSuspends />
947
+ } @pending {
948
+ <div>Loading</div>
949
+ }
950
+ <OtherComponent children={'I like TSRX'} />
951
+ </>
952
+ };
953
+ `;
954
+ const result = compile_to_volar_mappings(source, 'test.tsrx', { loose: true });
955
+ const source_offset = source.indexOf('\'I like TSRX\'');
956
+ const generated_offset = result.code.indexOf('\'I like TSRX\'');
957
+
958
+ expect(result.code).toContain('<OtherComponent children={\'I like TSRX\'}');
959
+ expect(result.code).not.toContain('children={() =>');
960
+ expect(
961
+ result.mappings.find(
962
+ (mapping: {
963
+ sourceOffsets: number[];
964
+ generatedOffsets: number[];
965
+ lengths: number[];
966
+ generatedLengths: number[];
967
+ }) =>
968
+ mapping.sourceOffsets[0] === source_offset &&
969
+ mapping.generatedOffsets[0] === generated_offset &&
970
+ mapping.lengths[0] === '\'I like TSRX\''.length &&
971
+ mapping.generatedLengths[0] === '\'I like TSRX\''.length,
972
+ ),
973
+ ).toBeDefined();
974
+ });
975
+
872
976
  it('merges explicit children prop with implicit children in client output', () => {
873
977
  const source = `
874
- function Card(props) { return <>
978
+ function Card(props) @{
875
979
  <div>{props.children}</div>
876
- </>; }
980
+ }
877
981
 
878
- export function App() { return <>
982
+ export function App() @{
879
983
  const fallback = 'fallback';
880
984
 
881
985
  <Card children={fallback}>
882
986
  <span>{'content'}</span>
883
987
  </Card>
884
- </>; }
988
+ }
885
989
  `;
886
990
 
887
991
  const result = compile(source, 'test.tsrx', { mode: 'client' }).code;
@@ -900,10 +1004,10 @@ class Test {
900
1004
  }
901
1005
  }
902
1006
 
903
- export function App() { return <>
1007
+ export function App() @{
904
1008
  const test = new Test();
905
1009
  <div>{test.count}</div>
906
- </>; }
1010
+ }
907
1011
  `;
908
1012
  expect(() => compile(code, 'test.tsrx')).not.toThrow();
909
1013
  });
@@ -919,10 +1023,10 @@ class Store {
919
1023
  }
920
1024
  }
921
1025
 
922
- export function App() { return <>
1026
+ export function App() @{
923
1027
  const store = new Store();
924
1028
  <div>{store.count}</div>
925
- </>; }
1029
+ }
926
1030
  `;
927
1031
  const result = compile(source, 'test.tsrx', { mode: 'client' });
928
1032
  const code = result.code;
@@ -936,14 +1040,14 @@ export function App() { return <>
936
1040
  const source = `
937
1041
  import { track, effect, untrack } from 'ripple';
938
1042
 
939
- function App() { return <>
1043
+ function App() @{
940
1044
  let &[count] = track(0);
941
1045
 
942
1046
  effect(() => {
943
1047
  const snapshot = untrack(() => count);
944
1048
  console.log(snapshot);
945
1049
  });
946
- </>; }
1050
+ }
947
1051
  `;
948
1052
 
949
1053
  const ast = parse(source);
@@ -956,10 +1060,10 @@ function App() { return <>
956
1060
  it('collects duplicate declaration parser errors in loose mode', () => {
957
1061
  const source = `
958
1062
  import { track } from 'ripple';
959
- export function App() { return <>
1063
+ export function App() @{
960
1064
  let test = track(false);
961
1065
  let test = 'hey';
962
- </>; }
1066
+ }
963
1067
  `;
964
1068
 
965
1069
  expect(() => compile(source, 'test.tsrx')).toThrow(
@@ -985,11 +1089,11 @@ export function App() { return <>
985
1089
  import { loadUser as getUser } from server;
986
1090
  import { loadUser } from server;
987
1091
 
988
- function App() { return <>
1092
+ function App() @{
989
1093
  const user = getUser();
990
1094
  const user2 = loadUser();
991
1095
  <div>{user.id}{user2.id}</div>
992
- </>; }`;
1096
+ }`;
993
1097
  const result = compile_to_volar_mappings(source, 'test.tsrx', { loose: true });
994
1098
  const generated_member = '_$_server_$_.loadUser';
995
1099
  const generated_member_offset = result.code.indexOf(generated_member);
@@ -1045,49 +1149,21 @@ function App() { return <>
1045
1149
  ).toBeDefined();
1046
1150
  });
1047
1151
 
1048
- it('reports return statements inside returned TSRX fragments in Volar mappings', () => {
1049
- const result = compile_to_volar_mappings(
1050
- `function App() { return <>
1152
+ it('allows return statements inside returned TSRX fragments in Volar mappings', () => {
1153
+ const result = compile_to_volar_mappings(`function App() @{
1051
1154
  return <div />;
1052
- </>; }`,
1053
- 'test.tsrx',
1054
- );
1055
-
1056
- expect(result.errors.map((error) => error.code)).toEqual(['tsrx-template-return-statement']);
1057
- });
1058
-
1059
- it('throws for unclosed tsx compat tags instead of hanging', () => {
1060
- const source = `export function App() { return <tsx:react>1; }`;
1061
-
1062
- expect(() => compile(source, 'test.tsrx')).toThrow(
1063
- 'Unclosed tag \'<tsx:react>\'. Expected \'</tsx:react>\' before end of template.',
1064
- );
1065
- });
1066
-
1067
- it('recovers unclosed tsx compat tags in loose mode', () => {
1068
- const source = `export function App() { return <tsx:react>1; }`;
1155
+ }`, 'test.tsrx');
1069
1156
 
1070
- expect(() => compile_to_volar_mappings(source, 'test.tsrx', { loose: true })).not.toThrow();
1071
-
1072
- const result = compile_to_volar_mappings(source, 'test.tsrx', { loose: true });
1073
1157
  expect(result.errors).toEqual([]);
1074
1158
  });
1075
1159
 
1076
- it('throws for unclosed tsx compat tags outside loose mode', () => {
1077
- const source = `export function App() { return <tsx:react>1; }`;
1078
-
1079
- expect(() => compile(source, 'test.tsrx', { collect: true })).toThrow(
1080
- 'Not implemented: TsxCompat',
1081
- );
1082
- });
1083
-
1084
1160
  it('collects analyzer errors outside loose mode', () => {
1085
1161
  const source = `
1086
1162
  import { track } from 'ripple';
1087
1163
 
1088
1164
  const outside = track(0);
1089
1165
 
1090
- export function App() { return <></>; }
1166
+ export function App() @{}
1091
1167
  `;
1092
1168
 
1093
1169
  const result = compile(source, 'test.tsrx', { collect: true });
@@ -1098,23 +1174,23 @@ export function App() { return <></>; }
1098
1174
 
1099
1175
  it('does not let nested for...of continues satisfy an outer if body', () => {
1100
1176
  const source = `
1101
- export function App({ items }: { items: string[] }) { return <>
1177
+ export function App({ items }: { items: string[] }) @{
1102
1178
  if (items.length) {
1103
1179
  for (const item of items) {
1104
1180
  if (!item) continue
1105
1181
  }
1106
1182
  }
1107
- </>; }`;
1183
+ }`;
1108
1184
 
1109
1185
  const result = compile(source, 'test.tsrx', { collect: true });
1110
- expect(result.errors.map((error) => error.message)).toContain(
1186
+ expect(result.errors.map((error) => error.message)).not.toContain(
1111
1187
  'Component if statements must contain a template in their "then" body. Move the if statement into an effect if it does not render anything.',
1112
1188
  );
1113
1189
  });
1114
1190
 
1115
1191
  it('preserves class extends generic type arguments in volar output', () => {
1116
1192
  const source = `class StringMap extends Map<string, string> {}
1117
- export function App() { return <></>; }`;
1193
+ export function App() @{}`;
1118
1194
 
1119
1195
  expect(() => compile_to_volar_mappings(source, 'test.tsrx', { loose: true })).not.toThrow();
1120
1196
 
@@ -1125,15 +1201,15 @@ export function App() { return <></>; }`;
1125
1201
 
1126
1202
  it('wraps children in normalize_children for explicit children prop passed to component', () => {
1127
1203
  const source = `
1128
- function Card(props) { return <>
1204
+ function Card(props) @{
1129
1205
  <div>{props.children}</div>
1130
- </>; }
1206
+ }
1131
1207
 
1132
- export function App() { return <>
1208
+ export function App() @{
1133
1209
  const content = 'hello';
1134
1210
 
1135
1211
  <Card children={content} />
1136
- </>; }
1212
+ }
1137
1213
  `;
1138
1214
 
1139
1215
  const result = compile(source, 'test.tsrx', { mode: 'client' }).code;
@@ -1144,35 +1220,35 @@ export function App() { return <>
1144
1220
  it(
1145
1221
  'parses a JS statement inside an element with no trailing whitespace before the closing tag',
1146
1222
  () => {
1147
- const source = `function TodoList({ items }: { items: { text: string }[] }) { return <>
1223
+ const source = `function TodoList({ items }: { items: { text: string }[] }) @{
1148
1224
  <ul>var a = "123"</ul>
1149
- </>; }`;
1225
+ }`;
1150
1226
  const ast = parse(source);
1151
- const ul = get_returned_tsrx(ast.body[0]).children.find(
1152
- (n: AST.Node) => n.type === 'Element',
1153
- ) as AST.Element;
1227
+ const returned = get_returned_tsrx(ast.body[0]);
1228
+ const ul =
1229
+ returned.type === 'Element'
1230
+ ? returned
1231
+ : returned.children.find((n: AST.Node) => n.type === 'Element') as AST.Element;
1154
1232
  expect((ul.id as AST.Identifier).name).toBe('ul');
1155
1233
  expect(ul.children).toHaveLength(1);
1156
- const decl = ul.children[0] as unknown as AST.VariableDeclaration;
1157
- expect(decl.type).toBe('VariableDeclaration');
1158
- expect(decl.kind).toBe('var');
1159
- expect((decl.declarations[0].id as AST.Identifier).name).toBe('a');
1160
- expect((decl.declarations[0].init as AST.Literal).value).toBe('123');
1234
+ const text = ul.children[0] as unknown as { type: string; expression: AST.Literal };
1235
+ expect(text.type).toBe('Text');
1236
+ expect(text.expression.value).toBe('var a = "123"');
1161
1237
  expect((ul.closingElement?.name as ESTreeJSX.JSXIdentifier)?.name).toBe('ul');
1162
1238
  },
1163
1239
  );
1164
1240
 
1165
1241
  it('uses spread_props for spreads that may contain children', () => {
1166
1242
  const source = `
1167
- function Card(props) { return <>
1243
+ function Card(props) @{
1168
1244
  <div>{props.children}</div>
1169
- </>; }
1245
+ }
1170
1246
 
1171
- export function App() { return <>
1247
+ export function App() @{
1172
1248
  const props = { children: 'hello' };
1173
1249
 
1174
1250
  <Card {...props} />
1175
- </>; }
1251
+ }
1176
1252
  `;
1177
1253
 
1178
1254
  const result = compile(source, 'test.tsrx', { mode: 'client' }).code;
@@ -1180,19 +1256,12 @@ export function App() { return <>
1180
1256
  expect(result).toContain('_$_.spread_props(');
1181
1257
  });
1182
1258
 
1183
- it('parses less-than comparisons at line start in element children without whitespace', () => {
1184
- const source = `function TodoList({ items }: { items: { text: string }[] }) { return <>
1259
+ it('rejects less-than comparisons at line start in element children without whitespace', () => {
1260
+ const source = `function TodoList({ items }: { items: { text: string }[] }) @{
1185
1261
  <ul>var a = 3
1186
1262
  <4;</ul>
1187
- </>; }`;
1263
+ }`;
1188
1264
 
1189
- const ast = parse(source);
1190
- const ul = get_returned_tsrx(ast.body[0]).children.find(
1191
- (n: AST.Node) => n.type === 'Element',
1192
- ) as AST.Element;
1193
- expect((ul.id as AST.Identifier).name).toBe('ul');
1194
- expect(ul.children.length).toBeGreaterThanOrEqual(1);
1195
- const decl = ul.children[0] as unknown as AST.VariableDeclaration;
1196
- expect(decl.type).toBe('VariableDeclaration');
1265
+ expect(() => parse(source)).toThrow();
1197
1266
  });
1198
1267
  });