rectorphp/rector · error · InvalidEregException
unescaped metacharacter ")"
Error message
unescaped metacharacter ")"
What it means
EregToPcreTransformer::transform() converts a POSIX ERE pattern (from an ereg()/eregi()/ereg_replace()/split()/spliti() call handled by EregToPregMatchRector) into a PCRE pattern. After the recursive conversion returns, it checks that the whole input was consumed; leftover input means the pattern contained an unbalanced closing ')' at the top level, which is invalid ERE. The resulting InvalidEregException aborts the Rector run on that file.
Solutions
- Find the failing ereg call (the Rector error output names the file) and fix the literal pattern: escape the parenthesis as '\)' if it is meant literally, or delete the stray one.
- Re-run rector to confirm the pattern now converts cleanly.
- If the pattern is intentionally odd, convert that call to preg_match() by hand and let rector skip it.
- Before large migrations, preflight-scan the codebase for ereg/split calls and validate their literal patterns with EregToPcreTransformer::transform() in a try/catch.
Example fix
// before - unbalanced top-level ')'
ereg('abc)', $subject);
// after - escaped literal parenthesis
ereg('abc\)', $subject); // rector rewrites to: preg_match('#abc\)#m', $subject) Defensive patterns
Strategy: try-catch
Validate before calling
// preflight: validate literal ereg patterns before running rector
use Rector\Php70\EregToPcreTransformer;
use Rector\Php70\Exception\InvalidEregException;
$transformer = new EregToPcreTransformer();
try {
$transformer->transform($literalPattern, false);
} catch (InvalidEregException $e) {
echo "Fix pattern before rector run: {$literalPattern} — {$e->getMessage()}\n";
} Type guard
function isConvertibleEreg(string $pattern): bool
{
try {
(new \Rector\Php70\EregToPcreTransformer())->transform($pattern, false);
return true;
} catch (\Rector\Php70\Exception\InvalidEregException $e) {
return false;
}
} Try / catch
try {
$pcre = $eregToPcreTransformer->transform($pattern, $ignoreCase);
} catch (\Rector\Php70\Exception\InvalidEregException $e) {
// log the file/pattern, fix the source literal by hand, then re-run rector
$this->errors[] = [$file, $pattern, $e->getMessage()];
continue;
} Prevention
- Before running the Php70 set, grep the codebase for ereg/eregi/ereg_replace/eregi_replace/split/spliti and eyeball each literal pattern.
- Balance parentheses/brackets in legacy patterns; a stray ')' at top level is exactly what this error reports.
- Validate suspicious literals up front with EregToPcreTransformer::transform() inside a try/catch harness.
When it happens
Trigger: Running rector with the Php70 set (or EregToPregMatchRector enabled) at a target PHP >= 7.0 on code containing an ereg-family call whose first argument is a string literal with a stray ')', e.g. `ereg('abc)', $s)` or `split(')|', $s)`. The inner parser breaks out at the ')' and transform() detects $i !== strlen($content).
Common situations: Migrating a legacy PHP 5 codebase to PHP 7+ where hand-written ereg patterns were never validated; patterns built by string concatenation that ended up with an extra ')'; copy-pasted legacy regexes that only worked because ereg tolerated them or were never executed.
Related errors
- a literal null byte in the regex
- an invalid bound
- an invalid character range
- an invalid escape sequence at the end
- an invalid or unsupported character class
AI-assisted analysis of rectorphp/rector@408fcb0ff1 (2026-08-21).
Data as JSON: /api/errors/805b12bf3ccec70f.
Report an issue: GitHub.
Appendix: source
Thrown at rules/Php70/EregToPcreTransformer.php:81
* Single type is chosen to prevent every regular with different delimiter.
*/
public function __construct(string $pcreDelimiter = '#')
{
$this->pcreDelimiter = $pcreDelimiter;
}
// converts the ERE $s into the PCRE $r. triggers error on any invalid input.
public function transform(string $content, bool $ignorecase): string
{
if ($ignorecase) {
if (isset($this->icache[$content])) {
return $this->icache[$content];
}
} elseif (isset($this->cache[$content])) {
return $this->cache[$content];
}
[$r, $i] = $this->_ere2pcre($content, 0);
if ($i !== strlen($content)) {
throw new InvalidEregException('unescaped metacharacter ")"');
}
if ($ignorecase) {
return $this->icache[$content] = $this->pcreDelimiter . $r . $this->pcreDelimiter . 'mi';
}
return $this->cache[$content] = $this->pcreDelimiter . $r . $this->pcreDelimiter . 'm';
}
/**
* Recursively converts ERE into PCRE, starting at the position $i.
*
* @return float[]|int[]|string[]
*/
private function _ere2pcre(string $content, int $i): array
{
$r = [''];
$rr = 0;
$l = strlen($content);
$normalizeUnprintableChar = \false;
while ($i < $l) {View on GitHub (pinned to 408fcb0ff1)