ripple 0.1.1 → 0.2.0

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 (43) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +56 -24
  3. package/src/ai.js +292 -0
  4. package/src/compiler/errors.js +26 -0
  5. package/src/compiler/index.js +26 -0
  6. package/src/compiler/phases/1-parse/index.js +543 -0
  7. package/src/compiler/phases/1-parse/style.js +566 -0
  8. package/src/compiler/phases/2-analyze/index.js +509 -0
  9. package/src/compiler/phases/2-analyze/prune.js +572 -0
  10. package/src/compiler/phases/3-transform/index.js +1572 -0
  11. package/src/compiler/phases/3-transform/segments.js +91 -0
  12. package/src/compiler/phases/3-transform/stylesheet.js +372 -0
  13. package/src/compiler/scope.js +421 -0
  14. package/src/compiler/utils.js +552 -0
  15. package/src/constants.js +4 -0
  16. package/src/jsx-runtime.d.ts +94 -0
  17. package/src/jsx-runtime.js +46 -0
  18. package/src/runtime/array.js +215 -0
  19. package/src/runtime/index.js +39 -0
  20. package/src/runtime/internal/client/blocks.js +247 -0
  21. package/src/runtime/internal/client/constants.js +23 -0
  22. package/src/runtime/internal/client/events.js +223 -0
  23. package/src/runtime/internal/client/for.js +388 -0
  24. package/src/runtime/internal/client/if.js +35 -0
  25. package/src/runtime/internal/client/index.js +53 -0
  26. package/src/runtime/internal/client/operations.js +72 -0
  27. package/src/runtime/internal/client/portal.js +33 -0
  28. package/src/runtime/internal/client/render.js +156 -0
  29. package/src/runtime/internal/client/runtime.js +909 -0
  30. package/src/runtime/internal/client/template.js +51 -0
  31. package/src/runtime/internal/client/try.js +139 -0
  32. package/src/runtime/internal/client/utils.js +16 -0
  33. package/src/utils/ast.js +214 -0
  34. package/src/utils/builders.js +733 -0
  35. package/src/utils/patterns.js +23 -0
  36. package/src/utils/sanitize_template_string.js +7 -0
  37. package/test-mappings.js +0 -0
  38. package/types/index.d.ts +2 -0
  39. package/.npmignore +0 -2
  40. package/History.md +0 -3
  41. package/Readme.md +0 -151
  42. package/lib/exec/index.js +0 -60
  43. package/ripple.js +0 -645
