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.
- package/.dist/chunks/{preprocess-BeZqJxso.js → preprocess-33aQ41uE.js} +2 -87
- package/.dist/chunks/preprocess-33aQ41uE.js.map +1 -0
- package/.dist/chunks/{transform-BguSlVDO.js → transform-B4g76Ur4.js} +327 -150
- package/.dist/chunks/transform-B4g76Ur4.js.map +1 -0
- package/.dist/error.d.ts +28 -0
- package/.dist/markup/transform/expressions.d.ts +8 -0
- package/.dist/markup/transform.js +1 -1
- package/.dist/preprocess.js +2 -2
- package/.dist/runtime/preprocess.js +2 -2
- package/package.json +1 -1
- package/.dist/chunks/preprocess-BeZqJxso.js.map +0 -1
- package/.dist/chunks/transform-BguSlVDO.js.map +0 -1
|
@@ -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
|
|
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
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
|
228
|
-
|
|
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
|
-
|
|
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:
|
|
403
|
-
originalEnd:
|
|
404
|
-
generatedText:
|
|
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/
|
|
688
|
+
//#region src/preprocess/ast.ts
|
|
468
689
|
/**
|
|
469
|
-
*
|
|
690
|
+
* Checks whether a node is a `yield*` binary expression.
|
|
470
691
|
*
|
|
471
|
-
* @
|
|
472
|
-
*
|
|
473
|
-
*
|
|
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
|
|
478
|
-
* @
|
|
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
|
|
482
|
-
return
|
|
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
|
-
*
|
|
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
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
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
|
|
626
|
-
if (
|
|
627
|
-
for (const
|
|
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:
|
|
633
|
-
end:
|
|
634
|
-
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(
|
|
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 (!((
|
|
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
|
|
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.
|
|
970
|
+
return stmt.declarationList.declarations.flatMap((decl) => {
|
|
799
971
|
const initializer = decl.initializer;
|
|
800
|
-
|
|
801
|
-
const
|
|
802
|
-
|
|
803
|
-
start
|
|
804
|
-
end
|
|
805
|
-
expr_text
|
|
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
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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-
|
|
1060
|
+
//# sourceMappingURL=transform-B4g76Ur4.js.map
|