symfony/css-selector · error · SyntaxErrorException

Got immediate child pseudo-element

Error message

Got immediate child pseudo-element ":%s" not at the start of a selector

What it means

SyntaxErrorException thrown by Parser::parseSimpleSelector() when the :scope pseudo-class appears anywhere other than the start of a selector (or immediately after a separator like ',', '(', '>', '+', '~'). CSS defines :scope as anchoring to the scoping root, so mid-selector occurrences like 'div :scope p' are invalid in this parser.

Solutions

  1. Place :scope at the very start of the selector: ':scope > div' instead of 'div :scope'.
  2. If you need to scope relative to a context element, prepend ':scope ' once at the top level and keep inner selectors :scope-free.
  3. When building selectors programmatically, only ever prefix ':scope' to the final string, never inject it mid-string.
  4. Catch SyntaxErrorException and fall back to a :scope-free selector.

Example fix

// before
$parser->parse('div :scope p');

// after
$parser->parse(':scope div p'); // or 'div p' without scoping
Defensive patterns

Strategy: validation

Validate before calling

if (preg_match('/\S\s+:scope\b|^\s*[^:]*[^:\s]\s+:scope/', $selector) && !preg_match('/^:scope/', trim($selector)) && !preg_match('/[:,>(+~]\s*:scope/', $selector)) {
    // heuristic: :scope not at start and not right after a separator
    if (!preg_match('/^:scope\b/', trim($selector)) && !preg_match('/[:,>(+~]\s*:scope\b/', $selector)) {
        throw new \InvalidArgumentException(':scope must be at the start of a selector or right after a separator');
    }
}

Try / catch

try {
    $nodes = (new Parser())->parse($selector);
} catch (SyntaxErrorException $e) {
    // fall back to a selector without :scope
}

Prevention

When it happens

Trigger: Parsing selectors where ':scope' follows another selector or descendant combinator at start-of-selector position, e.g. 'div :scope', '.a :scope.b'. The check at Parser/Parser.php:270-283 inspects the used-token stream (separators ',' for normal, plus '(', '>', '+', '~' inside relative selectors) and throws when :scope is not preceded by an allowed separator at the start.

Common situations: Rewriting selectors to scope them with :scope by naive concatenation ('oldSelector + " :scope ..."'), porting browser querySelector code that allows relative :scope, generating scoped queries inside :has()/:is() arguments.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at Parser/Parser.php:282

                    continue;
                }

                if (!$stream->getPeek()->isDelimiter(['('])) {
                    $result = new Node\PseudoNode($result, $identifier);
                    if ('Pseudo[Element[*]:scope]' === $result->__toString()) {
                        $used = \count($stream->getUsed());
                        $prevSeparators = [','];
                        if ($insideRelativeSelector) {
                            $prevSeparators = [',', '(', '>', '+', '~'];
                        }
                        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()');

View on GitHub (pinned to 08e2905152)