karatelabs/karate · error · ParserException
unexpected private name
Error message
unexpected private name '${name}': only valid as the left operand of `in` What it means
Private class names (`#name` in Karate's JS dialect, `#x` private fields) may only appear as the left operand of the `in` operator (existence check) inside the class that declares them. During the post-parse privateNodeChecks pass, any PRIVATE_NAME_EXPR node whose parent is not an IN_EXPR with itself as the first child is rejected. This enforces the spec rule that private fields cannot be accessed directly outside controlled positions.
Solutions
- Use the membership test form: `#x in obj` to check for the private member's existence
- Access the value through a public getter/method defined in the class instead of referencing `#x` directly
- Ensure the private name is used inside the class that declares it; declare it if missing (otherwise the related 'not declared' error fires)
Example fix
// before
if (obj.#count) { ... }
// after
if (#count in obj) { ... } Defensive patterns
Strategy: validation
Validate before calling
// Ensure private names appear only as `#name in obj`: const bad = /(?<!in\s)#\w+/g; // find private names not preceded by `in` and review them
Type guard
const isPrivateInCheck = (expr) => /^#\w+\s+in\s+/.test(expr.trim());
Try / catch
try { eval(js); } catch (e) { if (String(e).includes('unexpected private name')) { /* rewrite to `#x in obj` */ } throw e; } Prevention
- Use `#x in obj` for all private-member existence checks
- Access private values only via class methods
- Never treat private names as first-class expressions
When it happens
Trigger: Evaluating a private name in any expression position other than `#name in obj` — e.g. `obj.#x`, `#x` standalone, `#x === 1`, or passing `#x` as a value — inside a class body, while childPrivates/privateNodeChecks walks the AST.
Common situations: Developers new to private fields trying to read or pass around a private member directly; refactoring code that moved a private name out of an `in` check; testing class internals from scripts outside the class scope.
Related errors
- cannot delete private member
- private name is not declared in an enclosing class
- #constructor is a reserved private name
- duplicate private name
- private name is not declared in an enclosing class
AI-assisted analysis of karatelabs/karate@a22eb90246 (2026-09-12).
Data as JSON: /api/errors/4b18a5518de97bbb.
Report an issue: GitHub.
Appendix: source
Thrown at karate-js/src/main/java/io/karatelabs/parser/JsParser.java:283
* <li>{@code delete obj.#x} is an error (§13.5.1), and a bare {@code #x} is
* legal only as the left operand of {@code in}.</li>
* </ul>
*/
private PrivateScope privateNodeChecks(Node node, PrivateScope privates) {
switch (node.type) {
case CLASS_EXPR -> {
return classPrivateScope(node, privates);
}
case REF_DOT_EXPR -> {
Node key = node.size() > 2 ? node.get(2) : null;
if (key != null && key.isToken() && key.token.type == PRIVATE_NAME) {
requirePrivateDeclared(key.getText(), privates);
}
}
case PRIVATE_NAME_EXPR -> {
Node parent = node.getParent();
if (parent == null || parent.type != NodeType.IN_EXPR || parent.getFirst() != node) {
throw new ParserException("unexpected private name '" + node.getText()
+ "': only valid as the left operand of `in`");
}
requirePrivateDeclared(node.getText(), privates);
}
case DELETE_EXPR -> {
Node target = node.size() > 1 ? node.get(1) : null;
while (target != null && !target.isToken()) {
if (target.type == NodeType.REF_DOT_EXPR) {
Node key = target.size() > 2 ? target.get(2) : null;
if (key != null && key.isToken() && key.token.type == PRIVATE_NAME) {
throw new ParserException("cannot delete private member " + key.getText());
}
break;
}
if (target.type == NodeType.PAREN_EXPR) {
target = target.size() > 1 ? target.get(1) : null;
} else if (target.type == NodeType.EXPR || target.type == NodeType.EXPR_LIST) {
target = target.size() == 1 ? target.getFirst() : null;View on GitHub (pinned to a22eb90246)