evanw/esbuild · error · LexerPanic
Unterminated regular expression
Error message
Unterminated regular expression
What it means
esbuild's ScanRegExp throws this when the body of a regular expression literal reaches end-of-file or a line terminator ('\r', '\n', U+2028, U+2029) before the closing '/'. Regex literals cannot span lines or contain raw newlines, so this always means the '/' was never closed (or the '/' was never meant to start a regex).
Source
Thrown at internal/js_lexer/js_lexer.go:2044
}
// None of these are allowed in JSON
if lexer.json == JSON && (first == '.' || base != 0 || underscoreCount > 0 || isMissingDigitAfterDot) {
lexer.Unexpected()
}
}
func (lexer *Lexer) ScanRegExp() {
validateAndStep := func() {
if lexer.codePoint == '\\' {
lexer.step()
}
switch lexer.codePoint {
case -1, // This indicates the end of the file
'\r', '\n', 0x2028, 0x2029: // Newlines aren't allowed in regular expressions
lexer.addRangeError(logger.Range{Loc: logger.Loc{Start: int32(lexer.end)}}, "Unterminated regular expression")
panic(LexerPanic{})
default:
lexer.step()
}
}
for {
switch lexer.codePoint {
case '/':
lexer.step()
bits := uint32(0)
for js_ast.IsIdentifierContinue(lexer.codePoint) {
switch lexer.codePoint {
case 'd', 'g', 'i', 'm', 's', 'u', 'v', 'y':
bit := uint32(1) << uint32(lexer.codePoint-'a')
if (bit & bits) != 0 {
// Reject duplicate flags
r1 := logger.Range{Loc: logger.Loc{Start: int32(lexer.start)}, Len: 1}View on GitHub (pinned to f6058f8364)
Solutions
- Add the missing closing '/' to the regex literal.
- Remove or escape any newline inside the regex body.
- If the '/' is actually division, disambiguate with a semicolon/parenthesization so the lexer does not read a regex.
Example fix
// before const r = /abc // after const r = /abc/
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, "Unterminated regular expression") {
// locate the offending '/' via m.Location and surface to the user
}
} Type guard
// Crude check: a '/' that starts a regex must find a closing '/' before a newline.
func regexLiteralClosedFrom(line string, start int) bool {
for i := start + 1; i < len(line); i++ {
if line[i] == '\\' { i++; continue }
if line[i] == '/' { return true }
if line[i] == '\n' { return false }
}
return false
} Prevention
- When concatenating/minifying, insert semicolons before statements starting with '/' so division is not read as a regex.
- Lint with eslint's no-div-regex rule (use /(1)/style for division).
- Never let raw newlines land inside a regex literal.
When it happens
Trigger: Source like '/abc<newline>' (missing closing slash), a newline accidentally embedded in a regex, or a '/' that esbuild interpreted as the start of a regex when division was intended.
Common situations: Missing closing '/'; minified/concatenated code where a division '/' is misread as a regex start (ASI ambiguity); regex spanning a wrapped line; a character class containing an unescaped newline.
Related errors
- Unicode escape sequence is out of range
- [%s] %q is missing a filter
- [%s] %q filter is not a valid Go regular expression: %q
- Unterminated string literal
- Unexpected newline before "=>"
AI-assisted analysis of evanw/esbuild@f6058f8364 (2026-08-09).
Data as JSON: /api/errors/3a77b4f7b56b77c5.
Report an issue: GitHub.