Skip to content

[Bug]: template engine chained || returns literal string instead of evaluating #303

@Astro-Han

Description

@Astro-Han

Description

The template engine's || handler in evalExpr (src/pipeline/template.ts:68-74) only evaluates the left side of the first ||. When the left side is falsy, the entire right side is returned as a literal string instead of being recursively evaluated.

Single || works:

phonetic: "${{ item.phonetic || 'N/A' }}"
# item.phonetic = '' → returns 'N/A' ✓

Chained || breaks:

phonetic: "${{ item.phonetic || item.phonetics[0].text || '' }}"
# item.phonetic = '' → returns literal "item.phonetics[0].text || ''" ✗

Root cause

// template.ts:68-74
const orMatch = expr.match(/^(.+?)\s*\|\|\s*(.+)$/);
if (orMatch) {
    const left = evalExpr(orMatch[1].trim(), ctx);  // ← recursively evaluated
    if (left) return left;
    const right = orMatch[2].trim();
    return right.replace(/^['"]|['"]$/g, '');  // ← returned as literal string, NOT evaluated
}

The right side should be recursively evaluated with evalExpr() when the left side is falsy, or the entire expression should fall through to evalJsExpr() which handles chained || correctly via new Function().

Impact

Any YAML adapter using chained || for fallback chains will silently output template text instead of actual values. The workaround is wrapping in an IIFE:

phonetic: "${{ (() => item.phonetic || item.phonetics?.[0]?.text || '')() }}"

Suggested fix

Change line 73 from:

return right.replace(/^['"]|['"]$/g, '');

to:

return evalExpr(right, ctx);

This makes the right side recursively evaluate, correctly handling a || b || c chains.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions