can1357/oh-my-pi · error · OmpTypeError
unterminated regular expression in "${src}"
Error message
unterminated regular expression in "${src}" What it means
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.
Source
Thrown at packages/omptype/src/ir.ts:226
for (; j < n && src[j] !== c; j++) {
if (src[j] === "\\") {
j++;
if (j >= n) break;
}
value += src[j];
}
if (j >= n) throw new OmpTypeError(`unterminated string literal in "${src}"`);
toks.push({ t: "str", v: value });
i = j + 1;
continue;
}
if (c === "/") {
let j = i + 1;
for (; j < n; j++) {
if (src[j] === "\\") j++;
else if (src[j] === "/") break;
}
if (j >= n) throw new OmpTypeError(`unterminated regular expression in "${src}"`);
let end = j + 1;
while (end < n && /[dgimsuvy]/.test(src[end])) end++;
const source = src.slice(i + 1, j);
const flags = src.slice(j + 1, end);
try {
toks.push({ t: "regex", v: new RegExp(source, flags) });
} catch {
throw new OmpTypeError(`invalid regular expression "${src.slice(i, end)}"`);
}
i = end;
continue;
}
if ((c >= "0" && c <= "9") || (c === "-" && i + 1 < n && src[i + 1] >= "0" && src[i + 1] <= "9")) {
let j = i + 1;
while (j < n && /[\w.+-]/.test(src[j])) j++;
const raw = src.slice(i, j);
if (/^-?(?:0|[1-9]\d*)n$/.test(raw) && raw !== "-0n") {
toks.push({ t: "bigint", v: BigInt(raw.slice(0, -1)) });View on GitHub (pinned to 9690622007)
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.
Example fix
// before type(`path ~= /api/v1/`); // wait — pattern slash unescaped / closing logic // after type(`path ~= /api\/v1/`);
Defensive patterns
Strategy: validation
Validate before calling
function closedRegex(s: string): boolean {
let i = s.indexOf("/");
if (i < 0) return true; // no regex literal
for (i++; i < s.length; i++) {
if (s[i] === "\\") { i++; continue; }
if (s[i] === "/") return true;
}
return false;
} Try / catch
try {
const t = new TypeExpression(src);
} catch (err) {
if (err instanceof Error && err.message.includes("unterminated regular expression")) {
throw new Error(`Close the regex literal: ${err.message}`);
}
throw err;
} Prevention
- Escape literal slashes in patterns as `\/`.
- Ensure the closing `/` is not preceded by a backslash.
- Copy regexes with their delimiters intact.
When it happens
Trigger: Expressions like `value ~= /abc` or `/^foo\.bar$` with the closing slash missing; a trailing backslash escaping the intended closing slash (`/abc\/`).
Common situations: Hand-written pattern constraints with a dropped slash; slashes stripped by string escaping layers; regex copied without its delimiters.
Related errors
- unterminated date literal in "${src}"
- unterminated string literal in "${src}"
- invalid regular expression "${src.slice(i, end)}"
- err.to_string() (invalid regex pattern)
- invalid regular expression: {0}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/e122e26e4ff2f98b.
Report an issue: GitHub.