karatelabs/karate · error · JsErrorException (typeError)

cannot write to optional call expression

Error message

cannot write to optional call expression

What it means

When resolving the write target of an assignment, `PropertyAccess.resolveWriteSite` encountered an optional-call expression (`f?.()` on the LHS). Optional call expressions produce no reference to assign to, so the engine raises a TypeError instead of writing to nothing.

Solutions

  1. Remove the optional-call from the assignment LHS and assign to a plain reference or property.
  2. Check for a typo: `=` used where `==`/`===` was intended.
  3. If conditional invocation is intended, perform the call first and assign separately.

Example fix

// before
obj?.callback() = value; // TypeError: cannot write to optional call expression
// after
if (obj) obj.callback = value; // or: obj?.callback(value)
Defensive patterns

Strategy: validation

Validate before calling

// reject optional-call on the left of '=' before evaluating
const badLhs = /\?\.\s*\([^)]*\)\s*=|\?\.[A-Za-z_$]+\s*\([^)]*\)\s*=/.test(src);
if (badLhs) throw new Error('optional call cannot be an assignment target');

Type guard

function isPlainAssignTarget(expr) { return /^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*|\[[^\]]+\])*$/.test(expr.trim()); }

Try / catch

try { engine.set(varName, value); } catch (e) { if (String(e.message).includes('cannot write to optional call expression')) { /* rewrite the expression */ } else { throw e; } }

Prevention

When it happens

Trigger: An assignment whose left-hand side contains `?.()` or an optional-chain write target, e.g. `obj?.method() = value` or `a?.[b]?.() = x` parsed as a write site.

Common situations: Stray `=` in an expression where `==` was meant; generator/template code emitting optional-call LHS; hand-edited code leaving `?.()` in an assignment target.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at karate-js/src/main/java/io/karatelabs/js/PropertyAccess.java:180

                Node key = node.get(2);
                Object target = Interpreter.eval(node.getFirst(), context);
                if (target == SHORT_CIRCUITED) return SHORT_CIRCUIT_SITE;
                if (context.isStopped()) return null;
                if (key.token.type == TokenType.PRIVATE_NAME) {
                    return new AccessSite(target, null, false,
                            PrivateAccess.resolve(key.getText(), context));
                }
                return new AccessSite(target, key.getText(), false, null, superReceiver);
            }
            if (second.type == NodeType.REF_BRACKET_EXPR) {
                Object target = Interpreter.eval(node.getFirst(), context);
                if (target == SHORT_CIRCUITED) return SHORT_CIRCUIT_SITE;
                if (context.isStopped()) return null;
                Object index = Interpreter.eval(second.get(2), context);
                if (context.isStopped()) return null;
                return new AccessSite(target, index, true, null, superReceiver);
            }
            throw JsErrorException.typeError("cannot write to optional call expression");
        }
        // REF_BRACKET_EXPR
        Object target = Interpreter.eval(node.getFirst(), context);
        if (target == SHORT_CIRCUITED) return SHORT_CIRCUIT_SITE;
        if (context.isStopped()) return null;
        Object index = Interpreter.eval(node.get(2), context);
        if (context.isStopped()) return null;
        return new AccessSite(target, index, true, null, superReceiver);
    }

    /** Site read honoring a super receiver; the non-super shape delegates to
     *  the ordinary index/name workers unchanged. */
    private static Object siteRead(AccessSite site, CoreContext context) {
        if (site.receiver == null) {
            return site.isIndex
                    ? getByIndex(site.target, site.key, false, context, false)
                    : getByName(site.target, (String) site.key, false, context, false);
        }

View on GitHub (pinned to a22eb90246)