karatelabs/karate · error · ParserException

optional chain cannot be the operand of prefix ++/--

Error message

optional chain cannot be the operand of prefix ++/--

What it means

Prefix `++`/`--` require a valid simple assignment target; an optional chain (`++a?.b`) is not one. The Karate JS parser throws this early error when a prefix-update operand subtree contains an optional chain. Guard or split the expression.

Solutions

  1. Guard with an if: `if (a) ++a.b;`
  2. Compute the value separately: `if (a) a.b = (a?.b ?? 0) + 1;`
  3. Drop the `?.` when the object is guaranteed non-nullish: `++a.b`.

Example fix

// before
++stats?.hits;
// after
if (stats) ++stats.hits;
Defensive patterns

Strategy: validation

Validate before calling

// reject optional chains under prefix ++/--
function validPrefixUpdate(src) { return !/(\+\+|--)\s*[\w$.]+\?\./.test(src); }

Try / catch

try { karate.eval(expr); } catch (e) { if (String(e).includes('optional chain cannot be the operand of prefix')) { /* guard with if */ } }

Prevention

When it happens

Trigger: Parsing `++a?.b` or `--obj?.count`, detected in earlyErrorNodeChecks for MATH_PRE_EXPR nodes where the operator is `++`/`--` and the operand contains `?.`.

Common situations: Same as postfix updates but with the operator in front — often from reordering code or from authors assuming `?.` yields a writable reference; common in refactored null-guard code.

Related errors


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

Appendix: source

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

            case MATH_POST_EXPR -> {
                // children: [operand, ++/--]
                if (node.size() > 0) {
                    Node operand = node.getFirst();
                    if (sawOptionalChain && subtreeContainsOptionalChain(operand)) {
                        throw new ParserException("optional chain cannot be the operand of postfix ++/--");
                    }
                    checkSimpleAssignmentTarget(operand, "operand of postfix update", false);
                }
            }
            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

View on GitHub (pinned to a22eb90246)