ripple 0.3.71 → 0.3.74

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 (165) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/package.json +3 -3
  3. package/src/jsx-runtime.d.ts +2 -8
  4. package/src/runtime/index-client.js +3 -13
  5. package/src/runtime/internal/client/blocks.js +3 -25
  6. package/src/runtime/internal/client/for.js +80 -5
  7. package/src/runtime/internal/client/index.js +0 -2
  8. package/src/runtime/internal/client/types.d.ts +0 -10
  9. package/tests/client/__snapshots__/computed-properties.test.tsrx.snap +8 -0
  10. package/tests/client/__snapshots__/for.test.tsrx.snap +22 -0
  11. package/tests/client/__snapshots__/html.test.tsrx.snap +4 -0
  12. package/tests/client/array/array.copy-within.test.tsrx +19 -19
  13. package/tests/client/array/array.derived.test.tsrx +97 -109
  14. package/tests/client/array/array.iteration.test.tsrx +28 -28
  15. package/tests/client/array/array.mutations.test.tsrx +68 -68
  16. package/tests/client/array/array.static.test.tsrx +82 -92
  17. package/tests/client/array/array.to-methods.test.tsrx +15 -15
  18. package/tests/client/async-suspend.test.tsrx +180 -179
  19. package/tests/client/basic/__snapshots__/basic.attributes.test.tsrx.snap +2 -0
  20. package/tests/client/basic/__snapshots__/basic.rendering.test.tsrx.snap +4 -0
  21. package/tests/client/basic/basic.attributes.test.tsrx +273 -317
  22. package/tests/client/basic/basic.collections.test.tsrx +59 -71
  23. package/tests/client/basic/basic.components.test.tsrx +196 -222
  24. package/tests/client/basic/basic.errors.test.tsrx +72 -78
  25. package/tests/client/basic/basic.events.test.tsrx +80 -85
  26. package/tests/client/basic/basic.get-set.test.tsrx +54 -64
  27. package/tests/client/basic/basic.hmr.test.tsrx +15 -19
  28. package/tests/client/basic/basic.reactivity.test.tsrx +121 -135
  29. package/tests/client/basic/basic.rendering.test.tsrx +273 -178
  30. package/tests/client/basic/basic.utilities.test.tsrx +8 -10
  31. package/tests/client/boundaries.test.tsrx +18 -18
  32. package/tests/client/compiler/compiler.assignments.test.tsrx +77 -76
  33. package/tests/client/compiler/compiler.attributes.test.tsrx +18 -14
  34. package/tests/client/compiler/compiler.basic.test.tsrx +364 -296
  35. package/tests/client/compiler/compiler.regex.test.tsrx +40 -44
  36. package/tests/client/compiler/compiler.tracked-access.test.tsrx +57 -38
  37. package/tests/client/compiler/compiler.try-in-function.test.tsrx +16 -16
  38. package/tests/client/compiler/compiler.typescript.test.tsrx +4 -3
  39. package/tests/client/composite/composite.dynamic-components.test.tsrx +41 -44
  40. package/tests/client/composite/composite.generics.test.tsrx +165 -167
  41. package/tests/client/composite/composite.props.test.tsrx +66 -74
  42. package/tests/client/composite/composite.reactivity.test.tsrx +132 -166
  43. package/tests/client/composite/composite.render.test.tsrx +92 -101
  44. package/tests/client/computed-properties.test.tsrx +14 -18
  45. package/tests/client/context.test.tsrx +14 -18
  46. package/tests/client/css/global-additional-cases.test.tsrx +491 -437
  47. package/tests/client/css/global-advanced-selectors.test.tsrx +169 -153
  48. package/tests/client/css/global-at-rules.test.tsrx +71 -66
  49. package/tests/client/css/global-basic.test.tsrx +105 -98
  50. package/tests/client/css/global-classes-ids.test.tsrx +128 -114
  51. package/tests/client/css/global-combinators.test.tsrx +83 -78
  52. package/tests/client/css/global-complex-nesting.test.tsrx +134 -120
  53. package/tests/client/css/global-edge-cases.test.tsrx +138 -120
  54. package/tests/client/css/global-keyframes.test.tsrx +108 -96
  55. package/tests/client/css/global-nested.test.tsrx +88 -78
  56. package/tests/client/css/global-pseudo.test.tsrx +104 -98
  57. package/tests/client/css/global-scoping.test.tsrx +145 -125
  58. package/tests/client/css/style-identifier.test.tsrx +62 -69
  59. package/tests/client/date.test.tsrx +83 -83
  60. package/tests/client/dynamic-elements.test.tsrx +227 -283
  61. package/tests/client/events.test.tsrx +252 -266
  62. package/tests/client/for.test.tsrx +120 -127
  63. package/tests/client/head.test.tsrx +40 -48
  64. package/tests/client/html.test.tsrx +37 -49
  65. package/tests/client/input-value.test.tsrx +1125 -1354
  66. package/tests/client/lazy-array.test.tsrx +10 -16
  67. package/tests/client/lazy-destructuring.test.tsrx +169 -221
  68. package/tests/client/map.test.tsrx +39 -41
  69. package/tests/client/media-query.test.tsrx +15 -19
  70. package/tests/client/object.test.tsrx +46 -56
  71. package/tests/client/portal.test.tsrx +31 -37
  72. package/tests/client/ref.test.tsrx +173 -193
  73. package/tests/client/return.test.tsrx +62 -37
  74. package/tests/client/set.test.tsrx +33 -33
  75. package/tests/client/svg.test.tsrx +195 -215
  76. package/tests/client/switch.test.tsrx +201 -191
  77. package/tests/client/track-async-hydration.test.tsrx +14 -18
  78. package/tests/client/tracked-index-access.test.tsrx +18 -28
  79. package/tests/client/try.test.tsrx +494 -619
  80. package/tests/client/tsx.test.tsrx +290 -371
  81. package/tests/client/typescript-generics.test.tsrx +121 -129
  82. package/tests/client/url/url.derived.test.tsrx +21 -25
  83. package/tests/client/url/url.parsing.test.tsrx +35 -35
  84. package/tests/client/url/url.partial-removal.test.tsrx +32 -32
  85. package/tests/client/url/url.reactivity.test.tsrx +68 -72
  86. package/tests/client/url/url.serialization.test.tsrx +8 -8
  87. package/tests/client/url-search-params/url-search-params.derived.test.tsrx +21 -27
  88. package/tests/client/url-search-params/url-search-params.initialization.test.tsrx +16 -16
  89. package/tests/client/url-search-params/url-search-params.iteration.test.tsrx +37 -37
  90. package/tests/client/url-search-params/url-search-params.mutation.test.tsrx +56 -60
  91. package/tests/client/url-search-params/url-search-params.retrieval.test.tsrx +32 -34
  92. package/tests/client/url-search-params/url-search-params.serialization.test.tsrx +9 -9
  93. package/tests/client/url-search-params/url-search-params.tracked-url.test.tsrx +10 -10
  94. package/tests/hydration/compiled/client/basic.js +396 -325
  95. package/tests/hydration/compiled/client/composite.js +52 -44
  96. package/tests/hydration/compiled/client/for.js +734 -604
  97. package/tests/hydration/compiled/client/head.js +183 -103
  98. package/tests/hydration/compiled/client/html.js +93 -86
  99. package/tests/hydration/compiled/client/if-children.js +95 -71
  100. package/tests/hydration/compiled/client/if.js +113 -89
  101. package/tests/hydration/compiled/client/mixed-control-flow.js +225 -209
  102. package/tests/hydration/compiled/client/nested-control-flow.js +94 -98
  103. package/tests/hydration/compiled/client/reactivity.js +26 -24
  104. package/tests/hydration/compiled/client/return.js +8 -42
  105. package/tests/hydration/compiled/client/switch.js +208 -173
  106. package/tests/hydration/compiled/client/track-async-serialization.js +176 -128
  107. package/tests/hydration/compiled/client/try.js +29 -21
  108. package/tests/hydration/compiled/server/basic.js +210 -221
  109. package/tests/hydration/compiled/server/composite.js +13 -14
  110. package/tests/hydration/compiled/server/for.js +427 -444
  111. package/tests/hydration/compiled/server/head.js +199 -189
  112. package/tests/hydration/compiled/server/html.js +33 -41
  113. package/tests/hydration/compiled/server/if-children.js +114 -117
  114. package/tests/hydration/compiled/server/if.js +77 -83
  115. package/tests/hydration/compiled/server/mixed-control-flow.js +145 -150
  116. package/tests/hydration/compiled/server/nested-control-flow.js +10 -0
  117. package/tests/hydration/compiled/server/reactivity.js +24 -22
  118. package/tests/hydration/compiled/server/return.js +6 -18
  119. package/tests/hydration/compiled/server/switch.js +179 -176
  120. package/tests/hydration/compiled/server/track-async-serialization.js +88 -70
  121. package/tests/hydration/compiled/server/try.js +31 -35
  122. package/tests/hydration/components/basic.tsrx +216 -286
  123. package/tests/hydration/components/composite.tsrx +32 -42
  124. package/tests/hydration/components/events.tsrx +81 -101
  125. package/tests/hydration/components/for.tsrx +270 -336
  126. package/tests/hydration/components/head.tsrx +43 -39
  127. package/tests/hydration/components/hmr.tsrx +16 -22
  128. package/tests/hydration/components/html-in-template.tsrx +15 -21
  129. package/tests/hydration/components/html.tsrx +442 -526
  130. package/tests/hydration/components/if-children.tsrx +107 -125
  131. package/tests/hydration/components/if.tsrx +68 -90
  132. package/tests/hydration/components/mixed-control-flow.tsrx +65 -72
  133. package/tests/hydration/components/nested-control-flow.tsrx +202 -216
  134. package/tests/hydration/components/portal.tsrx +33 -41
  135. package/tests/hydration/components/reactivity.tsrx +26 -34
  136. package/tests/hydration/components/return.tsrx +4 -6
  137. package/tests/hydration/components/switch.tsrx +73 -78
  138. package/tests/hydration/components/track-async-serialization.tsrx +83 -93
  139. package/tests/hydration/components/try.tsrx +37 -51
  140. package/tests/hydration/switch.test.js +8 -8
  141. package/tests/server/await.test.tsrx +3 -3
  142. package/tests/server/basic.attributes.test.tsrx +120 -167
  143. package/tests/server/basic.components.test.tsrx +163 -197
  144. package/tests/server/basic.test.tsrx +298 -220
  145. package/tests/server/compiler.test.tsrx +142 -72
  146. package/tests/server/composite.props.test.tsrx +54 -58
  147. package/tests/server/composite.test.tsrx +165 -167
  148. package/tests/server/context.test.tsrx +13 -17
  149. package/tests/server/dynamic-elements.test.tsrx +103 -135
  150. package/tests/server/for.test.tsrx +115 -84
  151. package/tests/server/head.test.tsrx +31 -31
  152. package/tests/server/html-nesting-validation.test.tsrx +16 -8
  153. package/tests/server/if.test.tsrx +49 -59
  154. package/tests/server/lazy-destructuring.test.tsrx +288 -366
  155. package/tests/server/return.test.tsrx +58 -36
  156. package/tests/server/streaming-ssr.test.tsrx +4 -4
  157. package/tests/server/style-identifier.test.tsrx +58 -66
  158. package/tests/server/switch.test.tsrx +89 -97
  159. package/tests/server/track-async-serialization.test.tsrx +85 -103
  160. package/tests/server/try.test.tsrx +275 -360
  161. package/tests/utils/ref-types.test.js +72 -0
  162. package/tests/utils/vite-plugin-config.test.js +41 -74
  163. package/types/index.d.ts +1 -0
  164. package/src/runtime/internal/client/compat.js +0 -40
  165. 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
