svelte-effect-runtime 2.0.2 → 2.1.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.
@@ -196,7 +196,7 @@ function classify_declaration_tag(node, candidates, matched, classified) {
196
196
  }
197
197
  function visit_element_attributes(node, candidates, matched, classified) {
198
198
  for (const attr of node.attributes) {
199
- if (attr.type === "Attribute" && attr.name && (attr.name.startsWith("on:") || /^on[a-z]/.test(attr.name))) {
199
+ if (attr.type === "Attribute" && attr.name && is_event_attribute_name(attr.name)) {
200
200
  visit_attribute_value(attr.value, "event", candidates, matched, classified);
201
201
  continue;
202
202
  }
@@ -206,6 +206,9 @@ function visit_element_attributes(node, candidates, matched, classified) {
206
206
  }
207
207
  }
208
208
  }
209
+ function is_event_attribute_name(name) {
210
+ return name.startsWith("on:") || /^on[a-z]/.test(name);
211
+ }
209
212
  function visit_attribute_value(value, kind, candidates, matched, classified) {
210
213
  if (value === true) return;
211
214
  if (Array.isArray(value)) {
@@ -216,19 +219,219 @@ function visit_attribute_value(value, kind, candidates, matched, classified) {
216
219
  }
217
220
  function classify_expression(expression, kind, candidates, matched, classified) {
218
221
  if (!expression) return;
219
- const candidate = find_candidate(expression, candidates);
220
- if (!candidate || matched.has(candidate.placeholder)) return;
221
- matched.add(candidate.placeholder);
222
- classified.push({
223
- candidate,
224
- kind
225
- });
222
+ const found_candidates = find_candidates(expression, candidates);
223
+ for (const candidate of found_candidates) {
224
+ if (matched.has(candidate.placeholder)) continue;
225
+ matched.add(candidate.placeholder);
226
+ classified.push({
227
+ candidate,
228
+ kind
229
+ });
230
+ }
231
+ }
232
+ function find_candidates(expression, candidates) {
233
+ const found = [];
234
+ visit_expression_value(expression, candidates, /* @__PURE__ */ new Set(), /* @__PURE__ */ new Set(), found);
235
+ return found;
236
+ }
237
+ function visit_expression_value(value, candidates, seen_nodes, seen_placeholders, found) {
238
+ if (Array.isArray(value)) {
239
+ for (const item of value) visit_expression_value(item, candidates, seen_nodes, seen_placeholders, found);
240
+ return;
241
+ }
242
+ if (!is_record(value) || seen_nodes.has(value)) return;
243
+ seen_nodes.add(value);
244
+ if (value.type === "Identifier" && typeof value.name === "string") {
245
+ const candidate = candidates.get(value.name);
246
+ if (candidate && !seen_placeholders.has(candidate.placeholder)) {
247
+ seen_placeholders.add(candidate.placeholder);
248
+ found.push(candidate);
249
+ }
250
+ }
251
+ for (const child of Object.values(value)) visit_expression_value(child, candidates, seen_nodes, seen_placeholders, found);
226
252
  }
227
- function find_candidate(expression, candidates) {
228
- if (expression.type === "Identifier" && expression.name) return candidates.get(expression.name);
229
- if (expression.type === "CallExpression" && expression.callee?.type === "Identifier" && expression.callee.name) return candidates.get(expression.callee.name);
253
+ function is_record(value) {
254
+ return typeof value === "object" && value !== null;
230
255
  }
231
256
  //#endregion
257
+ //#region src/error.ts
258
+ /**
259
+ * Formats a runtime-owned error message with a stable screaming-case code.
260
+ *
261
+ * @example
262
+ * ```ts
263
+ * throw new Error(make_error_message("DISPATCHER_DISPOSED", "Dispatcher has been disposed"));
264
+ * ```
265
+ *
266
+ * @since 2.0.0
267
+ * @param code - Stable screaming-case identifier for the error category.
268
+ * @param message - Human-readable error message without the leading code.
269
+ * @returns The complete error message prefixed with the stable code.
270
+ */
271
+ function make_error_message(code, message) {
272
+ return `[${code}]: ${message}`;
273
+ }
274
+ /**
275
+ * Base error class for all preprocessor errors emitted during script and
276
+ * markup transformation. Carries the source filename so error messages can
277
+ * reference the affected file.
278
+ *
279
+ * @since 2.0.0
280
+ */
281
+ var PreprocessError = class extends Error {
282
+ /**
283
+ * The source filename that triggered this error.
284
+ *
285
+ * @since 2.0.0
286
+ */
287
+ filename;
288
+ constructor(message, filename) {
289
+ super(message);
290
+ this.name = "PreprocessError";
291
+ this.filename = filename;
292
+ }
293
+ };
294
+ /**
295
+ * Thrown when a statement mixes JavaScript `await` with Effect `yield*` work
296
+ * that must be lowered into an `Effect.gen` program.
297
+ *
298
+ * @since 2.0.0
299
+ */
300
+ var AwaitInEffectWorkError = class extends PreprocessError {
301
+ /**
302
+ * The full text of the problematic statement containing mixed async work.
303
+ *
304
+ * @since 2.0.0
305
+ */
306
+ statement_text;
307
+ constructor(filename, statement_text) {
308
+ super([
309
+ make_error_message("AWAIT_IN_EFFECT_WORK", `${filename}: await cannot be mixed with yield* in Effect work.`),
310
+ `Top-level await is supported as ordinary Svelte async rendering, but statements lowered into Effect.gen must use yield* for async Effect work.`,
311
+ "",
312
+ `Problematic statement:`,
313
+ statement_text
314
+ ].join("\n"), filename);
315
+ this.name = "AwaitInEffectWorkError";
316
+ this.statement_text = statement_text;
317
+ }
318
+ };
319
+ /**
320
+ * Thrown when async Effect work appears inside a Svelte rune position that
321
+ * must stay synchronous.
322
+ *
323
+ * @since 2.0.0
324
+ */
325
+ var AsyncEffectInSyncRuneError = class extends PreprocessError {
326
+ /**
327
+ * The name of the rune that contained async Effect work.
328
+ *
329
+ * @since 2.0.0
330
+ */
331
+ rune_name;
332
+ /**
333
+ * The full text of the expression that triggered the error.
334
+ *
335
+ * @since 2.0.0
336
+ */
337
+ expression_text;
338
+ constructor(rune_name, expression_text, filename) {
339
+ super([
340
+ make_error_message("ASYNC_EFFECT_IN_SYNC_RUNE", `${filename}: yield* cannot be used inside ${rune_name}().`),
341
+ `${rune_name}() must stay synchronous. Do not put async Effect work inside this rune.`,
342
+ "",
343
+ `Problematic expression:`,
344
+ expression_text
345
+ ].join("\n"), filename);
346
+ this.name = "AsyncEffectInSyncRuneError";
347
+ this.rune_name = rune_name;
348
+ this.expression_text = expression_text;
349
+ }
350
+ };
351
+ /**
352
+ * Thrown when async Effect work appears inside a non-generator callback nested
353
+ * in a markup event handler.
354
+ *
355
+ * @example
356
+ * ```ts
357
+ * throw new AsyncEffectInEventCallbackError(
358
+ * "Component.svelte",
359
+ * "Effect.try(() => yield* save())",
360
+ * );
361
+ * ```
362
+ *
363
+ * @since 2.0.0
364
+ */
365
+ var AsyncEffectInEventCallbackError = class extends PreprocessError {
366
+ /**
367
+ * The full text of the problematic event handler body.
368
+ *
369
+ * @since 2.0.0
370
+ */
371
+ expression_text;
372
+ constructor(filename, expression_text) {
373
+ super([
374
+ make_error_message("ASYNC_EFFECT_IN_EVENT_CALLBACK", `${filename}: yield* cannot be used inside a nested non-generator callback in a markup event handler.`),
375
+ `Move the yield* to the event handler body. Effect.try and Effect.sync callbacks are plain synchronous JavaScript; do not call Effect-returning functions inside them.`,
376
+ "",
377
+ `Run the remote Effect directly:`,
378
+ ` onclick={yield* UpvotePost(id)}`,
379
+ "",
380
+ `Recover from remote failures by composing the Effect value:`,
381
+ ` onclick={yield* UpvotePost(id).pipe(Effect.catch(() => Effect.void))}`,
382
+ "",
383
+ `Problematic expression:`,
384
+ expression_text
385
+ ].join("\n"), filename);
386
+ this.name = "AsyncEffectInEventCallbackError";
387
+ this.expression_text = expression_text;
388
+ }
389
+ };
390
+ /**
391
+ * Thrown when an event handler callback contains the old raw `yield*`
392
+ * shorthand. Effectful event handlers must put `yield*` directly in the event
393
+ * attribute so the callback boundary is generated by the markup runtime.
394
+ *
395
+ * @example
396
+ * ```ts
397
+ * throw new YieldStarInEventCallbackError(
398
+ * "Component.svelte",
399
+ * "() => yield* save()",
400
+ * );
401
+ * ```
402
+ *
403
+ * @since 2.0.0
404
+ * @param filename - Svelte component filename used to identify where the
405
+ * invalid event handler callback was found.
406
+ * @param expression_text - Original event handler callback text that contained
407
+ * `yield*` and should be rewritten as a direct event Effect expression.
408
+ */
409
+ var YieldStarInEventCallbackError = class extends PreprocessError {
410
+ /**
411
+ * The full text of the problematic event handler callback.
412
+ *
413
+ * @since 2.0.0
414
+ */
415
+ expression_text;
416
+ constructor(filename, expression_text) {
417
+ super([
418
+ make_error_message("ASYNC_EFFECT_IN_EVENT_HANDLER_CALLBACK", `${filename}: yield* in markup event handlers must be written directly as the event attribute value.`),
419
+ `SER generates the event callback for effectful event handlers; do not put yield* inside a JavaScript callback.`,
420
+ "",
421
+ `Use this form:`,
422
+ ` onclick={yield* UpvotePost(id)}`,
423
+ "",
424
+ `Instead of this form:`,
425
+ ` onclick={() => yield* UpvotePost(id)}`,
426
+ "",
427
+ `Problematic expression:`,
428
+ expression_text
429
+ ].join("\n"), filename);
430
+ this.name = "YieldStarInEventCallbackError";
431
+ this.expression_text = expression_text;
432
+ }
433
+ };
434
+ //#endregion
232
435
  //#region src/markup/transform/expressions.ts
233
436
  /**
234
437
  * Strips an event handler arrow function down to its executable body.
@@ -273,6 +476,20 @@ function strip_arrow_function(expr) {
273
476
  };
274
477
  }
275
478
  /**
479
+ * Returns whether an expression is a callback function.
480
+ *
481
+ * @since 2.0.0
482
+ * @param expr - Expression text from a markup attribute or expression tag.
483
+ * @returns Whether the expression parses as an arrow or function expression.
484
+ */
485
+ function is_callback_function_expression(expr) {
486
+ const wrapped = `const __ser_callback = ${expr};`;
487
+ const stmt = ts.createSourceFile("callback.ts", wrapped, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS).statements[0];
488
+ if (!ts.isVariableStatement(stmt)) return false;
489
+ const initializer = stmt.declarationList.declarations[0]?.initializer;
490
+ return initializer !== void 0 && (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer));
491
+ }
492
+ /**
276
493
  * Classifies `yield*` placement inside an event handler body.
277
494
  *
278
495
  * @example
@@ -336,7 +553,7 @@ function visit_ids(node, seen, ids) {
336
553
  node.forEachChild((child) => visit_ids(child, seen, ids));
337
554
  }
338
555
  function visit_event_body(node, context, result) {
339
- if (is_yield_star_expression(node)) {
556
+ if (is_yield_star_expression$1(node)) {
340
557
  if (context === "top_level") result.has_top_level_yield_star = true;
341
558
  else if (context === "nested_invalid") result.has_nested_invalid_yield_star = true;
342
559
  node.forEachChild((child) => visit_event_body(child, context, result));
@@ -355,7 +572,7 @@ function is_nested_function_boundary(node) {
355
572
  function is_generator_function_boundary(node) {
356
573
  return (ts.isFunctionExpression(node) || ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) && node.asteriskToken !== void 0;
357
574
  }
358
- function is_yield_star_expression(node) {
575
+ function is_yield_star_expression$1(node) {
359
576
  if (ts.isYieldExpression(node)) return node.asteriskToken !== void 0;
360
577
  return ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.AsteriskToken && ts.isIdentifier(node.left) && node.left.text === "yield";
361
578
  }
@@ -395,13 +612,12 @@ function emit_replacement(candidate, kind) {
395
612
  replacement_text = emit_each_expression(id_text, effect);
396
613
  helpers = [effect.helper];
397
614
  } else if (kind === "event") {
398
- const event = strip_arrow_function(candidate.expr_text);
399
- replacement_text = `${event.params} => { void ${HELPERS.run}(function* () { ${event.body}; }); }`;
615
+ replacement_text = make_event_handler(candidate).text;
400
616
  helpers = [];
401
617
  relocation = make_relocation(candidate, replacement_text, {
402
- originalStart: event.body_start,
403
- originalEnd: event.body_end,
404
- generatedText: event.body
618
+ originalStart: 0,
619
+ originalEnd: candidate.expr_text.length,
620
+ generatedText: candidate.expr_text
405
621
  });
406
622
  } else {
407
623
  const effect = make_effect_helper(candidate, helper_name);
@@ -416,6 +632,11 @@ function emit_replacement(candidate, kind) {
416
632
  relocation
417
633
  };
418
634
  }
635
+ function make_event_handler(candidate) {
636
+ if (is_callback_function_expression(candidate.expr_text)) throw new YieldStarInEventCallbackError(candidate.filename, candidate.expr_text);
637
+ if (analyze_event_body_yield_star(candidate.expr_text).has_nested_invalid_yield_star) throw new AsyncEffectInEventCallbackError(candidate.filename, candidate.expr_text);
638
+ return { text: `(event) => { void ${HELPERS.run}(function* () { ${candidate.expr_text}; }); }` };
639
+ }
419
640
  function emit_promise_expression(id_text, effect) {
420
641
  return `${HELPERS.promise}(${id_text}, ${effect.deps_text}, () => ${effect.call})`;
421
642
  }
@@ -464,139 +685,90 @@ function make_relocation(candidate, replacement_text, inner) {
464
685
  };
465
686
  }
466
687
  //#endregion
467
- //#region src/error.ts
688
+ //#region src/preprocess/ast.ts
468
689
  /**
469
- * Formats a runtime-owned error message with a stable screaming-case code.
690
+ * Checks whether a node is a `yield*` binary expression.
470
691
  *
471
- * @example
472
- * ```ts
473
- * throw new Error(make_error_message("DISPATCHER_DISPOSED", "Dispatcher has been disposed"));
474
- * ```
692
+ * @since 2.0.0
693
+ * @param node - TypeScript AST node to check.
694
+ * @returns Whether the node represents `yield * operand`.
695
+ */
696
+ function is_yield_star_expression(node) {
697
+ return ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.AsteriskToken && ts.isIdentifier(node.left) && node.left.text === "yield";
698
+ }
699
+ /**
700
+ * Checks whether a node owns its own yield semantics.
475
701
  *
476
702
  * @since 2.0.0
477
- * @param code - Stable screaming-case identifier for the error category.
478
- * @param message - Human-readable error message without the leading code.
479
- * @returns The complete error message prefixed with the stable code.
703
+ * @param node - TypeScript AST node to check.
704
+ * @returns Whether traversal should stop at this function boundary.
480
705
  */
481
- function make_error_message(code, message) {
482
- return `[${code}]: ${message}`;
706
+ function is_function_boundary_node(node) {
707
+ return ts.isArrowFunction(node) || ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isMethodDeclaration(node) || ts.isGetAccessorDeclaration(node) || ts.isSetAccessorDeclaration(node);
483
708
  }
484
709
  /**
485
- * Base error class for all preprocessor errors emitted during script and
486
- * markup transformation. Carries the source filename so error messages can
487
- * reference the affected file.
710
+ * Returns `true` if the node tree contains a top-level `await`.
488
711
  *
489
712
  * @since 2.0.0
713
+ * @param node - Root node to search.
714
+ * @returns Whether a top-level await expression was found.
490
715
  */
491
- var PreprocessError = class extends Error {
492
- /**
493
- * The source filename that triggered this error.
494
- *
495
- * @since 2.0.0
496
- */
497
- filename;
498
- constructor(message, filename) {
499
- super(message);
500
- this.name = "PreprocessError";
501
- this.filename = filename;
502
- }
503
- };
716
+ function contains_top_level_await(node) {
717
+ if (ts.isAwaitExpression(node)) return true;
718
+ return node.getChildren().some((child) => !is_function_boundary_node(child) && contains_top_level_await(child));
719
+ }
504
720
  /**
505
- * Thrown when a statement mixes JavaScript `await` with Effect `yield*` work
506
- * that must be lowered into an `Effect.gen` program.
721
+ * Collects top-level `yield*` nodes under an expression.
507
722
  *
508
723
  * @since 2.0.0
724
+ * @param node - Root node to search.
725
+ * @param on_found - Callback invoked for each matching yield node.
726
+ * @returns Nothing.
509
727
  */
510
- var AwaitInEffectWorkError = class extends PreprocessError {
511
- /**
512
- * The full text of the problematic statement containing mixed async work.
513
- *
514
- * @since 2.0.0
515
- */
516
- statement_text;
517
- constructor(filename, statement_text) {
518
- super([
519
- make_error_message("AWAIT_IN_EFFECT_WORK", `${filename}: await cannot be mixed with yield* in Effect work.`),
520
- `Top-level await is supported as ordinary Svelte async rendering, but statements lowered into Effect.gen must use yield* for async Effect work.`,
521
- "",
522
- `Problematic statement:`,
523
- statement_text
524
- ].join("\n"), filename);
525
- this.name = "AwaitInEffectWorkError";
526
- this.statement_text = statement_text;
728
+ function collect_yield_star_nodes(node, on_found) {
729
+ if (is_function_boundary_node(node)) return;
730
+ if (is_yield_star_expression(node)) {
731
+ on_found(node);
732
+ return;
527
733
  }
528
- };
734
+ node.forEachChild((child) => {
735
+ collect_yield_star_nodes(child, on_found);
736
+ });
737
+ }
529
738
  /**
530
- * Thrown when async Effect work appears inside a Svelte rune position that
531
- * must stay synchronous.
739
+ * Finds the first top-level `yield*` expression below a node.
532
740
  *
533
741
  * @since 2.0.0
742
+ * @param node - Root node to search.
743
+ * @param on_found - Callback invoked with the first matching node.
744
+ * @returns Nothing.
534
745
  */
535
- var AsyncEffectInSyncRuneError = class extends PreprocessError {
536
- /**
537
- * The name of the rune that contained async Effect work.
538
- *
539
- * @since 2.0.0
540
- */
541
- rune_name;
542
- /**
543
- * The full text of the expression that triggered the error.
544
- *
545
- * @since 2.0.0
546
- */
547
- expression_text;
548
- constructor(rune_name, expression_text, filename) {
549
- super([
550
- make_error_message("ASYNC_EFFECT_IN_SYNC_RUNE", `${filename}: yield* cannot be used inside ${rune_name}().`),
551
- `${rune_name}() must stay synchronous. Do not put async Effect work inside this rune.`,
552
- "",
553
- `Problematic expression:`,
554
- expression_text
555
- ].join("\n"), filename);
556
- this.name = "AsyncEffectInSyncRuneError";
557
- this.rune_name = rune_name;
558
- this.expression_text = expression_text;
746
+ function find_yield_star_node(node, on_found) {
747
+ if (is_function_boundary_node(node)) return;
748
+ if (is_yield_star_expression(node)) {
749
+ on_found(node);
750
+ return;
559
751
  }
560
- };
752
+ node.forEachChild((child) => {
753
+ find_yield_star_node(child, on_found);
754
+ });
755
+ }
561
756
  /**
562
- * Thrown when async Effect work appears inside a non-generator callback nested
563
- * in a markup event handler.
564
- *
565
- * @example
566
- * ```ts
567
- * throw new AsyncEffectInEventCallbackError(
568
- * "Component.svelte",
569
- * "Effect.try(() => yield* save())",
570
- * );
571
- * ```
757
+ * Extracts identifier names from a TypeScript binding name.
572
758
  *
573
759
  * @since 2.0.0
760
+ * @param name - Binding name node to flatten.
761
+ * @returns Identifier names from identifiers and destructuring patterns.
574
762
  */
575
- var AsyncEffectInEventCallbackError = class extends PreprocessError {
576
- /**
577
- * The full text of the problematic event handler body.
578
- *
579
- * @since 2.0.0
580
- */
581
- expression_text;
582
- constructor(filename, expression_text) {
583
- super([
584
- make_error_message("ASYNC_EFFECT_IN_EVENT_CALLBACK", `${filename}: yield* cannot be used inside a nested non-generator callback in a markup event handler.`),
585
- `Move the yield* to the event handler body. Effect.try and Effect.sync callbacks are plain synchronous JavaScript; do not call Effect-returning functions inside them.`,
586
- "",
587
- `Run the remote Effect directly:`,
588
- ` onclick={() => yield* UpvotePost(id)}`,
589
- "",
590
- `Recover from remote failures by composing the Effect value:`,
591
- ` onclick={() => yield* UpvotePost(id).pipe(Effect.catch(() => Effect.void))}`,
592
- "",
593
- `Problematic expression:`,
594
- expression_text
595
- ].join("\n"), filename);
596
- this.name = "AsyncEffectInEventCallbackError";
597
- this.expression_text = expression_text;
763
+ function extract_binding_names(name) {
764
+ if (ts.isIdentifier(name)) return [name.text];
765
+ const result = [];
766
+ for (const element of name.elements) {
767
+ if (ts.isOmittedExpression(element)) continue;
768
+ result.push(...extract_binding_names(element.name));
598
769
  }
599
- };
770
+ return result;
771
+ }
600
772
  //#endregion
601
773
  //#region src/markup/transform/scan.ts
602
774
  function sanitize_markup(content, filename) {
@@ -622,20 +794,20 @@ function sanitize_markup(content, filename) {
622
794
  const trimmed = inner.trimStart();
623
795
  const leading_ws = inner.length - trimmed.length;
624
796
  const tag_info = get_tag_info(trimmed);
625
- const declaration_initializers = collect_declaration_initializers(content, open, leading_ws, trimmed);
626
- if (declaration_initializers.length > 0) {
627
- for (const initializer of declaration_initializers) {
797
+ const declaration_yields = collect_declaration_yield_expressions(content, open, leading_ws, trimmed);
798
+ if (declaration_yields.length > 0) {
799
+ for (const declaration_yield of declaration_yields) {
628
800
  const placeholder = `__ser_markup_placeholder_${helper_index}`;
629
801
  helper_index += 1;
630
802
  candidates.push({
631
803
  placeholder,
632
- start: initializer.start,
633
- end: initializer.end,
634
- expr_text: initializer.expr_text,
804
+ start: declaration_yield.start,
805
+ end: declaration_yield.end,
806
+ expr_text: declaration_yield.expr_text,
635
807
  filename,
636
808
  key: "plain"
637
809
  });
638
- magic.overwrite(initializer.start, initializer.end, placeholder);
810
+ magic.overwrite(declaration_yield.start, declaration_yield.end, placeholder);
639
811
  }
640
812
  cursor = close + 1;
641
813
  continue;
@@ -643,7 +815,7 @@ function sanitize_markup(content, filename) {
643
815
  let expr_body = trimmed.slice(tag_info.prefix_length);
644
816
  /** For @const, only use the RHS after `=` as the expression body. */
645
817
  const equal_idx = tag_info.kind === "plain" && trimmed.startsWith("@const ") ? expr_body.indexOf("=") : -1;
646
- if (!((is_event_expression(inner) ? analyze_event_yield(inner, filename) : void 0)?.has_top_level_yield_star ?? contains_yield_star_in_text(expr_body))) {
818
+ if (!((is_event_callback_expression(inner) ? analyze_event_yield(inner) : void 0)?.has_top_level_yield_star ?? contains_yield_star_in_text(expr_body))) {
647
819
  cursor = close + 1;
648
820
  continue;
649
821
  }
@@ -789,35 +961,40 @@ function get_tag_info(trimmed) {
789
961
  prefix_length: 0
790
962
  };
791
963
  }
792
- function collect_declaration_initializers(content, open, leading_ws, trimmed) {
964
+ function collect_declaration_yield_expressions(content, open, leading_ws, trimmed) {
793
965
  if (!is_declaration_tag_text(trimmed)) return [];
794
966
  const source_file = ts.createSourceFile("declaration-tag.ts", `${trimmed};`, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
795
967
  const stmt = source_file.statements[0];
796
968
  if (!stmt || !ts.isVariableStatement(stmt)) return [];
797
969
  const tag_start = open + 1 + leading_ws;
798
- return stmt.declarationList.declarations.filter((decl) => decl.initializer && contains_top_level_yield_star(decl.initializer)).map((decl) => {
970
+ return stmt.declarationList.declarations.flatMap((decl) => {
799
971
  const initializer = decl.initializer;
800
- const start = tag_start + initializer.getStart(source_file);
801
- const end = tag_start + initializer.end;
802
- return {
803
- start,
804
- end,
805
- expr_text: content.slice(start, end).trim()
806
- };
972
+ if (!initializer || !contains_top_level_yield_star(initializer)) return [];
973
+ const expressions = [];
974
+ collect_yield_star_nodes(initializer, (yield_node) => {
975
+ const start = tag_start + yield_node.getStart(source_file);
976
+ const end = tag_start + yield_node.end;
977
+ const expr_text = content.slice(start, end).trim();
978
+ expressions.push({
979
+ start,
980
+ end,
981
+ expr_text
982
+ });
983
+ });
984
+ return expressions;
807
985
  });
808
986
  }
809
987
  function is_declaration_tag_text(trimmed) {
810
988
  return /^(?:const|let)\s/.test(trimmed);
811
989
  }
812
- function is_event_expression(inner) {
990
+ function is_event_callback_expression(inner) {
813
991
  const trimmed = inner.trimStart();
814
- return /^(?:\([^)]*\)|[A-Za-z_$][\w$]*)\s*=>/.test(trimmed);
992
+ return /^(?:async\s+)?(?:\([^)]*\)|[A-Za-z_$][\w$]*)\s*=>/.test(trimmed) || /^(?:async\s+)?function\b/.test(trimmed);
815
993
  }
816
- function analyze_event_yield(inner, filename) {
994
+ function analyze_event_yield(inner) {
817
995
  const event = strip_arrow_function(inner);
818
996
  const analysis = analyze_event_body_yield_star(event.body);
819
- if (analysis.has_nested_invalid_yield_star) throw new AsyncEffectInEventCallbackError(filename, event.body);
820
- return { has_top_level_yield_star: analysis.has_top_level_yield_star };
997
+ return { has_top_level_yield_star: analysis.has_top_level_yield_star || analysis.has_nested_invalid_yield_star || /\byield\s*\*/.test(event.body) };
821
998
  }
822
999
  function contains_yield_star_in_text(text) {
823
1000
  if (!/\byield\s*\*/.test(text)) return false;
@@ -878,6 +1055,6 @@ function transform_markup_effect(content, filename) {
878
1055
  };
879
1056
  }
880
1057
  //#endregion
881
- export { collect_free_identifiers as i, AsyncEffectInSyncRuneError as n, AwaitInEffectWorkError as r, transform_markup_effect as t };
1058
+ export { find_yield_star_node as a, AsyncEffectInSyncRuneError as c, extract_binding_names as i, AwaitInEffectWorkError as l, collect_yield_star_nodes as n, is_yield_star_expression as o, contains_top_level_await as r, collect_free_identifiers as s, transform_markup_effect as t };
882
1059
 
883
- //# sourceMappingURL=transform-BguSlVDO.js.map
1060
+ //# sourceMappingURL=transform-B4g76Ur4.js.map