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

  1. Add the missing closing '/' to the regex literal.
  2. Remove or escape any newline inside the regex body.
  3. 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 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


AI-assisted analysis of evanw/esbuild@f6058f8364 (2026-08-09). Data as JSON: /api/errors/3a77b4f7b56b77c5. Report an issue: GitHub.