evanw/esbuild · error · LexerPanic
Expected identifier but found %q
Error message
Expected identifier but found %q
What it means
In the named import/export clause parser, esbuild recorded a non-identifier (reserved keyword) where a binding name was expected, and the statement did not turn out to be an 'export ... from' re-export. It then reports 'Expected identifier but found <token>'.
Source
Thrown at internal/js_parser/js_parser.go:5989
isSingleLine = false
}
p.lexer.Next()
if p.lexer.HasNewlineBefore {
isSingleLine = false
}
}
if p.lexer.HasNewlineBefore {
isSingleLine = false
}
p.lexer.Expect(js_lexer.TCloseBrace)
// Throw an error here if we found a keyword earlier and this isn't an
// "export from" statement after all
if firstNonIdentifierLoc.Start != 0 && !p.lexer.IsContextualKeyword("from") {
r := js_lexer.RangeOfIdentifier(p.source, firstNonIdentifierLoc)
p.log.AddError(&p.tracker, r, fmt.Sprintf("Expected identifier but found %q", p.source.TextForRange(r)))
panic(js_lexer.LexerPanic{})
}
return items, isSingleLine
}
type parseBindingOpts struct {
isUsingStmt bool
}
func (p *parser) parseBinding(opts parseBindingOpts) js_ast.Binding {
loc := p.lexer.Loc()
switch p.lexer.Token {
case js_lexer.TIdentifier:
name := p.lexer.Identifier
// Forbid invalid identifiers
if (p.fnOrArrowDataParse.await != allowIdent && name.String == "await") ||View on GitHub (pinned to f6058f8364)
Solutions
- Alias reserved words: import { default as d } from 'mod', or use default-import syntax import d from 'mod'.
- Correct the keyword to a real identifier.
- Re-check generated specifier lists for stray keywords.
Example fix
// before
import { default } from 'mod'
// after
import d from 'mod' Defensive patterns
Strategy: validation
Validate before calling
import "github.com/evanw/esbuild/pkg/api"
res := api.Transform(src, api.TransformOptions{Loader: api.LoaderJS})
for _, m := range res.Errors {
if strings.Contains(m.Text, "Expected identifier but found") {
// a reserved word was used as an import/export binding name
}
} Type guard
// Flag reserved words used as bare names in import/export clauses.
var reserved = map[string]bool{"default": true, "class": true, "function": true, "return": true, "var": true, "let": true, "const": true}
var reClauseName = regexp.MustCompile(`(?:import|export)\\s*\\{([^}]*)\\}`)
func importExportUsesReservedWord(src string) bool {
for _, m := range reClauseName.FindAllStringSubmatch(src, -1) {
for _, part := range strings.Split(m[1], ",") {
name := strings.TrimSpace(strings.Fields(part)[0])
if reserved[name] { return true }
}
}
return false
} Prevention
- Alias reserved words in clauses: import { default as d }.
- Use default-import syntax (import d from 'm') instead of { default }.
- Lint import/export specifier lists with eslint-plugin-import.
When it happens
Trigger: import { default } from 'mod' or export { class } — using a reserved word as a bare specifier name without aliasing, or a stray keyword in the clause list.
Common situations: Using reserved words as import/export names without 'as'; typos; generated code emitting keywords as names.
Related errors
- Unexpected newline before "=>"
- For loop initializers cannot start with "async of"
- Invalid binding pattern
- Unexpected ":"
- Unexpected "..."
AI-assisted analysis of evanw/esbuild@f6058f8364 (2026-08-09).
Data as JSON: /api/errors/95d499134a31a507.
Report an issue: GitHub.