karatelabs/karate · error · ParserException

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

Error message

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

What it means

Postfix `++`/`--` require a valid simple assignment target; an optional chain (`a?.b++`) is never one. The Karate JS parser throws this early error when the postfix-update operand contains an optional chain. Split the read and the write instead.

Solutions

  1. Guard with an if: `if (a) a.b++;`
  2. Split the operation: `const v = a?.b; if (a) a.b = v + 1;`
  3. Use a definite reference when nullability is already handled: `a.b++`.

Example fix

// before
counter?.count++;
// after
if (counter) counter.count++;
Defensive patterns

Strategy: validation

Validate before calling

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

Try / catch

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

Prevention

When it happens

Trigger: Parsing `a?.b++` or `f()?.counter--`, detected in earlyErrorNodeChecks for MATH_POST_EXPR nodes whose operand subtree contains an optional chain.

Common situations: Incrementing a counter on a possibly-null object found via optional chaining; compact loop code written assuming `?.` composes with updates; mechanical rewrites from `a && a.b++`.

Related errors


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

Appendix: source

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

                Node base = stripExprWrappers(node.size() > 0 ? node.getFirst() : null);
                if (base != null) {
                    boolean unaryBase = switch (base.type) {
                        case DELETE_EXPR, TYPEOF_EXPR, UNARY_EXPR, AWAIT_EXPR -> true;
                        case MATH_PRE_EXPR -> base.size() > 0 && base.getFirst().isToken()
                                && (base.getFirst().token.type == PLUS || base.getFirst().token.type == MINUS);
                        default -> false;
                    };
                    if (unaryBase) {
                        throw new ParserException("unary expression cannot be the base of '**'; wrap it in parentheses");
                    }
                }
            }
            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);
                    }
                }
            }

View on GitHub (pinned to a22eb90246)