{"record":{"id":"8bb6e45a8088eff9","repo":"karatelabs/karate","slug":"a-lexical-declaration-may-not-be-the-body-of-where","errorCode":null,"errorMessage":"a lexical declaration may not be the body of ${where}","messagePattern":"a lexical declaration may not be the body of (.+?)","errorType":"exception","errorClass":"ParserException","httpStatus":null,"severity":"error","filePath":"karate-js/src/main/java/io/karatelabs/parser/JsParser.java","lineNumber":787,"sourceCode":"     *  of an `if`/`else`/loop clause is a Statement, and both are Declarations, not\n     *  Statements (§13.6/§14.x). A {@code var} declaration hoists and stays legal; a\n     *  braced body (BLOCK) is fine. Mode-independent — there is no Annex B carve-out\n     *  for these the way there is for FunctionDeclaration in an `if` clause. */\n    private static void checkNoLexicalOrClassDeclarationBody(Node node, String where) {\n        for (int i = 0, n = node.size(); i < n; i++) {\n            Node child = node.get(i);\n            if (child.isToken() || child.type != NodeType.STATEMENT) {\n                continue;\n            }\n            for (int j = 0, m = child.size(); j < m; j++) {\n                Node inner = child.get(j);\n                if (!inner.isToken()) {\n                    if (inner.type == NodeType.CLASS_EXPR) {\n                        throw new ParserException(\n                                \"a class declaration may not be the body of \" + where);\n                    }\n                    if (inner.type == NodeType.VAR_STMT && isLexicalVarStmt(inner)) {\n                        throw new ParserException(\n                                \"a lexical declaration may not be the body of \" + where);\n                    }\n                    break; // first non-token child decides\n                }\n            }\n        }\n    }\n\n    /** True if a {@code VAR_STMT}'s leading keyword token is {@code let} / {@code const}\n     *  (a LexicalDeclaration) rather than {@code var}. The keyword distinction lives on\n     *  VAR_STMT, not on the VAR_DECL children.\n     *  <p>One sloppy-mode subtlety: {@code let} is not a reserved word, so as the body of\n     *  an `if`/loop clause {@code let} followed by a LineTerminator is the identifier\n     *  {@code let} as an ExpressionStatement with ASI inserted before the next line\n     *  (`if (x) let\\n y = 1` is two statements: `let; y = 1`). The only `let`-form the\n     *  ExpressionStatement lookahead forbids is `let [`. So a LineTerminator immediately\n     *  after a {@code let} keyword means it is NOT a lexical declaration here. {@code const}\n     *  is a reserved word and has no such escape. */","sourceCodeStart":769,"sourceCodeEnd":805,"githubUrl":"https://github.com/karatelabs/karate/blob/a22eb90246d958d15a47bf436693d0121ad2812d/karate-js/src/main/java/io/karatelabs/parser/JsParser.java#L769-L805","documentation":"`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.","triggerScenarios":"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.","commonSituations":"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).","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)."],"exampleFix":"// before\nif (ok) const timeout = 5000;\n// after\nif (ok) { const timeout = 5000; }","handlingStrategy":"validation","validationCode":"function hasUnbracedLexicalBody(src) {\n  // let/const (or let[) directly as a clause body\n  return /(^|[^\\w$])(if|else|for|while|do|\\w+:)\\s*(let|const)\\s*[\\w$[{]/.test(src);\n}\nif (hasUnbracedLexicalBody(src)) throw new Error('brace the let/const declaration');","typeGuard":null,"tryCatchPattern":"try {\n  karate.eval(script);\n} catch (e) {\n  if (String(e.message).startsWith('a lexical declaration may not be the body of')) {\n    // wrap the let/const in a block and re-evaluate\n  } else throw e;\n}","preventionTips":["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)."],"tags":["javascript","parser","syntax-error","lexical-declaration"],"backgroundTag":"invalid-syntax","analyzedSha":"a22eb90246d958d15a47bf436693d0121ad2812d","analyzedAt":"2026-09-12T09:01:00.220Z","contentChangedAt":"2026-09-12T09:01:00.220Z","schemaVersion":2},"datasetVersion":"2026-09-19T12:17:13.211Z"}