symfony/css-selector · error · SyntaxErrorException

Got nested :not().

Error message

Got nested :not().

What it means

SyntaxErrorException thrown by Parser::parseSimpleSelector() when a :not() appears inside another :not(). The parser passes $insideNegation=true into the nested parseSimpleSelector and rejects ':not(:not(...))' because nested negation is not allowed per the CSS Selectors level 3/4 rules this component implements.

Solutions

  1. Remove the inner :not(): ':not(.a)' instead of ':not(:not(.a))' (double negation is identity).
  2. Use :is() to express matching: ':is(.a)' for positive matching.
  3. Before wrapping a selector in :not(), check whether it already starts with :not( and unwrap instead.
  4. Catch SyntaxErrorException for untrusted selector input.

Example fix

// before
$parser->parse('div:not(:not(.active))');

// after
$parser->parse('div:not(.active)');
Defensive patterns

Strategy: validation

Validate before calling

if (preg_match('/:not\(\s*:not\(/i', $selector)) {
    throw new \InvalidArgumentException('Nested :not() is not supported; use the inner condition directly');
}

Try / catch

try {
    $nodes = (new Parser())->parse($selector);
} catch (SyntaxErrorException $e) {
    if (str_contains($e->getMessage(), ':not()')) { /* simplify selector */ }
}

Prevention

When it happens

Trigger: Parsing selectors like 'div:not(:not(.a))', ':not(:not(#id))'. Fired at Parser/Parser.php:291-294 when identifier is 'not' and $insideNegation is already true (i.e. we are parsing the argument of an outer :not).

Common situations: Programmatic negation of already-negated selectors (wrapping user selectors in :not() blindly), minified/generated CSS containing double negation, attempts to express 'matches' via double-negation instead of :is().

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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

Appendix: source

Thrown at Parser/Parser.php:293

                        if (!(2 === $used
                           || 3 === $used && $stream->getUsed()[0]->isWhiteSpace()
                           || $used >= 3 && $stream->getUsed()[$used - 3]->isDelimiter($prevSeparators)
                           || $used >= 4
                                && $stream->getUsed()[$used - 3]->isWhiteSpace()
                                && $stream->getUsed()[$used - 4]->isDelimiter($prevSeparators)
                        )) {
                            throw SyntaxErrorException::notAtTheStartOfASelector('scope');
                        }
                    }
                    continue;
                }

                $stream->getNext();
                $stream->skipWhitespace();

                if ('not' === strtolower($identifier)) {
                    if ($insideNegation) {
                        throw SyntaxErrorException::nestedNot();
                    }

                    [$argument, $argumentPseudoElement] = $this->parseSimpleSelector($stream, true, true);
                    $next = $stream->getNext();

                    if (null !== $argumentPseudoElement) {
                        throw SyntaxErrorException::pseudoElementFound($argumentPseudoElement, 'inside :not()');
                    }

                    if (!$next->isDelimiter([')'])) {
                        throw SyntaxErrorException::unexpectedToken('")"', $next);
                    }

                    $result = new Node\NegationNode($result, $argument);
                } elseif ('is' === strtolower($identifier)) {
                    $selectors = $this->parseNestedSelectorList($stream, 'is');

                    $next = $stream->getNext();

View on GitHub (pinned to 08e2905152)