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

  1. Use the membership test form: `#x in obj` to check for the private member's existence
  2. Access the value through a public getter/method defined in the class instead of referencing `#x` directly
  3. 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

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


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)