facebook/flow · error · Error

Unexpected selector ${parsedSelector.value}

Error message

Unexpected selector ${parsedSelector.value}

What it means

getPossibleTypes() in NodeEventGenerator resolves an identifier-style selector (a bare node-type name such as 'CallExpression') against FlowVisitorKeys; if the name is not a key in that table it throws. In practice the selector comes from the event names your visitor registers, i.e. a visitor key like visitFooBar becomes the selector 'FooBar'. The error therefore means you registered a visitor for a node type that does not exist in the Flow AST vocabulary — usually a TypeScript/Babel-only type or a typo.

Source

Thrown at packages/flow-transform/src/traverse/NodeEventGenerator.js:78

  let result = [...new Set(arrays[0])];

  for (const array of arrays.slice(1)) {
    result = result.filter(x => array.includes(x));
  }
  return result;
}

/**
 * Gets the possible types of a selector
 * @param parsedSelector An object (from esquery) describing the matching behavior of the selector
 * @returns The node types that could possibly trigger this selector, or `null` if all node types could trigger it
 */
function getPossibleTypes(parsedSelector: Selector): ?Array<ESNode['type']> {
  switch (parsedSelector.type) {
    case 'identifier':
      if (!(parsedSelector.value in FlowVisitorKeys)) {
        throw new Error(`Unexpected selector ${parsedSelector.value}`);
      }
      // $FlowExpectedError[incompatible-type]
      return [parsedSelector.value];

    case 'matches': {
      const typesForComponents = parsedSelector.selectors.map(getPossibleTypes);
      const typesForComponentsNonNull = typesForComponents.filter(Boolean);

      if (typesForComponents.length === typesForComponentsNonNull.length) {
        return union(...typesForComponentsNonNull);
      }
      return null;
    }

    case 'compound': {
      const typesForComponents = parsedSelector.selectors
        .map(getPossibleTypes)
        .filter(Boolean);

View on GitHub (pinned to d1341dac89)

Solutions

  1. Use exact Flow AST node type names (see flow-ast's FlowVisitorKeys), e.g. visitTSTypeAnnotation -> visitTypeCastExpression or the Flow equivalent; StringLiteral -> Literal.
  2. Validate each visitor key's node type against FlowVisitorKeys before registering the visitor.
  3. For misspellings, cross-check the name against the Flow parser output (Object.keys on a parsed node's types).
  4. If you need TypeScript AST support, use a TS-aware toolchain instead of flow-transform.

Example fix

// before
const visitor = {
  visitTSTypeAnnotation(node) {}, // 'TSTypeAnnotation' not in FlowVisitorKeys
};

// after
const visitor = {
  visitTypeAnnotation(node) {}, // Flow AST type name
};
Defensive patterns

Strategy: type-guard

Validate before calling

import {FlowVisitorKeys} from 'flow-ast';

function validateVisitorKeys(visitor) {
  const invalid = Object.keys(visitor)
    .filter(k => k.startsWith('visit'))
    .map(k => k.slice('visit'.length))
    .filter(type => !(type in FlowVisitorKeys));
  if (invalid.length > 0) {
    throw new Error(`Unknown Flow node types in visitor: ${invalid.join(', ')}`);
  }
}

Type guard

/** True when `type` is a real Flow AST node type usable as a visitor selector. */
import {FlowVisitorKeys} from 'flow-ast';

function isFlowNodeType(type) {
  return typeof type === 'string' && type in FlowVisitorKeys;
}

Try / catch

try {
  traverse(ast, visitor);
} catch (err) {
  if (err instanceof Error && err.message.startsWith('Unexpected selector')) {
    // a visitor key names a non-Flow node type; fix the key names
    console.error('bad visitor key, not a Flow node type:', err.message);
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: Defining a visitor with a key for a non-Flow node type, e.g. visitTSTypeAnnotation() or visitClassPrivateProperty(), which parses to identifier selector 'TSTypeAnnotation' that is absent from FlowVisitorKeys; misspelling a real type such as visitCallExpresion(); using a selector like 'StringLiteral' (Babel naming) instead of Flow's 'Literal'.

Common situations: Porting an ESLint/Babel codemod to flow-transform and keeping TS/Babel type names; renaming refactors that break a visitor key; copy-pasting selectors from documentation for a different parser.

Related errors


AI-assisted analysis of facebook/flow@d1341dac89 (2026-08-17). Data as JSON: /api/errors/51c2aa16635bdfa8. Report an issue: GitHub.