karatelabs/karate · error · ParserException

identifier ' ' has already been declared

Error message

identifier '${name}' has already been declared

What it means

This is a strict-mode-style early error from the Karate JS parser: a lexical declaration (let/const/class) re-declares an identifier that was already lexically declared in the same scope. JavaScript forbids duplicate lexical bindings; in sloppy mode duplicate function declarations are tolerated, but in strict mode (Karate's default) any duplicate errors at parse time. It is thrown before any code executes, so nothing of the script runs.

Solutions

  1. Rename one of the conflicting declarations so each lexical name is unique per scope
  2. Drop the redundant `let`/`const` keyword and just reassign the existing binding (e.g. `x = 2;`)
  3. Move one declaration into a nested block `{ ... }` if a separate binding is genuinely intended
  4. Check for accidental duplicated paste of a declaration line or block

Example fix

// before
let temp = a;
let temp = b; // duplicate
// after
let tempA = a;
let tempB = b;
Defensive patterns

Strategy: validation

Validate before calling

// before running a Karate JS snippet, check for duplicate let/const/class names per scope
function hasDuplicateLexicalDecls(src) {
  const decls = [...src.matchAll(/\b(?:let|const|class)\s+([A-Za-z_$][\w$]*)/g)].map(m => m[1]);
  return decls.length !== new Set(decls).size;
}

Try / catch

try {
  karate.configure('...', jsSnippet);
} catch (e) {
  if (String(e).includes('has already been declared')) {
    // surface the offending name and fix the script
  }
  throw e;
}

Prevention

When it happens

Trigger: Parsing a script where two let/const/class declarations in the same block use the same name, e.g. `let x = 1; let x = 2;` — the duplicate-name check over lexNames fires when seen.add(name) fails and the name is either a non-function lexical binding or the code is strict.

Common situations: Copy-pasting a variable declaration into a Karate JS block that already declares it; refactoring that leaves both `let` and `const` for the same name in one function or block; converting `var` to `let` in two places without noticing the collision.

Related errors


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

Appendix: source

Thrown at karate-js/src/main/java/io/karatelabs/parser/JsParser.java:1268

                }
            } else if (d.type == NodeType.FN_EXPR && !topLevel) {
                String name = declarationName(d);
                if (name != null) {
                    lexNames.add(name); // function-bound — deliberately not in lexNonFn
                }
            }
        }
        if (lexNames.isEmpty()) {
            return; // no lexical declarations → no duplicate and no clash possible
        }
        // Duplicate LexicallyDeclaredNames. Annex B.3.3: a sloppy duplicate bound only
        // by FunctionDeclarations is allowed; everything else (and all of strict) errors.
        Set<String> seen = new HashSet<>();
        for (String name : lexNames) {
            if (!seen.add(name)) {
                boolean onlyFunctions = !lexNonFn.contains(name);
                if (strict || !onlyFunctions) {
                    throw new ParserException(
                            "identifier '" + name + "' has already been declared");
                }
            }
        }
        // Lexical names may not also be var-declared anywhere in the scope (vars hoist
        // through nested blocks; at top level a FunctionDeclaration is itself var-scoped).
        List<String> varNames = new ArrayList<>();
        for (Node stmt : statements) {
            collectVarNames(stmt, varNames);
            if (topLevel) {
                Node d = firstNonToken(stmt);
                if (d != null && d.type == NodeType.FN_EXPR) {
                    String name = declarationName(d);
                    if (name != null) {
                        varNames.add(name);
                    }
                }
            }

View on GitHub (pinned to a22eb90246)