rectorphp/rector · error · InvalidEregException

an invalid character range %d-%d"

Error message

an invalid character range %d-%d"

What it means

processSquareBracket() parsed a range 'a-b' inside a character class and found ord($a) > ord($b) — the range is inverted (EregToPcreTransformer.php:226). POSIX ERE requires the start code point to be <= the end code point, so '[z-a]' is invalid and InvalidEregException('an invalid character range %d-%d"') is thrown with the offending code points, aborting the ereg-to-preg conversion.

Source

Thrown at rules/Php70/EregToPcreTransformer.php:226

        do {
            if ($s[$i] === '[' && $i + 1 < $l && strpos('.=:', $s[$i + 1]) !== \false) {
                /** @var string $cls */
                [$cls, $i] = $this->processCharacterClass($s, $i, $cls);
            } else {
                $a = $s[$i];
                ++$i;
                if ($a === '-' && !$start && ($i >= $l || $s[$i] !== ']')) {
                    throw new InvalidEregException('"-" is invalid for the start character in the brackets');
                }
                if ($i < $l && $s[$i] === '-') {
                    $b = $s[++$i];
                    if ($b === ']') {
                        $cls .= $this->_ere2pcre_escape($a) . '\-';
                        break;
                    }
                    if (ord($a) > ord($b)) {
                        $errorMessage = sprintf('an invalid character range %d-%d"', (int) $a, (int) $b);
                        throw new InvalidEregException($errorMessage);
                    }
                    $cls .= $this->_ere2pcre_escape($a) . '-' . $this->_ere2pcre_escape($b);
                    ++$i;
                } else {
                    $cls .= $this->_ere2pcre_escape($a);
                }
            }
            $start = \false;
        } while ($i < $l && $s[$i] !== ']');
        return [$cls, $i];
    }
    private function _ere2pcre_escape(string $content): string
    {
        if ($content === "\x00") {
            throw new InvalidEregException('a literal null byte in the regex');
        }
        if (strpos('\^$.[]|()?*+{}-/', $content) !== \false) {
            return '\\' . $content;

View on GitHub (pinned to 408fcb0ff1)

Solutions

  1. Swap the endpoints in the flagged pattern: '[z-a]' -> '[a-z]'.
  2. If the intent was 'either case plus punctuation', write it explicitly, e.g. '[A-Za-z]'.
  3. Re-run rector; preflight-scan literal ereg patterns for '-'-separated pairs inside classes where the left code point exceeds the right.

Example fix

// before - inverted range
ereg('[z-a]', $subject);

// after - correct order
ereg('[a-z]', $subject); // rector: preg_match('#[a-z]#m', $subject)
Defensive patterns

Strategy: validation

Validate before calling

// quick lint: every a-b range inside a class must satisfy ord(a) <= ord(b)
function rangesOrdered(string $class): bool {
    return !preg_match('~(.)-(.)~', $class, $m) || ord($m[1]) <= ord($m[2]);
}

Try / catch

try {
    $pcre = $eregToPcreTransformer->transform($pattern, $ignoreCase);
} catch (InvalidEregException $e) {
    // inverted range like [z-a] — swap the endpoints and re-run
    continue;
}

Prevention

When it happens

Trigger: Rector (Php70 set / EregToPregMatchRector, target PHP >= 7.0) processes an ereg-family call whose literal pattern contains an inverted class range, e.g. `ereg('[z-a]', $s)`, `split('[Z-a]', $s)` is fine (Z < a) but `'[9-0]'` or `'[b-A]'` throw. The sprintf fills in (int) ord values of both endpoints.

Common situations: Typos when hand-writing ranges; 'make it match everything A-z' attempts that swap the endpoints; ranges copied between case-sensitive and case-insensitive contexts (eregi) where the author reversed letters expecting case folding.

Understand the failure class

Related errors


AI-assisted analysis of rectorphp/rector@408fcb0ff1 (2026-08-21). Data as JSON: /api/errors/d4b97d62a7ac83a7. Report an issue: GitHub.