karatelabs/karate · error · ParserException

tagged template literal cannot follow an optional chain

Error message

tagged template literal cannot follow an optional chain

What it means

Per the ES spec, a tagged template cannot appear in an optional chain (`a?.b`tag`` is an early error). The Karate JS parser throws this when a tagged-template node's tag subtree contains an optional chain. Evaluate the chain first, then tag the resulting function.

Solutions

  1. Extract the tag first: `const tag = a?.b; if (tag) tag`html`;`
  2. Use a non-optional member access when the reference is guaranteed: `a.b`html```
  3. Call via a wrapper: `(a?.b ?? defaultTag)`html``.

Example fix

// before
svc?.renderer`<div/>`;
// after
const renderer = svc?.renderer;
if (renderer) renderer`<div/>`;
Defensive patterns

Strategy: validation

Validate before calling

// reject tagged templates whose tag contains ?. 
function validTaggedTemplate(src) { return !/\?\.[\w$]+\s*`/.test(src); }

Try / catch

try { karate.eval(expr); } catch (e) { if (String(e).includes('tagged template literal cannot follow an optional chain')) { /* extract tag into a variable */ } }

Prevention

When it happens

Trigger: Parsing expressions like `api?.formatter`html`` or `f()?.tag`x``, detected in earlyErrorNodeChecks for FN_TAGGED_TEMPLATE_EXPR nodes whose first child contains an optional chain.

Common situations: Calling a tagged-template helper (e.g. html/sql template functions) retrieved through optional chaining; library code guarded with `?.` that is then used as a template tag.

Related errors


AI-assisted analysis of karatelabs/karate@a22eb90246 (2026-09-12). Data as JSON: /api/errors/37c483540583f155. Report an issue: GitHub.

Appendix: source

Thrown at karate-js/src/main/java/io/karatelabs/parser/JsParser.java:545

            }
            case MATH_PRE_EXPR -> {
                // children: [op, operand] — only ++/-- need a valid update target;
                // unary +/- have no assignment side and parse the same shape.
                if (node.size() > 1) {
                    Node op = node.getFirst();
                    if (op.isToken() && (op.token.type == PLUS_PLUS || op.token.type == MINUS_MINUS)) {
                        Node operand = node.get(1);
                        if (sawOptionalChain && subtreeContainsOptionalChain(operand)) {
                            throw new ParserException("optional chain cannot be the operand of prefix ++/--");
                        }
                        checkSimpleAssignmentTarget(operand, "operand of prefix update", false);
                    }
                }
            }
            case FN_TAGGED_TEMPLATE_EXPR -> {
                if (sawOptionalChain && node.size() > 0
                        && subtreeContainsOptionalChain(node.getFirst())) {
                    throw new ParserException("tagged template literal cannot follow an optional chain");
                }
            }
            // A FunctionDeclaration is a StatementListItem, never a Statement, so it
            // may not be the sole body of an iteration statement (§13.7). Unlike the
            // `if` clause (Annex B.3.4) there is no web-compat carve-out, so this is an
            // early error in BOTH sloppy and strict code — hence it lives here, not in
            // the strict-gated walk. `for (…) { function f(){} }` stays legal: the body
            // Statement wraps a BLOCK, so its direct child is BLOCK, not FN_EXPR.
            // A LexicalDeclaration (let/const) and a ClassDeclaration are likewise
            // Declarations, not Statements, and — unlike FunctionDeclaration — have no
            // Annex B carve-out for ANY clause, so they are illegal as the body of an
            // `if`/`else` clause too (§13.6/§14.x). The `for`-init `let` is a direct
            // FOR_STMT child, not a STATEMENT, so it is correctly ignored.
            case FOR_STMT, WHILE_STMT, DO_WHILE_STMT -> {
                checkNoFunctionDeclarationBody(node, "a loop");
                checkNoLexicalOrClassDeclarationBody(node, "a loop");
            }
            case IF_STMT -> checkNoLexicalOrClassDeclarationBody(node, "an `if`/`else` clause");

View on GitHub (pinned to a22eb90246)