@@ -0,0 +1,1572 @@
1
+ import { walk } from 'zimmerframe';
2
+ import path from 'node:path';
3
+ import { print } from 'esrap';
4
+ import tsx from 'esrap/languages/tsx';
5
+ import * as b from '../../../utils/builders.js';
6
+ import { IS_CONTROLLED, TEMPLATE_FRAGMENT } from '../../../constants.js';
7
+ import { sanitize_template_string } from '../../../utils/sanitize_template_string.js';
8
+ import {
9
+ build_hoisted_params,
10
+ is_event_attribute,
11
+ is_inside_component,
12
+ is_tracked_name,
13
+ is_passive_event,
14
+ build_assignment,
15
+ visit_assignment_expression,
16
+ escape_html,
17
+ is_boolean_attribute,
18
+ is_dom_property,
19
+ is_svelte_import,
20
+ is_declared_within_component,
21
+ is_inside_call_expression
22
+ } from '../../utils.js';
23
+ import is_reference from 'is-reference';
24
+ import { extract_paths, object } from '../../../utils/ast.js';
25
+ import { render_stylesheets } from './stylesheet.js';
26
+
27
+ function visit_function(node, context) {
28
+ if (context.state.to_ts) {
29
+ context.next(context.state);
30
+ return;
31
+ }
32
+ const metadata = node.metadata;
33
+ const state = context.state;
34
+
35
+ if (metadata?.hoisted === true) {
36
+ const params = build_hoisted_params(node, context);
37
+
38
+ return /** @type {FunctionExpression} */ ({
39
+ ...node,
40
+ params,
41
+ body: context.visit(node.body, state)
42
+ });
43
+ }
44
+
45
+ let body = context.visit(node.body, state);
46
+
47
+ if (metadata.tracked === true) {
48
+ return /** @type {FunctionExpression} */ ({
49
+ ...node,
50
+ params: node.params.map((param) => context.visit(param, state)),
51
+ body:
52
+ body.type === 'BlockStatement'
53
+ ? { ...body, body: [b.var('__block', b.call('$.scope')), ...body.body] }
54
+ : body
55
+ });
56
+ }
57
+
58
+ context.next(state);
59
+ }
60
+
61
+ function build_getter(node, context) {
62
+ const state = context.state;
63
+
64
+ for (let i = context.path.length - 1; i >= 0; i -= 1) {
65
+ const binding = state.scope.get(node.name);
66
+
67
+ // don't transform the declaration itself
68
+ if (node !== binding?.node && binding?.transform) {
69
+ return binding.transform.read(node);
70
+ }
71
+ }
72
+
73
+ return node;
74
+ }
75
+
76
+ const visitors = {
77
+ _: function set_scope(node, { next, state }) {
78
+ const scope = state.scopes.get(node);
79
+
80
+ if (scope && scope !== state.scope) {
81
+ return next({ ...state, scope });
82
+ } else {
83
+ return next();
84
+ }
85
+ },
86
+
87
+ Identifier(node, context) {
88
+ const parent = /** @type {Node} */ (context.path.at(-1));
89
+
90
+ if (is_reference(node, parent) && !context.state.to_ts) {
91
+ const binding = context.state.scope.get(node.name);
92
+ if (
93
+ context.state.metadata?.tracking === false &&
94
+ is_tracked_name(node.name) &&
95
+ binding?.node !== node
96
+ ) {
97
+ context.state.metadata.tracking = true;
98
+ }
99
+
100
+ if (node.name === 'structuredClone' && binding === null) {
101
+ return b.id('$.structured_clone');
102
+ }
103
+
104
+ return build_getter(node, context);
105
+ }
106
+ },
107
+
108
+ ImportDeclaration(node, context) {
109
+ if (!context.state.to_ts && node.importKind === 'type') {
110
+ return b.empty;
111
+ }
112
+ return context.next();
113
+ },
114
+
115
+ CallExpression(node, context) {
116
+ const callee = node.callee;
117
+ const parent = context.path.at(-1);
118
+
119
+ if (context.state.metadata?.tracking === false) {
120
+ context.state.metadata.tracking = true;
121
+ }
122
+
123
+ if (
124
+ context.state.to_ts ||
125
+ (parent?.type === 'MemberExpression' && parent.property === node) ||
126
+ is_inside_call_expression(context) ||
127
+ !context.path.some((node) => node.type === 'Component') ||
128
+ (is_svelte_import(callee, context) &&
129
+ (callee.type !== 'Identifier' ||
130
+ (callee.name !== 'array' && callee.name !== 'deferred'))) ||
131
+ is_declared_within_component(callee, context)
132
+ ) {
133
+ return context.next();
134
+ }
135
+
136
+ return b.call(
137
+ '$.with_scope',
138
+ b.id('__block'),
139
+ b.thunk({
140
+ ...node,
141
+ callee: context.visit(callee),
142
+ arguments: node.arguments.map((arg) => context.visit(arg))
143
+ })
144
+ );
145
+ },
146
+
147
+ MemberExpression(node, context) {
148
+ const parent = context.path.at(-1);
149
+
150
+ if (parent.type !== 'AssignmentExpression') {
151
+ const object = node.object;
152
+ const property = node.property;
153
+ const tracked_name =
154
+ property.type === 'Identifier'
155
+ ? is_tracked_name(property.name)
156
+ : property.type === 'Literal' && is_tracked_name(property.value);
157
+
158
+ // TODO should we enforce that the identifier is tracked too?
159
+ if ((node.computed && property.type === 'Identifier') || tracked_name) {
160
+ if (context.state.metadata?.tracking === false) {
161
+ context.state.metadata.tracking = true;
162
+ }
163
+
164
+ if (tracked_name) {
165
+ return b.call(
166
+ '$.get_property',
167
+ context.visit(object),
168
+ property.type === 'Identifier' ? b.literal(property.name) : property,
169
+ node.optional ? b.true : undefined
170
+ );
171
+ } else {
172
+ return b.call(
173
+ '$.get_property',
174
+ context.visit(object),
175
+ context.visit(property),
176
+ node.optional ? b.true : undefined
177
+ );
178
+ }
179
+ }
180
+
181
+ if (object.type === 'Identifier' && object.name === 'Object') {
182
+ const binding = context.state.scope.get(object.name);
183
+
184
+ if (binding === null) {
185
+ if (property.type === 'Identifier' && property.name === 'values') {
186
+ return b.id('$.object_values');
187
+ } else if (property.type === 'Identifier' && property.name === 'entries') {
188
+ return b.id('$.object_entries');
189
+ } else if (property.type === 'Identifier' && property.name === 'keys') {
190
+ return b.id('$.object_keys');
191
+ }
192
+ }
193
+ }
194
+ }
195
+
196
+ if (node.object.type === 'MemberExpression' && node.object.optional) {
197
+ const metadata = { tracking: false, await: false };
198
+
199
+ const object = context.visit(node.object, { ...context.state, metadata });
200
+
201
+ if (metadata.tracking) {
202
+ if (context.state.metadata?.tracking === false) {
203
+ context.state.metadata.tracking = true;
204
+ }
205
+
206
+ return {
207
+ ...node,
208
+ optional: true,
209
+ object,
210
+ property: context.visit(node.property)
211
+ };
212
+ }
213
+ if (metadata.await) {
214
+ if (context.state.metadata?.await === false) {
215
+ context.state.metadata.await = true;
216
+ }
217
+ }
218
+ } else {
219
+ context.next();
220
+ }
221
+ },
222
+
223
+ SpreadElement(node, context) {
224
+ const parent = context.path.at(-1);
225
+
226
+ if (parent.type === 'ObjectExpression') {
227
+ return b.spread(b.call('$.spread_object', context.visit(node.argument)));
228
+ }
229
+
230
+ context.next();
231
+ },
232
+
233
+ VariableDeclaration(node, context) {
234
+ const declarations = [];
235
+
236
+ for (const declarator of node.declarations) {
237
+ const metadata = declarator.metadata;
238
+
239
+ if (declarator.id.type === 'Identifier') {
240
+ const binding = context.state.scope.get(declarator.id.name);
241
+
242
+ if (!context.state.to_ts) {
243
+ delete declarator.id.typeAnnotation;
244
+ }
245
+
246
+ if (binding !== null && binding.kind === 'tracked' && !context.state.to_ts) {
247
+ let expression;
248
+
249
+ if (metadata.tracking && metadata.await) {
250
+ expression = b.call(
251
+ b.await(
252
+ b.call(
253
+ '$.resume_context',
254
+ b.call(
255
+ '$.async_computed',
256
+ b.thunk(context.visit(declarator.init), true),
257
+ b.id('__block')
258
+ )
259
+ )
260
+ )
261
+ );
262
+ } else if (metadata.tracking && !metadata.await) {
263
+ expression = b.call(
264
+ '$.computed',
265
+ b.thunk(context.visit(declarator.init)),
266
+ b.id('__block')
267
+ );
268
+ } else {
269
+ expression = b.call(
270
+ '$.tracked',
271
+ declarator.init === null ? undefined : context.visit(declarator.init),
272
+ b.id('__block')
273
+ );
274
+ }
275
+
276
+ declarations.push(b.declarator(declarator.id, expression));
277
+ } else {
278
+ declarations.push(context.visit(declarator));
279
+ }
280
+ } else {
281
+ const paths = extract_paths(declarator.id);
282
+ const has_tracked = paths.some(
283
+ (path) => path.node.type === 'Identifier' && is_tracked_name(path.node.name)
284
+ );
285
+
286
+ if (!context.state.to_ts) {
287
+ delete declarator.id.typeAnnotation;
288
+ }
289
+
290
+ if (!has_tracked || context.state.to_ts) {
291
+ declarations.push(context.visit(declarator));
292
+ continue;
293
+ }
294
+
295
+ const transformed = declarator.transformed;
296
+ let expression;
297
+
298
+ if (metadata.tracking && metadata.await) {
299
+ // TODO
300
+ debugger;
301
+ } else if (metadata.tracking && !metadata.await) {
302
+ expression = b.call(
303
+ '$.computed',
304
+ b.thunk(context.visit(declarator.init)),
305
+ b.id('__block')
306
+ );
307
+ } else {
308
+ expression = context.visit(declarator.init);
309
+ }
310
+
311
+ declarations.push(b.declarator(transformed, expression));
312
+ }
313
+ }
314
+
315
+ return { ...node, declarations };
316
+ },
317
+
318
+ FunctionDeclaration(node, context) {
319
+ return visit_function(node, context);
320
+ },
321
+ ArrowFunctionExpression(node, context) {
322
+ return visit_function(node, context);
323
+ },
324
+ FunctionExpression(node, context) {
325
+ return visit_function(node, context);
326
+ },
327
+
328
+ Element(node, context) {
329
+ const { state, visit } = context;
330
+
331
+ const type = node.id.name;
332
+ const is_dom_element = type[0].toLowerCase() === type[0];
333
+
334
+ const handle_static_attr = (name, value) => {
335
+ state.template.push(
336
+ b.literal(
337
+ ` ${name}${
338
+ is_boolean_attribute(name) && value === true
339
+ ? ''
340
+ : `="${value === true ? '' : escape_html(value, true)}"`
341
+ }`
342
+ )
343
+ );
344
+ };
345
+
346
+ if (is_dom_element) {
347
+ let class_attribute = null;
348
+
349
+ state.template.push(`<${type}`);
350
+
351
+ for (const attr of node.attributes) {
352
+ if (attr.type === 'Attribute') {
353
+ if (attr.name.type === 'Identifier') {
354
+ const name = attr.name.name;
355
+
356
+ if (attr.value.type === 'Literal' && name !== 'class') {
357
+ handle_static_attr(name, attr.value.value);
358
+ continue;
359
+ }
360
+
361
+ if (name === 'class' || name === '$class') {
362
+ class_attribute = attr;
363
+
364
+ continue;
365
+ }
366
+
367
+ if (name === 'value' || name === '$value') {
368
+ const id = state.flush_node();
369
+ const expression = visit(attr.value, state);
370
+
371
+ if (name === '$value') {
372
+ state.update.push(b.stmt(b.call('$.set_value', id, expression)));
373
+ } else {
374
+ state.init.push(b.stmt(b.call('$.set_value', id, expression)));
375
+ }
376
+
377
+ continue;
378
+ }
379
+
380
+ if (name === 'checked' || name === '$checked') {
381
+ const id = state.flush_node();
382
+ const expression = visit(attr.value, state);
383
+
384
+ if (name === '$checked') {
385
+ state.update.push(b.stmt(b.call('$.set_checked', id, expression)));
386
+ } else {
387
+ state.init.push(b.stmt(b.call('$.set_checked', id, expression)));
388
+ }
389
+ continue;
390
+ }
391
+
392
+ if (name === 'selected' || name === '$selected') {
393
+ const id = state.flush_node();
394
+ const expression = visit(attr.value, state);
395
+
396
+ if (name === '$selected') {
397
+ state.update.push(b.stmt(b.call('$.set_selected', id, expression)));
398
+ } else {
399
+ state.init.push(b.stmt(b.call('$.set_selected', id, expression)));
400
+ }
401
+ continue;
402
+ }
403
+
404
+ if (name === '$ref') {
405
+ const id = state.flush_node();
406
+ const value = attr.value;
407
+
408
+ state.init.push(b.stmt(b.call('$.set_ref', id, visit(value, state))));
409
+ continue;
410
+ }
411
+
412
+ if (is_event_attribute(name)) {
413
+ const event_name = name.slice(2).toLowerCase();
414
+ let handler = visit(attr.value, state);
415
+ let capture = false; // TODO
416
+
417
+ if (attr.metadata?.delegated) {
418
+ let delegated_assignment;
419
+
420
+ if (!state.events.has(event_name)) {
421
+ state.events.add(event_name);
422
+ }
423
+
424
+ // Hoist function if we can, otherwise we leave the function as is
425
+ if (attr.metadata.delegated.hoisted) {
426
+ if (attr.metadata.delegated.function === attr.value) {
427
+ const func_name = state.scope.root.unique('on_' + event_name);
428
+ state.hoisted.push(b.var(func_name, handler));
429
+ handler = func_name;
430
+ }
431
+
432
+ const hoisted_params = /** @type {Expression[]} */ (
433
+ attr.metadata.delegated.function.metadata.hoisted_params
434
+ );
435
+
436
+ const args = [handler, b.id('__block'), ...hoisted_params];
437
+ delegated_assignment = b.array(args);
438
+ } else if (
439
+ handler.type === 'Identifier' &&
440
+ is_declared_within_component(handler, context)
441
+ ) {
442
+ delegated_assignment = handler;
443
+ } else {
444
+ delegated_assignment = b.array([handler, b.id('__block')]);
445
+ }
446
+ const id = state.flush_node();
447
+
448
+ state.init.push(
449
+ b.stmt(b.assignment('=', b.member(id, '__' + event_name), delegated_assignment))
450
+ );
451
+ } else {
452
+ const passive = is_passive_event(event_name);
453
+ const id = state.flush_node();
454
+
455
+ state.init.push(
456
+ b.stmt(
457
+ b.call(
458
+ '$.event',
459
+ b.literal(event_name),
460
+ id,
461
+ handler,
462
+ capture && b.true,
463
+ passive === undefined ? undefined : b.literal(passive)
464
+ )
465
+ )
466
+ );
467
+ }
468
+
469
+ continue;
470
+ }
471
+
472
+ // All other attributes
473
+ if (is_tracked_name(name)) {
474
+ const attribute = name.slice(1);
475
+ const id = state.flush_node();
476
+ const expression = visit(attr.value, state);
477
+
478
+ if (is_dom_property(attribute)) {
479
+ state.update.push(b.stmt(b.assignment('=', b.member(id, attribute), expression)));
480
+ } else {
481
+ state.update.push(
482
+ b.stmt(b.call('$.set_attribute', id, b.literal(attribute), expression))
483
+ );
484
+ }
485
+ } else {
486
+ const id = state.flush_node();
487
+ const expression = visit(attr.value, state);
488
+
489
+ if (is_dom_property(name)) {
490
+ state.init.push(b.stmt(b.assignment('=', b.member(id, name), expression)));
491
+ } else {
492
+ state.init.push(b.stmt(b.call('$.set_attribute', id, b.literal(name), expression)));
493
+ }
494
+ }
495
+ }
496
+ }
497
+ }
498
+
499
+ if (class_attribute !== null) {
500
+ if (class_attribute.value.type === 'Literal') {
501
+ let value = class_attribute.value.value;
502
+
503
+ if (node.metadata.scoped && state.component.css) {
504
+ value = `${state.component.css.hash} ${value}`;
505
+ }
506
+
507
+ handle_static_attr(class_attribute.name.name, value);
508
+ } else {
509
+ const id = state.flush_node();
510
+ let expression = visit(class_attribute.value, state);
511
+
512
+ if (node.metadata.scoped && state.component.css) {
513
+ expression = b.binary('+', b.literal(state.component.css.hash + ' '), expression);
514
+ }
515
+
516
+ if (class_attribute.name.name === '$class') {
517
+ state.update.push(b.stmt(b.call('$.set_class', id, expression)));
518
+ } else {
519
+ state.init.push(b.stmt(b.call('$.set_class', id, expression)));
520
+ }
521
+ }
522
+ } else if (node.metadata.scoped && state.component.css) {
523
+ const value = state.component.css.hash;
524
+
525
+ handle_static_attr('class', value);
526
+ }
527
+
528
+ state.template.push('>');
529
+
530
+ transform_children(node.children, { visit, state, root: false });
531
+
532
+ state.template.push(`</${type}>`);
533
+ } else {
534
+ if (node.id.type !== 'Identifier') {
535
+ throw new Error('TODO');
536
+ }
537
+ const id = state.flush_node();
538
+
539
+ state.template.push('<!>');
540
+
541
+ const tracked = [];
542
+ let props = [];
543
+ let children_prop = null;
544
+
545
+ for (const attr of node.attributes) {
546
+ if (attr.type === 'Attribute') {
547
+ if (attr.name.type === 'Identifier' && is_tracked_name(attr.name.name)) {
548
+ const metadata = { tracking: false, await: false };
549
+ let property = visit(attr.value, { ...state, metadata });
550
+
551
+ tracked.push(b.literal(attr.name.name));
552
+
553
+ if (metadata.tracking) {
554
+ const thunk = b.thunk(property);
555
+ property = b.call('$.computed_property', thunk, b.id('__block'));
556
+
557
+ if (attr.name.name === '$children') {
558
+ children_prop = thunk;
559
+ }
560
+ }
561
+
562
+ props.push(b.prop('init', attr.name, property));
563
+ } else {
564
+ props.push(b.prop('init', attr.name, visit(attr.value, state)));
565
+ }
566
+ } else {
567
+ throw new Error('TODO');
568
+ }
569
+ }
570
+
571
+ if (node.children.length > 0) {
572
+ const component_scope = context.state.scopes.get(node);
573
+ const children = b.arrow(
574
+ [b.id('__anchor')],
575
+ b.block(
576
+ transform_body(node.children, {
577
+ ...context,
578
+ state: { ...context.state, scope: component_scope }
579
+ })
580
+ )
581
+ );
582
+ if (children_prop) {
583
+ children_prop.body = b.logical('??', children_prop.body, children);
584
+ } else {
585
+ props.push(b.prop('init', b.id('$children'), children));
586
+ }
587
+ }
588
+
589
+ if (tracked.length > 0) {
590
+ state.init.push(
591
+ b.stmt(
592
+ b.call(
593
+ node.id,
594
+ id,
595
+ b.call('$.tracked_object', b.object(props), b.array(tracked), b.id('__block')),
596
+ b.id('$.active_block')
597
+ )
598
+ )
599
+ );
600
+ } else {
601
+ state.init.push(b.stmt(b.call(node.id, id, b.object(props), b.id('$.active_block'))));
602
+ }
603
+ }
604
+ },
605
+
606
+ Fragment(node, context) {
607
+ if (!context.state.to_ts) {
608
+ if (!context.state.imports.has(`import * as $ from 'ripple/internal/client'`)) {
609
+ context.state.imports.add(`import * as $ from 'ripple/internal/client'`);
610
+ }
611
+ }
612
+
613
+ const metadata = { await: false };
614
+
615
+ const body_statements = transform_body(node.body, {
616
+ ...context,
617
+ state: { ...context.state, component: node, metadata }
618
+ });
619
+
620
+ return b.function(
621
+ node.id,
622
+ [b.id('__anchor'), ...node.params.map((param) => context.visit(param, context.state))],
623
+ b.block(
624
+ metadata.await
625
+ ? [b.stmt(b.call('$.async', b.thunk(b.block(body_statements), true)))]
626
+ : body_statements
627
+ )
628
+ );
629
+ },
630
+
631
+ Component(node, context) {
632
+ let prop_statements;
633
+
634
+ if (!context.state.to_ts) {
635
+ if (!context.state.imports.has(`import * as $ from 'ripple/internal/client'`)) {
636
+ context.state.imports.add(`import * as $ from 'ripple/internal/client'`);
637
+ }
638
+ }
639
+
640
+ const metadata = { await: false };
641
+
642
+ if (context.state.to_ts) {
643
+ const body_statements = [
644
+ ...transform_body(node.body, {
645
+ ...context,
646
+ state: { ...context.state, component: node, metadata }
647
+ })
648
+ ];
649
+
650
+ return b.function(node.id, node.params, b.block(body_statements));
651
+ }
652
+
653
+ if (node.params.length > 0) {
654
+ let props = node.params[0];
655
+
656
+ if (props.type === 'Identifier') {
657
+ delete props.typeAnnotation;
658
+ } else if (props.type === 'ObjectPattern') {
659
+ const paths = extract_paths(props);
660
+
661
+ for (const path of paths) {
662
+ const name = path.node.name;
663
+ const binding = context.state.scope.get(name);
664
+ const key = b.key(name);
665
+
666
+ if (binding !== null && !is_tracked_name(name)) {
667
+ if (prop_statements === undefined) {
668
+ prop_statements = [];
669
+ }
670
+ prop_statements.push(b.var(name, b.member(b.id('__props'), key)));
671
+ }
672
+ }
673
+ }
674
+ }
675
+
676
+ const body_statements = [
677
+ b.stmt(b.call('$.push_component')),
678
+ ...transform_body(node.body, {
679
+ ...context,
680
+ state: { ...context.state, component: node, metadata }
681
+ }),
682
+ b.stmt(b.call('$.pop_component'))
683
+ ];
684
+
685
+ if (node.css !== null) {
686
+ context.state.stylesheets.push(node.css);
687
+ }
688
+
689
+ return b.function(
690
+ node.id,
691
+ node.params.length > 0
692
+ ? [
693
+ b.id('__anchor'),
694
+ node.params[0].type === 'Identifier' ? node.params[0] : b.id('__props'),
695
+ b.id('__block')
696
+ ]
697
+ : [b.id('__anchor'), b.id('_'), b.id('__block')],
698
+ b.block([
699
+ ...(prop_statements ?? []),
700
+ ...(metadata.await
701
+ ? [b.stmt(b.call('$.async', b.thunk(b.block(body_statements), true)))]
702
+ : body_statements)
703
+ ])
704
+ );
705
+ },
706
+
707
+ AssignmentExpression(node, context) {
708
+ if (context.state.to_ts) {
709
+ return context.next();
710
+ }
711
+
712
+ const left = node.left;
713
+
714
+ if (
715
+ left.type === 'MemberExpression' &&
716
+ ((left.property.type === 'Identifier' && is_tracked_name(left.property.name)) ||
717
+ left.computed)
718
+ ) {
719
+ return b.call(
720
+ '$.set_property',
721
+ context.visit(left.object),
722
+ left.computed ? context.visit(left.property) : b.literal(left.property.name),
723
+ visit_assignment_expression(node, context, build_assignment) ?? context.next(),
724
+ b.id('__block')
725
+ );
726
+ }
727
+
728
+ const visited = visit_assignment_expression(node, context, build_assignment) ?? context.next();
729
+
730
+ if (
731
+ left.type === 'MemberExpression' &&
732
+ left.property.type === 'Identifier' &&
733
+ left.property.name === '$length' &&
734
+ !left.computed
735
+ ) {
736
+ return b.call('$.with_scope', b.id('__block'), b.thunk(visited));
737
+ }
738
+
739
+ return visited;
740
+ },
741
+
742
+ UpdateExpression(node, context) {
743
+ if (context.state.to_ts) {
744
+ context.next();
745
+ return;
746
+ }
747
+ const argument = node.argument;
748
+
749
+ if (
750
+ argument.type === 'MemberExpression' &&
751
+ ((argument.property.type === 'Identifier' && is_tracked_name(argument.property.name)) ||
752
+ argument.computed)
753
+ ) {
754
+ return b.call(
755
+ node.prefix ? '$.update_pre_property' : '$.update_property',
756
+ context.visit(argument.object),
757
+ argument.computed ? context.visit(argument.property) : b.literal(argument.property.name),
758
+ b.id('__block'),
759
+ node.operator === '--' ? b.literal(-1) : undefined
760
+ );
761
+ }
762
+
763
+ const left = object(argument);
764
+ const binding = context.state.scope.get(left.name);
765
+ const transformers = left && binding?.transform;
766
+
767
+ if (left === argument && transformers?.update) {
768
+ // we don't need to worry about ownership_invalid_mutation here, because
769
+ // we're not mutating but reassigning
770
+ return transformers.update(node);
771
+ }
772
+
773
+ context.next();
774
+ },
775
+
776
+ ObjectExpression(node, context) {
777
+ const properties = [];
778
+ const tracked = [];
779
+
780
+ for (const property of node.properties) {
781
+ if (
782
+ property.type === 'Property' &&
783
+ !property.computed &&
784
+ property.key.type === 'Identifier' &&
785
+ property.kind === 'init' &&
786
+ is_tracked_name(property.key.name)
787
+ ) {
788
+ tracked.push(b.literal(property.key.name));
789
+ const metadata = { tracking: false, await: false };
790
+ const tracked_property = context.visit(property, { ...context.state, metadata });
791
+
792
+ if (metadata.tracking) {
793
+ properties.push({
794
+ ...tracked_property,
795
+ value: b.call('$.computed_property', b.thunk(tracked_property.value), b.id('__block'))
796
+ });
797
+ } else {
798
+ properties.push(tracked_property);
799
+ }
800
+ } else {
801
+ properties.push(context.visit(property));
802
+ }
803
+ }
804
+
805
+ if (tracked.length > 0) {
806
+ return b.call('$.tracked_object', { ...node, properties }, b.array(tracked), b.id('__block'));
807
+ }
808
+
809
+ context.next();
810
+ },
811
+
812
+ ArrayExpression(node, context) {
813
+ const elements = [];
814
+ const tracked = [];
815
+ let i = 0;
816
+
817
+ for (const element of node.elements) {
818
+ if (element === null) {
819
+ elements.push(null);
820
+ } else if (element.type === 'Element') {
821
+ const metadata = { tracking: false, await: false };
822
+ const tracked_element = context.visit(element, { ...context.state, metadata });
823
+
824
+ if (metadata.tracking) {
825
+ tracked.push(b.literal(i));
826
+ elements.push(tracked_element);
827
+ } else {
828
+ elements.push(tracked_element);
829
+ }
830
+ } else if (element.type === 'SpreadElement') {
831
+ const metadata = { tracking: false, await: false };
832
+ const tracked_element = context.visit(element, { ...context.state, metadata });
833
+
834
+ if (metadata.tracking) {
835
+ tracked.push(b.spread(b.call('Object.keys', tracked_element.argument)));
836
+ elements.push(tracked_element);
837
+ } else {
838
+ elements.push(tracked_element);
839
+ }
840
+ } else {
841
+ const metadata = { tracking: false, await: false };
842
+ elements.push(context.visit(element, { ...context.state, metadata }));
843
+ }
844
+ i++;
845
+ }
846
+
847
+ if (tracked.length > 0) {
848
+ return b.call('$.tracked_object', { ...node, elements }, b.array(tracked), b.id('__block'));
849
+ }
850
+
851
+ context.next();
852
+ },
853
+
854
+ ForOfStatement(node, context) {
855
+ if (!is_inside_component(context)) {
856
+ context.next();
857
+ return;
858
+ }
859
+ const is_controlled = node.is_controlled;
860
+
861
+ // do only if not controller
862
+ if (!is_controlled) {
863
+ context.state.template.push('<!>');
864
+ }
865
+
866
+ const id = context.state.flush_node(is_controlled);
867
+ const pattern = node.left.declarations[0].id;
868
+ const body_scope = context.state.scopes.get(node.body);
869
+
870
+ context.state.init.push(
871
+ b.stmt(
872
+ b.call(
873
+ '$.for',
874
+ id,
875
+ b.thunk(context.visit(node.right)),
876
+ b.arrow(
877
+ [b.id('__anchor'), pattern],
878
+ b.block(
879
+ transform_body(node.body.body, {
880
+ ...context,
881
+ state: { ...context.state, scope: body_scope }
882
+ })
883
+ )
884
+ ),
885
+ b.literal(is_controlled ? IS_CONTROLLED : 0)
886
+ )
887
+ )
888
+ );
889
+ },
890
+
891
+ IfStatement(node, context) {
892
+ if (!is_inside_component(context)) {
893
+ context.next();
894
+ return;
895
+ }
896
+ context.state.template.push('<!>');
897
+
898
+ const id = context.state.flush_node();
899
+ const statements = [];
900
+
901
+ const consequent_scope = context.state.scopes.get(node.consequent);
902
+ const consequent = b.block(
903
+ transform_body(node.consequent.body, {
904
+ ...context,
905
+ state: { ...context.state, scope: consequent_scope }
906
+ })
907
+ );
908
+ const consequent_id = context.state.scope.generate('consequent');
909
+
910
+ statements.push(b.var(b.id(consequent_id), b.arrow([b.id('__anchor')], consequent)));
911
+
912
+ let alternate_id;
913
+
914
+ if (node.alternate !== null) {
915
+ const alternate_scope = context.state.scopes.get(node.alternate) || context.state.scope;
916
+ let alternate_body = node.alternate.body;
917
+ if (node.alternate.type === 'IfStatement') {
918
+ alternate_body = [node.alternate];
919
+ }
920
+ const alternate = b.block(
921
+ transform_body(alternate_body, {
922
+ ...context,
923
+ state: { ...context.state, scope: alternate_scope }
924
+ })
925
+ );
926
+ alternate_id = context.state.scope.generate('alternate');
927
+ statements.push(b.var(b.id(alternate_id), b.arrow([b.id('__anchor')], alternate)));
928
+ }
929
+
930
+ statements.push(
931
+ b.stmt(
932
+ b.call(
933
+ '$.if',
934
+ id,
935
+ b.arrow(
936
+ [b.id('__render')],
937
+ b.block([
938
+ b.if(
939
+ context.visit(node.test),
940
+ b.stmt(b.call(b.id('__render'), b.id(consequent_id))),
941
+ alternate_id
942
+ ? b.stmt(
943
+ b.call(
944
+ b.id('__render'),
945
+ b.id(alternate_id),
946
+ node.alternate ? b.literal(false) : undefined
947
+ )
948
+ )
949
+ : undefined
950
+ )
951
+ ])
952
+ )
953
+ )
954
+ )
955
+ );
956
+
957
+ context.state.init.push(b.block(statements));
958
+ },
959
+
960
+ TryStatement(node, context) {
961
+ if (!is_inside_component(context)) {
962
+ context.next();
963
+ return;
964
+ }
965
+ context.state.template.push('<!>');
966
+
967
+ const id = context.state.flush_node();
968
+ const metadata = { await: false };
969
+ let body = transform_body(node.block.body, {
970
+ ...context,
971
+ state: { ...context.state, metadata }
972
+ });
973
+
974
+ if (metadata.await) {
975
+ body = [b.stmt(b.call('$.async', b.thunk(b.block(body), true)))];
976
+ }
977
+
978
+ context.state.init.push(
979
+ b.stmt(
980
+ b.call(
981
+ '$.try',
982
+ id,
983
+ b.arrow([b.id('__anchor')], b.block(body)),
984
+ node.handler === null
985
+ ? b.literal(null)
986
+ : b.arrow(
987
+ [b.id('__anchor'), ...(node.handler.param ? [node.handler.param] : [])],
988
+ b.block(transform_body(node.handler.body.body, context))
989
+ ),
990
+ node.async === null
991
+ ? undefined
992
+ : b.arrow([b.id('__anchor')], b.block(transform_body(node.async.body, context)))
993
+ )
994
+ )
995
+ );
996
+ },
997
+
998
+ AwaitExpression(node, context) {
999
+ if (!is_inside_component(context)) {
1000
+ context.next();
1001
+ }
1002
+
1003
+ if (context.state.metadata?.await === false) {
1004
+ context.state.metadata.await = true;
1005
+ }
1006
+
1007
+ return b.call(b.await(b.call('$.resume_context', context.visit(node.argument))));
1008
+ },
1009
+
1010
+ JSXText(node) {
1011
+ const text = node.value;
1012
+ if (text.trim() === '') {
1013
+ return b.empty;
1014
+ }
1015
+ return b.literal(text);
1016
+ },
1017
+
1018
+ JSXExpressionContainer(node, context) {
1019
+ const expression = context.visit(node.expression);
1020
+ if (expression.type === b.empty) {
1021
+ return b.empty;
1022
+ }
1023
+ return expression;
1024
+ },
1025
+
1026
+ JSXIdentifier(node, context) {
1027
+ return context.visit(b.id(node.name));
1028
+ },
1029
+
1030
+ JSXMemberExpression(node, context) {
1031
+ return b.member(context.visit(node.object), context.visit(node.property));
1032
+ },
1033
+
1034
+ BinaryExpression(node, context) {
1035
+ return b.binary(node.operator, context.visit(node.left), context.visit(node.right));
1036
+ },
1037
+
1038
+ JSXElement(node, context) {
1039
+ if (
1040
+ !context.state.imports.has(`import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'`)
1041
+ ) {
1042
+ context.state.imports.add(`import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'`);
1043
+ }
1044
+
1045
+ const openingElement = node.openingElement;
1046
+ const name = openingElement.name;
1047
+ const props = b.object([]);
1048
+ const children = node.children
1049
+ .map((child) => context.visit(child, context.state))
1050
+ .filter((child) => child !== b.empty);
1051
+
1052
+ if (children.length > 0) {
1053
+ props.properties.push(b.prop('init', b.id('children'), b.array(children)));
1054
+ }
1055
+
1056
+ for (const attr of openingElement.attributes) {
1057
+ if (attr.type === 'JSXAttribute') {
1058
+ props.properties.push(
1059
+ b.prop('init', b.id(attr.name.name), context.visit(attr.value, context.state))
1060
+ );
1061
+ }
1062
+ }
1063
+
1064
+ return b.call(
1065
+ children.length > 0 ? '_jsxs' : '_jsx',
1066
+ name.type === 'JSXIdentifier' && name.name[0] === name.name[0].toLowerCase()
1067
+ ? b.literal(name.name)
1068
+ : context.visit(name),
1069
+ props
1070
+ );
1071
+ },
1072
+
1073
+ RenderFragment(node, context) {
1074
+ const identifer = node.expression.callee;
1075
+
1076
+ context.state.template.push('<!>');
1077
+
1078
+ const id = context.state.flush_node();
1079
+
1080
+ context.state.init.push(
1081
+ b.stmt(
1082
+ b.call(
1083
+ context.visit(identifer),
1084
+ id,
1085
+ ...node.expression.arguments.map((arg) => context.visit(arg, context.state))
1086
+ )
1087
+ )
1088
+ );
1089
+ },
1090
+
1091
+ BlockStatement(node, context) {
1092
+ const statements = [];
1093
+
1094
+ for (const statement of node.body) {
1095
+ if (statement.type === 'Eval') {
1096
+ const finalizer = statement.finalizer;
1097
+ if (finalizer === null) {
1098
+ throw new Error('`eval` block should have been transformed');
1099
+ }
1100
+
1101
+ for (const final_node of finalizer.body) {
1102
+ if (final_node.type === 'EmptyStatement') {
1103
+ continue;
1104
+ } else {
1105
+ statements.push(context.visit(final_node));
1106
+ }
1107
+ }
1108
+ continue;
1109
+ }
1110
+ statements.push(context.visit(statement));
1111
+ }
1112
+
1113
+ return b.block(statements);
1114
+ },
1115
+
1116
+ Program(node, context) {
1117
+ const statements = [];
1118
+
1119
+ for (const statement of node.body) {
1120
+ if (statement.type === 'Eval') {
1121
+ const finalizer = statement.finalizer;
1122
+ if (finalizer === null) {
1123
+ throw new Error('`eval` block should have been transformed');
1124
+ }
1125
+
1126
+ for (const final_node of finalizer.body) {
1127
+ if (final_node.type === 'EmptyStatement') {
1128
+ continue;
1129
+ } else {
1130
+ statements.push(context.visit(final_node));
1131
+ }
1132
+ }
1133
+ continue;
1134
+ }
1135
+ statements.push(context.visit(statement));
1136
+ }
1137
+
1138
+ return { ...node, body: statements };
1139
+ }
1140
+ };
1141
+
1142
+ /**
1143
+ * @param {Array<string | Expression>} items
1144
+ */
1145
+ function join_template(items) {
1146
+ let quasi = b.quasi('');
1147
+ const template = b.template([quasi], []);
1148
+
1149
+ /**
1150
+ * @param {Expression} expression
1151
+ */
1152
+ function push(expression) {
1153
+ if (expression.type === 'TemplateLiteral') {
1154
+ for (let i = 0; i < expression.expressions.length; i += 1) {
1155
+ const q = expression.quasis[i];
1156
+ const e = expression.expressions[i];
1157
+
1158
+ quasi.value.cooked += /** @type {string} */ (q.value.cooked);
1159
+ push(e);
1160
+ }
1161
+
1162
+ const last = /** @type {TemplateElement} */ (expression.quasis.at(-1));
1163
+ quasi.value.cooked += /** @type {string} */ (last.value.cooked);
1164
+ } else if (expression.type === 'Literal') {
1165
+ /** @type {string} */ (quasi.value.cooked) += expression.value;
1166
+ } else {
1167
+ template.expressions.push(expression);
1168
+ template.quasis.push((quasi = b.quasi('')));
1169
+ }
1170
+ }
1171
+
1172
+ for (const item of items) {
1173
+ if (typeof item === 'string') {
1174
+ quasi.value.cooked += item;
1175
+ } else {
1176
+ push(item);
1177
+ }
1178
+ }
1179
+
1180
+ for (const quasi of template.quasis) {
1181
+ quasi.value.raw = sanitize_template_string(/** @type {string} */ (quasi.value.cooked));
1182
+ }
1183
+
1184
+ quasi.tail = true;
1185
+
1186
+ return template;
1187
+ }
1188
+
1189
+ function normalize_child(node, normalized) {
1190
+ if (node.type === 'EmptyStatement') {
1191
+ return;
1192
+ } else if (node.type === 'Eval') {
1193
+ const finalizer = node.finalizer;
1194
+ if (finalizer === null) {
1195
+ throw new Error('`eval` block should have been transformed');
1196
+ }
1197
+
1198
+ for (const final_node of finalizer.body) {
1199
+ normalize_child(final_node, normalized);
1200
+ }
1201
+ } else {
1202
+ normalized.push(node);
1203
+ }
1204
+ }
1205
+
1206
+ function transform_ts_child(node, context) {
1207
+ const { state, visit } = context;
1208
+
1209
+ if (node.type === 'Text') {
1210
+ state.init.push(b.stmt(visit(node.expression, { ...state })));
1211
+ } else if (node.type === 'Element') {
1212
+ const type = node.id.name;
1213
+ const children = [];
1214
+ let has_children_props = false;
1215
+
1216
+ const attributes = node.attributes.map((attr) => {
1217
+ if (attr.type === 'Attribute') {
1218
+ const metadata = { await: false };
1219
+ const name = visit(attr.name, { ...state, metadata });
1220
+ const value = visit(attr.value, { ...state, metadata });
1221
+ const jsx_name = b.jsx_id(name.name);
1222
+ if (name.name === '$children') {
1223
+ has_children_props = true;
1224
+ }
1225
+ jsx_name.loc = name.loc;
1226
+
1227
+ return b.jsx_attribute(jsx_name, b.jsx_expression_container(value));
1228
+ } else {
1229
+ debugger;
1230
+ }
1231
+ });
1232
+
1233
+ if (!node.selfClosing && !has_children_props && node.children.length > 0) {
1234
+ const is_dom_element = type[0].toLowerCase() === type[0];
1235
+
1236
+ const component_scope = context.state.scopes.get(node);
1237
+ const thunk = b.thunk(
1238
+ b.block(
1239
+ transform_body(node.children, {
1240
+ ...context,
1241
+ state: { ...state, scope: component_scope }
1242
+ })
1243
+ )
1244
+ );
1245
+
1246
+ if (is_dom_element) {
1247
+ children.push(b.jsx_expression_container(b.call(thunk)));
1248
+ } else {
1249
+ const children_name = context.state.scope.generate('fragment');
1250
+ const children_id = b.id(children_name);
1251
+ const jsx_id = b.jsx_id('$children');
1252
+ jsx_id.loc = node.id.loc;
1253
+ state.init.push(b.const(children_id, thunk));
1254
+ attributes.push(b.jsx_attribute(jsx_id, b.jsx_expression_container(children_id)));
1255
+ }
1256
+ }
1257
+
1258
+ const opening_type = b.jsx_id(type);
1259
+ opening_type.loc = node.id.loc;
1260
+
1261
+ let closing_type = undefined;
1262
+
1263
+ if (!node.selfClosing) {
1264
+ closing_type = b.jsx_id(type);
1265
+ closing_type.loc = {
1266
+ start: {
1267
+ line: node.loc.end.line,
1268
+ column: node.loc.end.column - type.length - 1
1269
+ },
1270
+ end: {
1271
+ line: node.loc.end.line,
1272
+ column: node.loc.end.column - 1
1273
+ }
1274
+ };
1275
+ }
1276
+
1277
+ state.init.push(
1278
+ b.stmt(b.jsx_element(opening_type, attributes, children, node.selfClosing, closing_type))
1279
+ );
1280
+ } else if (node.type === 'IfStatement') {
1281
+ const consequent_scope = context.state.scopes.get(node.consequent);
1282
+ const consequent = b.block(
1283
+ transform_body(node.consequent.body, {
1284
+ ...context,
1285
+ state: { ...context.state, scope: consequent_scope }
1286
+ })
1287
+ );
1288
+
1289
+ let alternate;
1290
+
1291
+ if (node.alternate !== null) {
1292
+ const alternate_scope = context.state.scopes.get(node.alternate) || context.state.scope;
1293
+ let alternate_body = node.alternate.body;
1294
+ if (node.alternate.type === 'IfStatement') {
1295
+ alternate_body = [node.alternate];
1296
+ }
1297
+ alternate = b.block(
1298
+ transform_body(alternate_body, {
1299
+ ...context,
1300
+ state: { ...context.state, scope: alternate_scope }
1301
+ })
1302
+ );
1303
+ }
1304
+
1305
+ state.init.push(b.if(visit(node.test), consequent, alternate));
1306
+ } else if (node.type === 'ForOfStatement') {
1307
+ const body_scope = context.state.scopes.get(node.body);
1308
+ const body = b.block(
1309
+ transform_body(node.body.body, {
1310
+ ...context,
1311
+ state: { ...context.state, scope: body_scope }
1312
+ })
1313
+ );
1314
+
1315
+ state.init.push(b.for_of(visit(node.left), visit(node.right), body, node.await));
1316
+ } else if (node.type === 'RenderFragment') {
1317
+ const identifer = node.expression.callee;
1318
+
1319
+ state.init.push(
1320
+ b.stmt(
1321
+ b.call(
1322
+ context.visit(identifer),
1323
+ ...node.expression.arguments.map((arg) => context.visit(arg, context.state))
1324
+ )
1325
+ )
1326
+ );
1327
+ } else {
1328
+ throw new Error('TODO');
1329
+ }
1330
+ }
1331
+
1332
+ function transform_children(children, { visit, state, root }) {
1333
+ const normalized = [];
1334
+
1335
+ for (const node of children) {
1336
+ normalize_child(node, normalized);
1337
+ }
1338
+
1339
+ const is_fragment =
1340
+ normalized.some(
1341
+ (node) =>
1342
+ node.type === 'IfStatement' ||
1343
+ node.type === 'TryStatement' ||
1344
+ node.type === 'ForOfStatement' ||
1345
+ node.type === 'RenderFragment' ||
1346
+ (node.type === 'Element' && node.id.name[0].toLowerCase() !== node.id.name[0])
1347
+ ) ||
1348
+ normalized.filter(
1349
+ (node) => node.type !== 'VariableDeclaration' && node.type !== 'EmptyStatement'
1350
+ ).length > 1;
1351
+ let initial = null;
1352
+ let prev = null;
1353
+ let template_id = null;
1354
+
1355
+ const get_id = (node) => {
1356
+ return b.id(
1357
+ node.type == 'Element' && node.id.name[0].toLowerCase() === node.id.name[0]
1358
+ ? state.scope.generate(node.id.name)
1359
+ : node.type == 'Text'
1360
+ ? state.scope.generate('text')
1361
+ : state.scope.generate('node')
1362
+ );
1363
+ };
1364
+
1365
+ const create_initial = (node) => {
1366
+ const id = is_fragment ? b.id(state.scope.generate('fragment')) : get_id(node);
1367
+ initial = id;
1368
+ template_id = state.scope.generate('root');
1369
+ state.setup.push(b.var(id, b.call(template_id)));
1370
+ };
1371
+
1372
+ for (let i = normalized.length - 1; i >= 0; i--) {
1373
+ const child = normalized[i];
1374
+ const prev_child = normalized[i - 1];
1375
+
1376
+ if (child.type === 'Text' && prev_child?.type === 'Text') {
1377
+ if (child.expression.type === 'Literal' && prev_child.expression.type === 'Literal') {
1378
+ prev_child.expression.value += child.expression.value;
1379
+ } else {
1380
+ prev_child.expression = b.binary('+', prev_child.expression, child.expression);
1381
+ }
1382
+ normalized.splice(i, 1);
1383
+ }
1384
+ }
1385
+
1386
+ for (const node of normalized) {
1387
+ if (
1388
+ node.type === 'VariableDeclaration' ||
1389
+ node.type === 'ExpressionStatement' ||
1390
+ node.type === 'FunctionDeclaration' ||
1391
+ node.type === 'DebuggerStatement' ||
1392
+ node.type === 'ClassDeclaration'
1393
+ ) {
1394
+ const metadata = { await: false };
1395
+ state.init.push(visit(node, { ...state, metadata }));
1396
+ if (metadata.await) {
1397
+ state.init.push(b.if(b.call('$.aborted'), b.return(null)));
1398
+ if (state.metadata?.await === false) {
1399
+ state.metadata.await = true;
1400
+ }
1401
+ }
1402
+ } else if (state.to_ts) {
1403
+ transform_ts_child(node, { visit, state });
1404
+ } else {
1405
+ if (initial === null && root) {
1406
+ create_initial(node);
1407
+ }
1408
+
1409
+ const current_prev = prev;
1410
+ let cached;
1411
+ const flush_node = (is_controlled) => {
1412
+ if (cached && !is_controlled) {
1413
+ return cached;
1414
+ } else if (current_prev !== null) {
1415
+ const id = get_id(node);
1416
+ state.setup.push(b.var(id, b.call('$.sibling', current_prev())));
1417
+ cached = id;
1418
+ return id;
1419
+ } else if (initial !== null) {
1420
+ if (is_fragment) {
1421
+ const id = get_id(node);
1422
+ state.setup.push(b.var(id, b.call('$.child_frag', initial)));
1423
+ cached = id;
1424
+ return id;
1425
+ }
1426
+ return initial;
1427
+ } else if (state.flush_node !== null) {
1428
+ if (is_controlled) {
1429
+ return state.flush_node();
1430
+ }
1431
+
1432
+ const id = get_id(node);
1433
+ state.setup.push(b.var(id, b.call('$.child', state.flush_node())));
1434
+ cached = id;
1435
+ return id;
1436
+ } else {
1437
+ debugger;
1438
+ }
1439
+ };
1440
+
1441
+ prev = flush_node;
1442
+
1443
+ if (node.type === 'Element') {
1444
+ visit(node, { ...state, flush_node });
1445
+ } else if (node.type === 'Text') {
1446
+ const metadata = { tracking: false, await: false };
1447
+ const expression = visit(node.expression, { ...state, metadata });
1448
+
1449
+ if (metadata.tracking) {
1450
+ state.template.push(' ');
1451
+ const id = flush_node();
1452
+ state.update.push(b.stmt(b.call('$.set_text', id, expression)));
1453
+ } else if (normalized.length === 1) {
1454
+ if (expression.type === 'Literal') {
1455
+ state.template.push(expression.value);
1456
+ } else {
1457
+ const id = state.flush_node();
1458
+ state.init.push(
1459
+ b.stmt(b.assignment('=', b.member(id, b.id('textContent')), expression))
1460
+ );
1461
+ }
1462
+ } else {
1463
+ // Handle Text nodes in fragments
1464
+ state.template.push(' ');
1465
+ const id = flush_node();
1466
+ state.update.push(b.stmt(b.call('$.set_text', id, expression)));
1467
+ }
1468
+ } else if (node.type === 'ForOfStatement') {
1469
+ const is_controlled = normalized.length === 1;
1470
+ node.is_controlled = is_controlled;
1471
+ visit(node, { ...state, flush_node });
1472
+ } else if (node.type === 'IfStatement') {
1473
+ const is_controlled = normalized.length === 1;
1474
+ node.is_controlled = is_controlled;
1475
+ visit(node, { ...state, flush_node });
1476
+ } else if (node.type === 'TryStatement') {
1477
+ const is_controlled = normalized.length === 1;
1478
+ node.is_controlled = is_controlled;
1479
+ visit(node, { ...state, flush_node });
1480
+ } else if (node.type === 'RenderFragment') {
1481
+ const is_controlled = normalized.length === 1;
1482
+ node.is_controlled = is_controlled;
1483
+ visit(node, { ...state, flush_node });
1484
+ } else {
1485
+ debugger;
1486
+ }
1487
+ }
1488
+ }
1489
+
1490
+ if (root && initial !== null && template_id !== null) {
1491
+ const flags = is_fragment ? b.literal(TEMPLATE_FRAGMENT) : b.literal(0);
1492
+ state.final.push(b.stmt(b.call('$.append', b.id('__anchor'), initial)));
1493
+ state.hoisted.push(
1494
+ b.var(template_id, b.call('$.template', join_template(state.template), flags))
1495
+ );
1496
+ }
1497
+ }
1498
+
1499
+ function transform_body(body, { visit, state }) {
1500
+ const body_state = {
1501
+ ...state,
1502
+ template: [],
1503
+ setup: [],
1504
+ init: [],
1505
+ update: [],
1506
+ final: [],
1507
+ metadata: state.metadata
1508
+ };
1509
+
1510
+ transform_children(body, { visit, state: body_state, root: true });
1511
+
1512
+ if (body_state.update.length > 0) {
1513
+ body_state.init.push(b.stmt(b.call('$.render', b.thunk(b.block(body_state.update)))));
1514
+ }
1515
+
1516
+ return [...body_state.setup, ...body_state.init, ...body_state.final];
1517
+ }
1518
+
1519
+ export function transform(filename, source, analysis, to_ts) {
1520
+ const state = {
1521
+ imports: new Set(),
1522
+ events: new Set(),
1523
+ template: null,
1524
+ hoisted: [],
1525
+ setup: null,
1526
+ init: null,
1527
+ update: null,
1528
+ final: null,
1529
+ flush_node: null,
1530
+ scope: analysis.scope,
1531
+ scopes: analysis.scopes,
1532
+ stylesheets: [],
1533
+ to_ts
1534
+ };
1535
+
1536
+ const program = /** @type {ESTree.Program} */ (
1537
+ walk(/** @type {AST.SvelteNode} */ (analysis.ast), state, visitors)
1538
+ );
1539
+
1540
+ for (const hoisted of state.hoisted) {
1541
+ program.body.unshift(hoisted);
1542
+ }
1543
+
1544
+ for (const import_node of state.imports) {
1545
+ program.body.unshift(b.stmt(b.id(import_node)));
1546
+ }
1547
+
1548
+ if (state.events.size > 0) {
1549
+ program.body.push(
1550
+ b.stmt(b.call('$.delegate', b.array(Array.from(state.events).map((name) => b.literal(name)))))
1551
+ );
1552
+ }
1553
+
1554
+ const js = print(
1555
+ program,
1556
+ tsx({
1557
+ comments: analysis.ast.comments || []
1558
+ }),
1559
+ {
1560
+ sourceMapContent: source,
1561
+ sourceMapSource: path.basename(filename)
1562
+ }
1563
+ );
1564
+
1565
+ const css = render_stylesheets(state.stylesheets);
1566
+
1567
+ return {
1568
+ ast: program,
1569
+ js,
1570
+ css
1571
+ };
1572
+ }