karatelabs/karate · error · ParserException
a lexical declaration may not be the body of
Error message
a lexical declaration may not be the body of ${where} What it means
`let` and `const` declarations are LexicalDeclarations, not Statements, so they cannot be the unbraced body of an `if`/`else` clause, loop, or labelled statement. Karate's parser enforces this in all modes (no Annex B exception). `var` remains legal because it hoists; a braced block is also fine.
Solutions
- Brace the clause: `if (cond) { let x = 1; }`.
- Declare the variable before the clause and assign inside it.
- If hoisting semantics are truly wanted, use `var`: `if (cond) var x = 1;` (not recommended).
Example fix
// before
if (ok) const timeout = 5000;
// after
if (ok) { const timeout = 5000; } Defensive patterns
Strategy: validation
Validate before calling
function hasUnbracedLexicalBody(src) {
// let/const (or let[) directly as a clause body
return /(^|[^\w$])(if|else|for|while|do|\w+:)\s*(let|const)\s*[\w$[{]/.test(src);
}
if (hasUnbracedLexicalBody(src)) throw new Error('brace the let/const declaration'); Try / catch
try {
karate.eval(script);
} catch (e) {
if (String(e.message).startsWith('a lexical declaration may not be the body of')) {
// wrap the let/const in a block and re-evaluate
} else throw e;
} Prevention
- Never write `if (x) let y = ...` — brace it.
- When upgrading `var` to `let`/`const`, check the enclosing clause still has braces.
- Remember `var` is the only declaration legal as an unbraced clause body (and avoid it anyway).
When it happens
Trigger: Writing `if (cond) let x = 1;`, `while (x) const y = f();`, or `label: let [a] = arr;` in Karate-embedded JS. Note the ASI subtlety: `let` followed by a line terminator is parsed as the identifier `let` and does not trigger this error.
Common situations: Omitting braces around a `let`/`const` in a one-line conditional; converting `var` to `let`/`const` in existing unbraced code (`if (x) var a = 1;` was legal, `if (x) let a = 1;` is not).
Related errors
- a class declaration may not be the body of
- a function declaration may not be the body of
- 'await' is a reserved word in a class static initialization…
- 'break' may not cross a class static initialization block
- 'continue' may not cross a class static initialization block
AI-assisted analysis of karatelabs/karate@a22eb90246 (2026-09-12).
Data as JSON: /api/errors/8bb6e45a8088eff9.
Report an issue: GitHub.
Appendix: source
Thrown at karate-js/src/main/java/io/karatelabs/parser/JsParser.java:787
* of an `if`/`else`/loop clause is a Statement, and both are Declarations, not
* Statements (§13.6/§14.x). A {@code var} declaration hoists and stays legal; a
* braced body (BLOCK) is fine. Mode-independent — there is no Annex B carve-out
* for these the way there is for FunctionDeclaration in an `if` clause. */
private static void checkNoLexicalOrClassDeclarationBody(Node node, String where) {
for (int i = 0, n = node.size(); i < n; i++) {
Node child = node.get(i);
if (child.isToken() || child.type != NodeType.STATEMENT) {
continue;
}
for (int j = 0, m = child.size(); j < m; j++) {
Node inner = child.get(j);
if (!inner.isToken()) {
if (inner.type == NodeType.CLASS_EXPR) {
throw new ParserException(
"a class declaration may not be the body of " + where);
}
if (inner.type == NodeType.VAR_STMT && isLexicalVarStmt(inner)) {
throw new ParserException(
"a lexical declaration may not be the body of " + where);
}
break; // first non-token child decides
}
}
}
}
/** True if a {@code VAR_STMT}'s leading keyword token is {@code let} / {@code const}
* (a LexicalDeclaration) rather than {@code var}. The keyword distinction lives on
* VAR_STMT, not on the VAR_DECL children.
* <p>One sloppy-mode subtlety: {@code let} is not a reserved word, so as the body of
* an `if`/loop clause {@code let} followed by a LineTerminator is the identifier
* {@code let} as an ExpressionStatement with ASI inserted before the next line
* (`if (x) let\n y = 1` is two statements: `let; y = 1`). The only `let`-form the
* ExpressionStatement lookahead forbids is `let [`. So a LineTerminator immediately
* after a {@code let} keyword means it is NOT a lexical declaration here. {@code const}
* is a reserved word and has no such escape. */View on GitHub (pinned to a22eb90246)