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
- Flatten or reduce the nesting depth of the selector (merge redundant :is()/:not() wrappers).
- Catch Symfony\Component\CssSelector\Exception\SyntaxErrorException around cssToXPath and reject the input as invalid CSS.
- Pre-check user-supplied selector strings for nesting depth before parsing.
- 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
- Cap user-supplied selector length and parenthesis depth before parsing.
- Do not build :is()/:not() chains in loops.
- Run cssToXPath inside try-catch whenever input is not fully controlled.
- Pin selector generation templates to known-flat shapes.
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
- Unclosed/invalid string at
- String not allowed as function argument.
- Unexpected pseudo-element
- Got too deeply nested :has().
- Expected , but found.
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)