facebook/flow · error · SyntaxError
Syntax error in selector
Error message
Syntax error in selector "${rawSelector}" at position ${err.location.start.offset}: ${err.message} What it means
SyntaxError thrown by flow-transform's traverse when an esquery selector fails to parse. The raw selector (with a trailing `:exit` stripped) goes through `esquery.parse`; parse errors that carry a location are rethrown as SyntaxError with the offending selector, the character offset, and esquery's message. Thrown during listener registration, before traversal starts.
Solutions
- Validate every visitor key with `esquery.parse` at startup or in unit tests before wiring traverse.
- Remember `:exit` is the only supported suffix and it must be last (it is stripped before parsing).
- Check esquery's selector grammar (identifiers, `>`, ` `, `,`, attribute brackets, `:not`, `:matches`, `:nth-child(n)`).
- Log the raw selector on failure - the reported offset indexes into the pre-`:exit` string.
Example fix
// before
traverseWithContext(code, ast, scope, () => ({}), ctx => ({
'FunctionDeclaration >': node => {}, // dangling combinator - SyntaxError
}));
// after
traverseWithContext(code, ast, scope, () => ({}), ctx => ({
'FunctionDeclaration > Identifier': node => {},
})); Defensive patterns
Strategy: validation
Validate before calling
import esquery from 'esquery';
function isValidSelector(rawSelector: string): boolean {
try {
esquery.parse(rawSelector.replace(/:exit$/u, ''));
return true;
} catch {
return false;
}
}
const bad = Object.keys(selectors).filter(s => !isValidSelector(s));
if (bad.length > 0) throw new Error(`invalid selectors: ${bad.join(', ')}`); Try / catch
try {
traverseWithContext(code, ast, scope, () => ({}), visitors);
} catch (e) {
if (e instanceof SyntaxError && /selector/.test(e.message)) {
// a visitor key is malformed - the message includes the offset into the selector
}
throw e;
} Prevention
- Unit-test every visitor key with esquery.parse before shipping codemods.
- Build dynamic selectors from validated fragments; never interpolate unvalidated input.
- Remember ':exit' is stripped before parsing and must be the trailing suffix.
When it happens
Trigger: A visitor key that is not valid esquery: unbalanced brackets (`'CallExpression >'`), a bare `':exit'` (empty after stripping), invalid pseudos (`':nth-child()'` without an argument), stray characters, or dynamically built selector strings that are malformed.
Common situations: Programmatically composing selectors (template strings with missing parts); typos in `:exit` (`':exi'`); using CSS features esquery does not support; quotes lost when selectors pass through JSON configs.
Related errors
- Unexpected selector
- Attempted to insert a statement into
- Cannot perform a remove mutation on node of type
- Could not find target in array of
- Could not find target in array of
AI-assisted analysis of facebook/flow@f88ac94bcf (2026-08-20).
Data as JSON: /api/errors/93c8429ae2b8d3e3.
Report an issue: GitHub.
Appendix: source
Thrown at packages/flow-transform/src/traverse/NodeEventGenerator.js:235
);
}
/**
* Parses a raw selector string, and throws a useful error if parsing fails.
* @param rawSelector A raw AST selector
* @returns An object (from esquery) describing the matching behavior of this selector
* @throws An error if the selector is invalid
*/
function tryParseSelector(rawSelector: string): Selector {
try {
return esquery.parse(rawSelector.replace(/:exit$/u, ''));
} catch (err) {
if (
err.location &&
err.location.start &&
typeof err.location.start.offset === 'number'
) {
throw new SyntaxError(
`Syntax error in selector "${rawSelector}" at position ${err.location.start.offset}: ${err.message}`,
);
}
throw err;
}
}
const selectorCache = new Map<string, ParsedSelector>();
/**
* Parses a raw selector string, and returns the parsed selector along with specificity and type information.
* @param rawSelector A raw AST selector
* @returns A selector descriptor
*/
function parseSelector(rawSelector: string): ParsedSelector {
const cachedSelector = selectorCache.get(rawSelector);
if (cachedSelector) {
return cachedSelector;View on GitHub (pinned to f88ac94bcf)