karatelabs/karate · error · JsErrorException (referenceError, via SlotTable.tdzError)

cannot access ' ' before initialization

Error message

cannot access '${node.getText()}' before initialization

What it means

This is a JavaScript temporal-dead-zone (TDZ) TypeError raised while evaluating a compound assignment (e.g. `x += 1`). Karate's JS engine keeps `let`/`const` bindings in a SlotTable marked TDZ until their declaration executes; reading such a binding throws this error before any RHS side effects run, matching the ES2015+ spec order.

Solutions

  1. Move the `let`/`const` declaration above the first read of the variable
  2. Rename the later declaration or the earlier use to eliminate the same-scope redeclaration
  3. Convert the binding to `var` only if legacy hoisting semantics are truly intended
  4. Reorder initialization so top-level consts are set before functions that reference them run

Example fix

// before
x += 1;
let x = 2;
// after
let x = 2;
x += 1;
Defensive patterns

Strategy: try-catch

Validate before calling

// JS (script under test)
if (typeof x === 'undefined' && declaredLater) { /* restructure */ }
// Java-side guard before eval: pre-scan script for let/const declared after use
code.lines().stream().filter(l -> l.matches(".*\\b(let|const)\\s+x\\b.*"))

Type guard

function isInitialized(v) { return typeof v !== 'undefined'; }
// note: typeof cannot help inside TDZ; guard by ordering, not by typeof on the TDZ name

Try / catch

try {
  evalScript("x += 1;");
} catch (JsErrorException e) {
  if (e.getMessage().contains("before initialization")) {
    // re-declare earlier or initialize before use
  } else throw e;
}

Prevention

When it happens

Trigger: A `let`/`const` variable is read on the LHS of a compound assignment (`x += ...`, `x *= ...`) via its slot fast path in PropertyAccess.compound before the `let`/`const` declaration that initializes it has executed — e.g. `x += 1; let x = 2;` or a hoisted function body touching a later-declared const.

Common situations: Porting `var` code to `let`/`const` so hoisting no longer provides `undefined`; accidentally shadowing a variable declared later in the same block/scope; recursive or callback code that runs before a module-level const is initialized.

Related errors


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

Appendix: source

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

    }

    /**
     * Compound assignment (`op=`, e.g. +=, -=, *=) in spec §13.15 order:
     * resolve the LHS Reference, GetValue it, and only then evaluate the RHS —
     * a computed-key or getter side effect precedes any RHS side effect, and
     * an abrupt completion at each step skips the rest. Returns the new value.
     */
    static Object compound(Node node, CoreContext context, TokenType operator, Node rhsNode, Node trackingNode) {
                return switch (node.type) {
            case REF_EXPR -> {
                // Slot fast path, in the same spec order as the name tail below: read the
                // LHS (a TDZ read throws before any RHS side effect), THEN evaluate the RHS.
                int slot = node.slot;
                Object[] frame;
                if (slot >= 0 && (frame = context.frame) != null && frame[slot] != SlotTable.UNDECLARED) {
                    Object oldValue = frame[slot];
                    if (oldValue == SlotTable.TDZ) {
                        throw SlotTable.tdzError(node.getText());
                    }
                    Object operand = Interpreter.eval(rhsNode, context);
                    if (context.isStopped()) yield Terms.UNDEFINED;
                    Object newValue = applyOperator(oldValue, operator, operand, context);
                    context.updateSlot(slot, newValue, trackingNode);
                    yield newValue;
                }
                yield compoundRefByName(node, context, operator, rhsNode, trackingNode);
            }
            case REF_DOT_EXPR, REF_BRACKET_EXPR -> {
                AccessSite site = resolveWriteSite(node, context);
                if (site == null || site == SHORT_CIRCUIT_SITE || context.isStopped()) yield Terms.UNDEFINED;
                Object oldValue = site.privateName != null
                        ? PrivateAccess.get(site.target, site.privateName, context)
                        : siteRead(site, context);
                if (context.isStopped()) yield Terms.UNDEFINED;
                Object operand = Interpreter.eval(rhsNode, context);
                if (context.isStopped()) yield Terms.UNDEFINED;

View on GitHub (pinned to a22eb90246)