{"record":{"id":"3a77b4f7b56b77c5","repo":"evanw/esbuild","slug":"unterminated-regular-expression","errorCode":null,"errorMessage":"Unterminated regular expression","messagePattern":"Unterminated regular expression","errorType":"panic","errorClass":"LexerPanic","httpStatus":null,"severity":"error","filePath":"internal/js_lexer/js_lexer.go","lineNumber":2044,"sourceCode":"\t}\n\n\t// None of these are allowed in JSON\n\tif lexer.json == JSON && (first == '.' || base != 0 || underscoreCount > 0 || isMissingDigitAfterDot) {\n\t\tlexer.Unexpected()\n\t}\n}\n\nfunc (lexer *Lexer) ScanRegExp() {\n\tvalidateAndStep := func() {\n\t\tif lexer.codePoint == '\\\\' {\n\t\t\tlexer.step()\n\t\t}\n\n\t\tswitch lexer.codePoint {\n\t\tcase -1, // This indicates the end of the file\n\t\t\t'\\r', '\\n', 0x2028, 0x2029: // Newlines aren't allowed in regular expressions\n\t\t\tlexer.addRangeError(logger.Range{Loc: logger.Loc{Start: int32(lexer.end)}}, \"Unterminated regular expression\")\n\t\t\tpanic(LexerPanic{})\n\n\t\tdefault:\n\t\t\tlexer.step()\n\t\t}\n\t}\n\n\tfor {\n\t\tswitch lexer.codePoint {\n\t\tcase '/':\n\t\t\tlexer.step()\n\t\t\tbits := uint32(0)\n\t\t\tfor js_ast.IsIdentifierContinue(lexer.codePoint) {\n\t\t\t\tswitch lexer.codePoint {\n\t\t\t\tcase 'd', 'g', 'i', 'm', 's', 'u', 'v', 'y':\n\t\t\t\t\tbit := uint32(1) << uint32(lexer.codePoint-'a')\n\t\t\t\t\tif (bit & bits) != 0 {\n\t\t\t\t\t\t// Reject duplicate flags\n\t\t\t\t\t\tr1 := logger.Range{Loc: logger.Loc{Start: int32(lexer.start)}, Len: 1}","sourceCodeStart":2026,"sourceCodeEnd":2062,"githubUrl":"https://github.com/evanw/esbuild/blob/f6058f8364fe7ab91ca57a83e02577ed74c9cae4/internal/js_lexer/js_lexer.go#L2026-L2062","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst r = /abc\n// after\nconst r = /abc/","handlingStrategy":"validation","validationCode":"import \"github.com/evanw/esbuild/pkg/api\"\nres := api.Transform(src, api.TransformOptions{Loader: api.LoaderJS})\nfor _, m := range res.Errors {\n  if strings.Contains(m.Text, \"Unterminated regular expression\") {\n    // locate the offending '/' via m.Location and surface to the user\n  }\n}","typeGuard":"// Crude check: a '/' that starts a regex must find a closing '/' before a newline.\nfunc regexLiteralClosedFrom(line string, start int) bool {\n  for i := start + 1; i < len(line); i++ {\n    if line[i] == '\\\\' { i++; continue }\n    if line[i] == '/' { return true }\n    if line[i] == '\\n' { return false }\n  }\n  return false\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["syntax-error","lexer","regex"],"backgroundTag":null,"analyzedSha":"f6058f8364fe7ab91ca57a83e02577ed74c9cae4","analyzedAt":"2026-08-09T18:37:22.223Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}