* Each(array, varName, template) — evaluate template once per array item. * varName is user-defined (e.g. `issue`, `ticket`) — no $ prefix collision. * * Before evaluation, substitutes all Ref(varName) in the template with the * current item's literal value. This ensures deferred expressions (li
( name: string, args: ASTNode[], context: EvaluationContext, schemaCtx?: SchemaContext, )
| 445 | * Action/Set steps) capture concrete values instead of dangling loop refs. |
| 446 | */ |
| 447 | function evaluateLazyBuiltin( |
| 448 | name: string, |
| 449 | args: ASTNode[], |
| 450 | context: EvaluationContext, |
| 451 | schemaCtx?: SchemaContext, |
| 452 | ): unknown { |
| 453 | if (name === "Each") { |
| 454 | if (args.length < 3) return []; |
| 455 | const arr = evaluate(args[0], context); |
| 456 | if (!Array.isArray(arr)) return []; |
| 457 | |
| 458 | const varName = |
| 459 | args[1].k === "Ref" ? args[1].n : args[1].k === "Str" ? (args[1] as any).v : null; |
| 460 | if (!varName) return []; |
| 461 | const template = args[2]; |
| 462 | |
| 463 | return arr.map((item, _idx) => { |
| 464 | // Pre-substitute loop variable refs with concrete values in the template AST. |
| 465 | // This captures the item for deferred expressions (Action steps evaluated at click time). |
| 466 | const substituted = substituteRef(template, varName, item); |
| 467 | const childCtx: EvaluationContext = { |
| 468 | ...context, |
| 469 | resolveRef: (refName: string) => { |
| 470 | if (refName === varName) return item; |
| 471 | return context.resolveRef(refName); |
| 472 | }, |
| 473 | }; |
| 474 | const result = evaluate(substituted, childCtx, schemaCtx); |
| 475 | // If schema context is available and result is an ElementNode, evaluate its props |
| 476 | if (schemaCtx && isElementNode(result)) { |
| 477 | return evaluateElementInline(result as ElementNode, childCtx, schemaCtx); |
| 478 | } |
| 479 | return result; |
| 480 | }); |
| 481 | } |
| 482 | return null; |
| 483 | } |
no test coverage detected