symfony/css-selector · error · SyntaxErrorException
Expected , but found.
Error message
Expected %s, but %s found.
What it means
SyntaxErrorException thrown by Parser::parseRelativeSelector() when a :has() argument begins with a string or number token instead of a selector. Relative selectors inside :has() must be element/compound selectors (optionally preceded by >, +, ~), so tokens like "'text'" or '42' are rejected as unexpected tokens where 'an argument' was expected.
Solutions
- Replace string/number arguments with a proper selector: 'div:has(p)' to match divs containing a <p>.
- For text matching, use a different mechanism (e.g. XPath contains(text(),...) or filter results after conversion).
- Escape or strip user input so stray strings/numbers never reach :has() arguments.
- Catch SyntaxErrorException and show the offending token to the user.
Example fix
// before
$parser->parse('div:has("hello")');
// after
$parser->parse('div:has(span)'); // or filter text content post-conversion Defensive patterns
Strategy: validation
Validate before calling
if (preg_match('/:has\(\s*("|\'|[0-9])/', $selector)) {
throw new \InvalidArgumentException(':has() arguments must be selectors, not strings or numbers');
} Try / catch
try {
$nodes = (new Parser())->parse($selector);
} catch (SyntaxErrorException $e) {
// surface 'unexpected token' details to caller
} Prevention
- Only pass element/class/attribute selectors into :has()
- Use XPath or post-filtering for text matching instead of :has("text")
- Escape user input that might inject quotes or digits into :has()
When it happens
Trigger: Parsing selectors like 'div:has("text")', 'div:has(42)', 'div:has(> "foo")'. The check at Parser/Parser.php:181-183 fires when the peeked token after the optional combinator is a string or number token.
Common situations: Confusing :has() with functions that take strings (e.g. :contains-style selectors from other engines), copy-pasting jQuery-style selectors, passing raw text matches inside :has() expecting XPath-like text() semantics.
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
- Unexpected pseudo-element
- Got too deeply nested :has().
- Got immediate child pseudo-element
- Got nested :not().
- Got too deeply nested
AI-assisted analysis of symfony/css-selector@08e2905152 (2026-09-14).
Data as JSON: /api/errors/f93f06dce9ae086a.
Report an issue: GitHub.
Appendix: source
Thrown at Parser/Parser.php:182
++$this->hasNestingDepth;
try {
$arguments = [];
while (true) {
$stream->skipWhitespace();
$peek = $stream->getPeek();
if ($peek->isDelimiter(['+', '>', '~'])) {
$combinator = $stream->getNext()->getValue();
$stream->skipWhitespace();
$peek = $stream->getPeek();
} else {
$combinator = ' ';
}
if ($peek->isString() || $peek->isNumber()) {
throw SyntaxErrorException::unexpectedToken('an argument', $stream->getNext());
}
$selector = $this->parserSelectorNode($stream, true, true);
if (null !== $pseudoElement = $selector->getPseudoElement()) {
throw SyntaxErrorException::pseudoElementFound($pseudoElement, 'inside :has()');
}
$arguments[] = [$combinator, $selector];
if ($stream->getPeek()->isDelimiter([','])) {
$stream->getNext();
continue;
}
break;
}
View on GitHub (pinned to 08e2905152)