evanw/esbuild · error · LexerPanic
Unexpected "-"
Error message
Unexpected "-"
What it means
In parseJSXTag's member-expression chain ('<a.b.c>'), after a '.' the following identifier segment must not contain a '-'. Dashes are illegal in JavaScript identifiers, so a hyphenated segment after a dot is rejected.
Source
Thrown at internal/js_parser/js_parser.go:5240
// Otherwise, this is an identifier
tag := js_ast.Expr{Loc: loc, Data: &js_ast.EIdentifier{Ref: p.storeNameInRef(tagName)}}
// Parse a member expression chain
var chain strings.Builder
chain.WriteString(tagName.String)
for p.lexer.Token == js_lexer.TDot {
p.lexer.NextInsideJSXElement()
memberRange := p.lexer.Range()
member := p.lexer.Identifier
p.lexer.ExpectInsideJSXElement(js_lexer.TIdentifier)
// Dashes are not allowed in member expression chains
index := strings.IndexByte(member.String, '-')
if index >= 0 {
p.log.AddError(&p.tracker, logger.Range{Loc: logger.Loc{Start: memberRange.Loc.Start + int32(index)}},
"Unexpected \"-\"")
panic(js_lexer.LexerPanic{})
}
chain.WriteString("." + member.String)
tag = js_ast.Expr{Loc: loc, Data: p.dotOrMangledPropParse(tag, member, memberRange.Loc, js_ast.OptionalChainNone, wasOriginallyDot)}
tagRange.Len = memberRange.Loc.Start + memberRange.Len - tagRange.Loc.Start
}
return tagRange, chain.String(), tag
}
func (p *parser) parseJSXElement(loc logger.Loc) js_ast.Expr {
// Keep track of the location of the first JSX element for error messages
if p.firstJSXElementLoc.Start == -1 {
p.firstJSXElementLoc = loc
}
// Parse the tag
startRange, startText, startTagOrNil := p.parseJSXTag()View on GitHub (pinned to f6058f8364)
Solutions
- Rename the member so it has no hyphen, e.g. '<ui.Button />'.
- For kebab-case custom elements, use a single tag '<my-el>' (no dots).
- Reference the component through a properly-named variable instead of a dotted path.
Example fix
// before <ui.button-1 /> // after <ui.Button />
Defensive patterns
Strategy: validation
Validate before calling
import "github.com/evanw/esbuild/pkg/api"
res := api.Transform(jsx, api.TransformOptions{Loader: api.LoaderJSX})
for _, m := range res.Errors {
if strings.Contains(m.Text, "Unexpected \"-\"") {
// a dotted JSX tag member contained a hyphen
}
} Type guard
// Flag dotted JSX tags whose member segment has a hyphen: <a.b-c>.
var reDashedMember = regexp.MustCompile(`<[-A-Za-z0-9]+(?:\.[-A-Za-z0-9]*)*\.[A-Za-z0-9]*-[A-Za-z0-9-]*`)
func jsxTagHasDashedMember(src string) bool { return reDashedMember.MatchString(src) } Prevention
- Use PascalCase / camelCase for component object members used in dotted JSX tags.
- Represent kebab-case elements as single tags <my-el>, not dotted paths.
- Lint JSX tag names with eslint-plugin-react where possible.
When it happens
Trigger: '<Foo.Bar-Baz />' or '<ui.button-1 />' — a dotted JSX tag whose member segment contains a hyphen.
Common situations: Trying to reach a kebab-case property via dot notation; confusing custom-element names ('<my-el>') with member access.
Related errors
- Expected identifier after %q in namespaced JSX name
- Unexpected backslash in JSX element
- Unexpected end of file before a closing %s
- Unexpected newline before "=>"
- For loop initializers cannot start with "async of"
AI-assisted analysis of evanw/esbuild@f6058f8364 (2026-08-09).
Data as JSON: /api/errors/f9b30c8892cb673a.
Report an issue: GitHub.