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

  1. Close the regex with an unescaped `/`: `/abc/`.
  2. Escape any literal `/` inside the pattern as `\/` so the scanner doesn't end early — and check the final slash isn't itself escaped.
  3. 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

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


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/e122e26e4ff2f98b. Report an issue: GitHub.