symfony/css-selector · error · SyntaxErrorException

Got too deeply nested

Error message

Got too deeply nested :%s().

What it means

SyntaxErrorException thrown by Parser::parseNestedSelectorList when CSS nesting (e.g. :is(), :has(), :not() argument selectors) exceeds the parser's fixed NESTING_LIMIT. The library imposes this cap to prevent stack overflows / denial-of-service on deeply nested malicious selectors. It is a hard compile-time limit, not a tunable option.

Solutions

  1. Flatten or reduce the nesting depth of the selector (merge redundant :is()/:not() wrappers).
  2. Catch Symfony\Component\CssSelector\Exception\SyntaxErrorException around cssToXPath and reject the input as invalid CSS.
  3. Pre-check user-supplied selector strings for nesting depth before parsing.
  4. If a deeper limit is genuinely needed, fork/patch the NESTING_LIMIT constant in Parser (no runtime config exists).

Example fix

// before
$xpath = $translator->cssToXPath($userSelector); // throws on deep nesting
// after
try {
    $xpath = $translator->cssToXPath($userSelector);
} catch (SyntaxErrorException $e) {
    $xpath = null; // reject selector
}
Defensive patterns

Strategy: try-catch

Validate before calling

function isSafeSelectorDepth(string $css, int $max = 15): bool {
    $depth = 0; $maxSeen = 0;
    foreach (str_split($css) as $c) {
        if ($c === '(') { $maxSeen = max($maxSeen, ++$depth); }
        elseif ($c === ')') { --$depth; }
    }
    return $maxSeen <= $max;
}

Type guard

function hasAcceptableNesting(string $css): bool { return substr_count($css, ':') <= 20 && substr_count($css, '(') <= 15; }

Try / catch

try {
    $xpath = $translator->cssToXPath($css);
} catch (\Symfony\Component\CssSelector\Exception\SyntaxErrorException $e) {
    if (str_contains($e->getMessage(), 'too deeply nested')) {
        // reject selector
    }
}

Prevention

When it happens

Trigger: Calling Translator::cssToXPath (or the Parser directly) with a selector whose nested functional pseudo-classes are nested more than self::NESTING_LIMIT levels deep, e.g. :is(:is(:is(...))) repeated past the limit.

Common situations: Programmatically generated selectors (loops building nested :not/:is chains), user-supplied CSS selectors processed server-side, minified or concatenated selector strings from bundlers.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of symfony/css-selector@08e2905152 (2026-09-14). Data as JSON: /api/errors/2723e399d3e917ba. Report an issue: GitHub.

Appendix: source

Thrown at Parser/Parser.php:375

            }
        }

        if (\count($stream->getUsed()) === $selectorStart) {
            throw SyntaxErrorException::unexpectedToken('selector', $stream->getPeek());
        }

        return [$result, $pseudoElement];
    }

    /**
     * @return Node\SelectorNode[]
     *
     * @throws SyntaxErrorException
     */
    private function parseNestedSelectorList(TokenStream $stream, string $identifier): array
    {
        if ($this->nestingDepth >= self::NESTING_LIMIT) {
            throw new SyntaxErrorException(\sprintf('Got too deeply nested :%s().', $identifier));
        }

        ++$this->nestingDepth;

        try {
            return $this->parseSelectorList($stream, true);
        } finally {
            --$this->nestingDepth;
        }
    }

    private function parseElementNode(TokenStream $stream): Node\ElementNode
    {
        $peek = $stream->getPeek();

        if ($peek->isIdentifier() || $peek->isDelimiter(['*'])) {
            if ($peek->isIdentifier()) {
                $namespace = $stream->getNext()->getValue();

View on GitHub (pinned to 08e2905152)