- expect(fragment.type).toBe('Tsrx');
123
- expect(fragment.children[0].type).toBe('ExpressionStatement');
199
+ expect(fragment.type).toBe('TsrxFragment');
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
- expect(fragment.type).toBe('Tsrx');
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 <tsx><span>{'nested'}</span></tsx>;
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,34 +566,33 @@ function App() { return <>
505
566
 
506
567
  it('lowers native expression values in to_ts output', () => {
507
568
  const source = `
508
- function App() { return <>
509
- const nested = <tsx>
569
+ function App() @{
570
+ const nested = <>
510
571
  <span class="nested-tsx">
511
572
  {'inside nested tsx'}
512
573
  </span>
513
- </tsx>;
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
- expect(result).not.toContain('<tsx>');
522
- expect(result).not.toContain('</tsx>');
523
- expect(result).toContain('const nested = <>');
582
+ expect(result).toContain('const nested = <span class="nested-tsx">');
583
+ expect(result).toContain('return content;');
524
584
  expect(result).toContain('const content = <div class="native">');
525
585
  });
526
586
 
527
587
  it('keeps assigned style blocks anchored in to_ts output', () => {
528
588
  const source = `
529
- function App() { return <>
589
+ function App() @{
530
590
  const styles = <style>
531
591
  .logo { display: block; }
532
592
  </style>;
533
593
 
534
594
  <div class={styles.logo} />
535
- </>; }
595
+ }
536
596
  `;
537
597
  const result = compile_to_volar_mappings(source, 'test.tsrx', { loose: true });
538
598
  const source_offset = source.indexOf('<style>') + 1;
@@ -556,22 +616,22 @@ function App() { return <>
556
616
 
557
617
  it('maps identifiers from native expression values in to_ts output', () => {
558
618
  const source = `
559
- function App() { return <>
560
- const nested = <tsx>
619
+ function App() @{
620
+ const nested = <>
561
621
  <span class="nested-tsx">
562
622
  {'inside nested tsx'}
563
623
  </span>
564
- </tsx>;
624
+ </>;
565
625
  const content = <div class="native">{nested}</div>;
566
626
 
567
- {content}
568
- </>; }
627
+ <>{content}</>
628
+ }
569
629
  `;
570
630
  const result = compile_to_volar_mappings(source, 'test.tsrx', { loose: true });
571
631
  const source_declaration = source.indexOf('nested =');
572
632
  const source_reference = source.indexOf('nested}</div>');
573
633
  const generated_declaration = result.code.indexOf('const nested') + 'const '.length;
574
- const generated_reference = result.code.indexOf('nested;', generated_declaration);
634
+ const generated_reference = result.code.indexOf('nested}</div>', generated_declaration);
575
635
 
576
636
  function find_mapping(source_offset: number, generated_offset: number) {
577
637
  return result.mappings.find(
@@ -612,9 +672,9 @@ type Props<Item> = {
612
672
  items: readonly Item[];
613
673
  }
614
674
 
615
- export function MyComponent<Item>(props: Props<Item>) { return <>
675
+ export function MyComponent<Item>(props: Props<Item>) @{
616
676
  <div />
617
- </>; }
677
+ }
618
678
  `;
619
679
  const result = compile_to_volar_mappings(source, 'test.tsrx').code;
620
680
 
@@ -623,22 +683,23 @@ export function MyComponent<Item>(props: Props<Item>) { return <>
623
683
 
624
684
  it('preserves arrow functions that return TSRX in to_ts output', () => {
625
685
  const source = `
626
- const Inline = (props: { x: string }) => <>
686
+ const Inline = (props: { x: string }) => @{
627
687
  <div>{props.x}</div>
628
- </>
688
+ }
629
689
  `;
630
690
  const result = compile_to_volar_mappings(source, 'test.tsrx').code;
631
691
 
632
- expect(result).toContain('const Inline = (props: { x: string }) => <div>');
692
+ expect(result).toContain('const Inline = (props: { x: string }) => {');
693
+ expect(result).toContain('return <div>');
633
694
  expect(result).not.toContain('function Inline');
634
695
  expect(result).not.toContain('function (props');
635
696
  });
636
697
 
637
698
  it('preserves function expressions that return TSRX in to_ts output', () => {
638
699
  const source = `
639
- const Inline = function(props: { x: string }) { return <>
700
+ const Inline = function(props: { x: string }) @{
640
701
  <div>{props.x}</div>
641
- </>; }
702
+ }
642
703
  `;
643
704
  const result = compile_to_volar_mappings(source, 'test.tsrx').code;
644
705
 
@@ -651,13 +712,13 @@ const Inline = function(props: { x: string }) { return <>
651
712
  const source = `
652
713
  type User = { name: string };
653
714
 
654
- function RenderProp<Item>(props: { children: (item: Item) => any }) { return <></>; }
715
+ function RenderProp<Item>(props: { children: (item: Item) => any }) @{}
655
716
 
656
- export function App() { return <>
717
+ export function App() @{
657
718
  <RenderProp<User>>
658
719
  {(item) => item.name}
659
720
  </RenderProp>
660
- </>; }
721
+ }
661
722
  `;
662
723
  const result = compile_to_volar_mappings(source, 'test.tsrx').code;
663
724
 
@@ -666,13 +727,13 @@ export function App() { return <>
666
727
 
667
728
  it('preserves generic type arguments on self-closing JSX component tags in to_ts output', () => {
668
729
  const source = `
669
- function Box<T>({ value }: { value: T }) { return <>
730
+ function Box<T>({ value }: { value: T }) @{
670
731
  <div>{String(value)}</div>
671
- </>; }
732
+ }
672
733
 
673
- export function App() { return <>
734
+ export function App() @{
674
735
  <Box<string> value="hi" />
675
- </>; }
736
+ }
676
737
  `;
677
738
  const result = compile_to_volar_mappings(source, 'test.tsrx').code;
678
739
 
@@ -772,9 +833,9 @@ interface Props extends PolymorphicProps<'div'> {
772
833
  id: string;
773
834
  }
774
835
 
775
- export function App(props: Props) { return <>
836
+ export function App(props: Props) @{
776
837
  <div id={props.id} />
777
- </>; }
838
+ }
778
839
  `;
779
840
 
780
841
  const result = compile_to_volar_mappings(source, 'test.tsrx').code;
@@ -785,7 +846,7 @@ export function App(props: Props) { return <>
785
846
  it('handles if-else expression statements in Volar mappings', () => {
786
847
  const source = `
787
848
  import { track } from 'ripple';
788
- export function App() { return <>
849
+ export function App() @{
789
850
  let &[level] = track(1);
790
851
 
791
852
  <button
@@ -797,58 +858,61 @@ export function App() { return <>
797
858
  >
798
859
  {'Toggle'}
799
860
  </button>
800
- </>; }
861
+ }
801
862
  `;
802
863
 
803
864
  expect(() => compile_to_volar_mappings(source, 'test.tsrx')).not.toThrow();
804
865
  });
805
866
 
806
- 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', () => {
807
868
  const code = `
808
- function Card(props) { return <>
869
+ function Card(props) @{
809
870
  <div class="card">
810
871
  {props.children}
811
872
  </div>
812
- </>; }
873
+ }
813
874
 
814
- export function App() { return <>
815
- function children() { return <>
875
+ export function App() @{
876
+ function children() @{
816
877
  <p>{'Card content here'}</p>
817
- </>; }
878
+ }
818
879
 
819
880
  <Card {children} />
820
881
 
821
882
  const test = 5;
822
883
 
823
884
  <div>{test}</div>
824
- </>; }
885
+ }
825
886
  `;
826
- 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
+ );
827
890
  });
828
891
 
829
- it('allows component functions inside composite children', () => {
892
+ it('allows component functions passed to composite children', () => {
830
893
  const source = `
831
- export function App() { return <>
832
- <ark.div class="host-class" data-value="42">
833
- function asChild({ children, href, ...rest }: { href: string; [key: string]: any }) { return <>
834
- <a id="aschild-anchor" {href} {...rest} data-extra="yes">{'Link'}</a>
835
- </>; }
836
- </ark.div>
837
- </>; }
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
+ }
838
901
  `;
839
902
 
840
903
  expect(() => compile_to_volar_mappings(source, 'test.tsrx')).not.toThrow();
841
904
  });
842
905
 
843
- it('allows parent element attributes referencing child-declared functions', () => {
906
+ it('allows parent element attributes referencing setup-declared functions', () => {
844
907
  const source = `
845
- export function App() { return <>
908
+ export function App() @{
909
+ function Z() @{
910
+ <div>{'hello'}</div>
911
+ }
912
+
846
913
  <Test {Z}>
847
- function Z() { return <>
848
- <div>{'hello'}</div>
849
- </>; }
850
914
  </Test>
851
- </>; }
915
+ }
852
916
  `;
853
917
 
854
918
  expect(() => compile(source, 'test.tsrx')).not.toThrow();
@@ -856,13 +920,13 @@ export function App() { return <>
856
920
 
857
921
  it('preserves explicit component props in Volar mappings', () => {
858
922
  const source = `
859
- export function App() { return <>
860
- 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 }) @{
861
925
  <a id="aschild-anchor" {href} {...rest} data-extra="yes">{'Link'}</a>
862
- </>; }
926
+ }
863
927
 
864
928
  <ark.div class="host-class" data-value="42" {asChild} />
865
- </>; }
929
+ }
866
930
  `;
867
931
  const result = compile_to_volar_mappings(source, 'test.tsrx').code;
868
932
 
@@ -870,19 +934,58 @@ export function App() { return <>
870
934
  expect(result).not.toContain('children={() =>');
871
935
  });
872
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
+
873
976
  it('merges explicit children prop with implicit children in client output', () => {
874
977
  const source = `
875
- function Card(props) { return <>
978
+ function Card(props) @{
876
979
  <div>{props.children}</div>
877
- </>; }
980
+ }
878
981
 
879
- export function App() { return <>
982
+ export function App() @{
880
983
  const fallback = 'fallback';
881
984
 
882
985
  <Card children={fallback}>
883
986
  <span>{'content'}</span>
884
987
  </Card>
885
- </>; }
988
+ }
886
989
  `;
887
990
 
888
991
  const result = compile(source, 'test.tsrx', { mode: 'client' }).code;
@@ -901,10 +1004,10 @@ class Test {
901
1004
  }
902
1005
  }
903
1006
 
904
- export function App() { return <>
1007
+ export function App() @{
905
1008
  const test = new Test();
906
1009
  <div>{test.count}</div>
907
- </>; }
1010
+ }
908
1011
  `;
909
1012
  expect(() => compile(code, 'test.tsrx')).not.toThrow();
910
1013
  });
@@ -920,10 +1023,10 @@ class Store {
920
1023
  }
921
1024
  }
922
1025
 
923
- export function App() { return <>
1026
+ export function App() @{
924
1027
  const store = new Store();
925
1028
  <div>{store.count}</div>
926
- </>; }
1029
+ }
927
1030
  `;
928
1031
  const result = compile(source, 'test.tsrx', { mode: 'client' });
929
1032
  const code = result.code;
@@ -937,14 +1040,14 @@ export function App() { return <>
937
1040
  const source = `
938
1041
  import { track, effect, untrack } from 'ripple';
939
1042
 
940
- function App() { return <>
1043
+ function App() @{
941
1044
  let &[count] = track(0);
942
1045
 
943
1046
  effect(() => {
944
1047
  const snapshot = untrack(() => count);
945
1048
  console.log(snapshot);
946
1049
  });
947
- </>; }
1050
+ }
948
1051
  `;
949
1052
 
950
1053
  const ast = parse(source);
@@ -957,10 +1060,10 @@ function App() { return <>
957
1060
  it('collects duplicate declaration parser errors in loose mode', () => {
958
1061
  const source = `
959
1062
  import { track } from 'ripple';
960
- export function App() { return <>
1063
+ export function App() @{
961
1064
  let test = track(false);
962
1065
  let test = 'hey';
963
- </>; }
1066
+ }
964
1067
  `;
965
1068
 
966
1069
  expect(() => compile(source, 'test.tsrx')).toThrow(
@@ -986,11 +1089,11 @@ export function App() { return <>
986
1089
  import { loadUser as getUser } from server;
987
1090
  import { loadUser } from server;
988
1091
 
989
- function App() { return <>
1092
+ function App() @{
990
1093
  const user = getUser();
991
1094
  const user2 = loadUser();
992
1095
  <div>{user.id}{user2.id}</div>
993
- </>; }`;
1096
+ }`;
994
1097
  const result = compile_to_volar_mappings(source, 'test.tsrx', { loose: true });
995
1098
  const generated_member = '_$_server_$_.loadUser';
996
1099
  const generated_member_offset = result.code.indexOf(generated_member);
@@ -1046,49 +1149,21 @@ function App() { return <>
1046
1149
  ).toBeDefined();
1047
1150
  });
1048
1151
 
1049
- it('reports return statements inside returned TSRX fragments in Volar mappings', () => {
1050
- const result = compile_to_volar_mappings(
1051
- `function App() { return <>
1152
+ it('allows return statements inside returned TSRX fragments in Volar mappings', () => {
1153
+ const result = compile_to_volar_mappings(`function App() @{
1052
1154
  return <div />;
1053
- </>; }`,
1054
- 'test.tsrx',
1055
- );
1056
-
1057
- expect(result.errors.map((error) => error.code)).toEqual(['tsrx-template-return-statement']);
1058
- });
1059
-
1060
- it('throws for unclosed tsx compat tags instead of hanging', () => {
1061
- const source = `export function App() { return <tsx:react>1; }`;
1062
-
1063
- expect(() => compile(source, 'test.tsrx')).toThrow(
1064
- 'Unclosed tag \'<tsx:react>\'. Expected \'</tsx:react>\' before end of template.',
1065
- );
1066
- });
1067
-
1068
- it('recovers unclosed tsx compat tags in loose mode', () => {
1069
- const source = `export function App() { return <tsx:react>1; }`;
1155
+ }`, 'test.tsrx');
1070
1156
 
1071
- expect(() => compile_to_volar_mappings(source, 'test.tsrx', { loose: true })).not.toThrow();
1072
-
1073
- const result = compile_to_volar_mappings(source, 'test.tsrx', { loose: true });
1074
1157
  expect(result.errors).toEqual([]);
1075
1158
  });
1076
1159
 
1077
- it('throws for unclosed tsx compat tags outside loose mode', () => {
1078
- const source = `export function App() { return <tsx:react>1; }`;
1079
-
1080
- expect(() => compile(source, 'test.tsrx', { collect: true })).toThrow(
1081
- 'Not implemented: TsxCompat',
1082
- );
1083
- });
1084
-
1085
1160
  it('collects analyzer errors outside loose mode', () => {
1086
1161
  const source = `
1087
1162
  import { track } from 'ripple';
1088
1163
 
1089
1164
  const outside = track(0);
1090
1165
 
1091
- export function App() { return <></>; }
1166
+ export function App() @{}
1092
1167
  `;
1093
1168
 
1094
1169
  const result = compile(source, 'test.tsrx', { collect: true });
@@ -1099,23 +1174,23 @@ export function App() { return <></>; }
1099
1174
 
1100
1175
  it('does not let nested for...of continues satisfy an outer if body', () => {
1101
1176
  const source = `
1102
- export function App({ items }: { items: string[] }) { return <>
1177
+ export function App({ items }: { items: string[] }) @{
1103
1178
  if (items.length) {
1104
1179
  for (const item of items) {
1105
1180
  if (!item) continue
1106
1181
  }
1107
1182
  }
1108
- </>; }`;
1183
+ }`;
1109
1184
 
1110
1185
  const result = compile(source, 'test.tsrx', { collect: true });
1111
- expect(result.errors.map((error) => error.message)).toContain(
1186
+ expect(result.errors.map((error) => error.message)).not.toContain(
1112
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.',
1113
1188
  );
1114
1189
  });
1115
1190
 
1116
1191
  it('preserves class extends generic type arguments in volar output', () => {
1117
1192
  const source = `class StringMap extends Map<string, string> {}
1118
- export function App() { return <></>; }`;
1193
+ export function App() @{}`;
1119
1194
 
1120
1195
  expect(() => compile_to_volar_mappings(source, 'test.tsrx', { loose: true })).not.toThrow();
1121
1196
 
@@ -1126,15 +1201,15 @@ export function App() { return <></>; }`;
1126
1201
 
1127
1202
  it('wraps children in normalize_children for explicit children prop passed to component', () => {
1128
1203
  const source = `
1129
- function Card(props) { return <>
1204
+ function Card(props) @{
1130
1205
  <div>{props.children}</div>
1131
- </>; }
1206
+ }
1132
1207
 
1133
- export function App() { return <>
1208
+ export function App() @{
1134
1209
  const content = 'hello';
1135
1210
 
1136
1211
  <Card children={content} />
1137
- </>; }
1212
+ }
1138
1213
  `;
1139
1214
 
1140
1215
  const result = compile(source, 'test.tsrx', { mode: 'client' }).code;
@@ -1145,35 +1220,35 @@ export function App() { return <>
1145
1220
  it(
1146
1221
  'parses a JS statement inside an element with no trailing whitespace before the closing tag',
1147
1222
  () => {
1148
- const source = `function TodoList({ items }: { items: { text: string }[] }) { return <>
1223
+ const source = `function TodoList({ items }: { items: { text: string }[] }) @{
1149
1224
  <ul>var a = "123"</ul>
1150
- </>; }`;
1225
+ }`;
1151
1226
  const ast = parse(source);
1152
- const ul = get_returned_tsrx(ast.body[0]).children.find(
1153
- (n: AST.Node) => n.type === 'Element',
1154
- ) 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;
1155
1232
  expect((ul.id as AST.Identifier).name).toBe('ul');
1156
1233
  expect(ul.children).toHaveLength(1);
1157
- const decl = ul.children[0] as unknown as AST.VariableDeclaration;
1158
- expect(decl.type).toBe('VariableDeclaration');
1159
- expect(decl.kind).toBe('var');
1160
- expect((decl.declarations[0].id as AST.Identifier).name).toBe('a');
1161
- 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"');
1162
1237
  expect((ul.closingElement?.name as ESTreeJSX.JSXIdentifier)?.name).toBe('ul');
1163
1238
  },
1164
1239
  );
1165
1240
 
1166
1241
  it('uses spread_props for spreads that may contain children', () => {
1167
1242
  const source = `
1168
- function Card(props) { return <>
1243
+ function Card(props) @{
1169
1244
  <div>{props.children}</div>
1170
- </>; }
1245
+ }
1171
1246
 
1172
- export function App() { return <>
1247
+ export function App() @{
1173
1248
  const props = { children: 'hello' };
1174
1249
 
1175
1250
  <Card {...props} />
1176
- </>; }
1251
+ }
1177
1252
  `;
1178
1253
 
1179
1254
  const result = compile(source, 'test.tsrx', { mode: 'client' }).code;
@@ -1181,19 +1256,12 @@ export function App() { return <>
1181
1256
  expect(result).toContain('_$_.spread_props(');
1182
1257
  });
1183
1258
 
1184
- it('parses less-than comparisons at line start in element children without whitespace', () => {
1185
- 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 }[] }) @{
1186
1261
  <ul>var a = 3
1187
1262
  <4;</ul>
1188
- </>; }`;
1263
+ }`;
1189
1264
 
1190
- const ast = parse(source);
1191
- const ul = get_returned_tsrx(ast.body[0]).children.find(
1192
- (n: AST.Node) => n.type === 'Element',
1193
- ) as AST.Element;
1194
- expect((ul.id as AST.Identifier).name).toBe('ul');
1195
- expect(ul.children.length).toBeGreaterThanOrEqual(1);
1196
- const decl = ul.children[0] as unknown as AST.VariableDeclaration;
1197
- expect(decl.type).toBe('VariableDeclaration');
1265
+ expect(() => parse(source)).toThrow();
1198
1266
  });
1199
1267
  });