{"record":{"id":"e122e26e4ff2f98b","repo":"can1357/oh-my-pi","slug":"unterminated-regular-expression-in-src","errorCode":null,"errorMessage":"unterminated regular expression in \"${src}\"","messagePattern":"unterminated regular expression in \"(.+?)\"","errorType":"exception","errorClass":"OmpTypeError","httpStatus":null,"severity":"error","filePath":"packages/omptype/src/ir.ts","lineNumber":226,"sourceCode":"\t\t\tfor (; j < n && src[j] !== c; j++) {\n\t\t\t\tif (src[j] === \"\\\\\") {\n\t\t\t\t\tj++;\n\t\t\t\t\tif (j >= n) break;\n\t\t\t\t}\n\t\t\t\tvalue += src[j];\n\t\t\t}\n\t\t\tif (j >= n) throw new OmpTypeError(`unterminated string literal in \"${src}\"`);\n\t\t\ttoks.push({ t: \"str\", v: value });\n\t\t\ti = j + 1;\n\t\t\tcontinue;\n\t\t}\n\t\tif (c === \"/\") {\n\t\t\tlet j = i + 1;\n\t\t\tfor (; j < n; j++) {\n\t\t\t\tif (src[j] === \"\\\\\") j++;\n\t\t\t\telse if (src[j] === \"/\") break;\n\t\t\t}\n\t\t\tif (j >= n) throw new OmpTypeError(`unterminated regular expression in \"${src}\"`);\n\t\t\tlet end = j + 1;\n\t\t\twhile (end < n && /[dgimsuvy]/.test(src[end])) end++;\n\t\t\tconst source = src.slice(i + 1, j);\n\t\t\tconst flags = src.slice(j + 1, end);\n\t\t\ttry {\n\t\t\t\ttoks.push({ t: \"regex\", v: new RegExp(source, flags) });\n\t\t\t} catch {\n\t\t\t\tthrow new OmpTypeError(`invalid regular expression \"${src.slice(i, end)}\"`);\n\t\t\t}\n\t\t\ti = end;\n\t\t\tcontinue;\n\t\t}\n\t\tif ((c >= \"0\" && c <= \"9\") || (c === \"-\" && i + 1 < n && src[i + 1] >= \"0\" && src[i + 1] <= \"9\")) {\n\t\t\tlet j = i + 1;\n\t\t\twhile (j < n && /[\\w.+-]/.test(src[j])) j++;\n\t\t\tconst raw = src.slice(i, j);\n\t\t\tif (/^-?(?:0|[1-9]\\d*)n$/.test(raw) && raw !== \"-0n\") {\n\t\t\t\ttoks.push({ t: \"bigint\", v: BigInt(raw.slice(0, -1)) });","sourceCodeStart":208,"sourceCodeEnd":244,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/omptype/src/ir.ts#L208-L244","documentation":"A regex literal in a type expression starts with `/` but no unescaped closing `/` appears before end of input (backslashes skip the next character during the scan). The tokenizer throws `OmpTypeError` instead of silently consuming the rest of the expression.","triggerScenarios":"Expressions like `value ~= /abc` or `/^foo\\.bar$` with the closing slash missing; a trailing backslash escaping the intended closing slash (`/abc\\/`).","commonSituations":"Hand-written pattern constraints with a dropped slash; slashes stripped by string escaping layers; regex copied without its delimiters.","solutions":["Close the regex with an unescaped `/`: `/abc/`.","Escape any literal `/` inside the pattern as `\\/` so the scanner doesn't end early — and check the final slash isn't itself escaped.","Verify the expression string after all shell/template escaping is applied."],"exampleFix":"// before\ntype(`path ~= /api/v1/`); // wait — pattern slash unescaped / closing logic\n// after\ntype(`path ~= /api\\/v1/`);","handlingStrategy":"validation","validationCode":"function closedRegex(s: string): boolean {\n  let i = s.indexOf(\"/\");\n  if (i < 0) return true; // no regex literal\n  for (i++; i < s.length; i++) {\n    if (s[i] === \"\\\\\") { i++; continue; }\n    if (s[i] === \"/\") return true;\n  }\n  return false;\n}","typeGuard":null,"tryCatchPattern":"try {\n  const t = new TypeExpression(src);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"unterminated regular expression\")) {\n    throw new Error(`Close the regex literal: ${err.message}`);\n  }\n  throw err;\n}","preventionTips":["Escape literal slashes in patterns as `\\/`.","Ensure the closing `/` is not preceded by a backslash.","Copy regexes with their delimiters intact."],"tags":["parser","regex","syntax-error"],"backgroundTag":"unterminated-literal","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}