facebook/flow · error · Error

No visitor keys found for node type "${node.type}".

Error message

No visitor keys found for node type "${node.type}".

What it means

NodeEventGenerator configures esquery with FlowVisitorKeys and a fallback that throws when traversal reaches a node whose type has no visitor-keys entry. The error means the AST being traversed contains a node type the Flow visitor-key table does not know, so esquery cannot enumerate the node's children. It almost always indicates an AST produced by something other than the matching Flow parser version (Babel/TypeScript ASTs, hand-built nodes, or a flow-ast version out of sync with flow-transform).

Source

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

type ParsedSelector = Readonly<{
  /** The string that was parsed into this selector */
  rawSelector: string,
  /** `true` if this should be emitted when exiting the node rather than when entering */
  isExit: boolean,
  /** An object (from esquery) describing the matching behavior of the selector */
  parsedSelector: Selector,
  /** A list of node types that could possibly cause the selector to match, or `null` if all node types could cause a match */
  listenerTypes: ?Array<ESNode['type']>,
  /** The total number of classes, pseudo-classes, and attribute queries in this selector */
  attributeCount: number,
  /** The total number of identifier queries in this selector */
  identifierCount: number,
}>;

const ESQUERY_OPTIONS: ESQueryOptions = Object.freeze({
  visitorKeys: FlowVisitorKeys,
  fallback: (node: ESNode) => {
    throw new Error(`No visitor keys found for node type "${node.type}".`);
  },
});

/**
 * Computes the union of one or more arrays
 * @param arrays One or more arrays to union
 * @returns The union of the input arrays
 */
function union<T>(...arrays: Array<Array<T>>): Array<T> {
  return [...new Set(arrays.flat())];
}

/**
 * Computes the intersection of one or more arrays
 * @param arrays One or more arrays to intersect
 * @returns The intersection of the input arrays
 */
function intersection<T>(...arrays: Array<Array<T>>): Array<T> {

View on GitHub (pinned to d1341dac89)

Solutions

  1. Parse the source with the Flow parser that pairs with your flow-transform version before traversing.
  2. Align versions: make flow-ast/flow-parser and flow-transform come from the same release so FlowVisitorKeys covers every emitted node type.
  3. If you inject custom nodes, strip or replace them before calling traverse().
  4. Log node.type in a pre-pass and compare against FlowVisitorKeys to identify the offending node.

Example fix

// before
import {parse} from '@babel/parser';
const ast = parse(code, {sourceType: 'module'});
traverse(ast, visitor); // Babel AST -> unknown node types

// after
import {parse} from 'flow-parser';
const ast = parse(code);
traverse(ast, visitor); // Flow AST matches FlowVisitorKeys
Defensive patterns

Strategy: validation

Validate before calling

import {FlowVisitorKeys} from 'flow-ast';

function astOnlyHasKnownNodeTypes(ast) {
  const unknown = new Set();
  (function walk(node) {
    if (node == null || typeof node.type !== 'string') return;
    if (!(node.type in FlowVisitorKeys)) unknown.add(node.type);
    for (const key of FlowVisitorKeys[node.type] ?? []) {
      const child = node[key];
      if (Array.isArray(child)) child.forEach(walk);
      else if (child != null && typeof child.type === 'string') walk(child);
    }
  })(ast);
  return unknown.size === 0 ? null : [...unknown];
}

Type guard

/** True when the node type is traversable by flow-transform (present in FlowVisitorKeys). */
import {FlowVisitorKeys} from 'flow-ast';

function isKnownNodeType(node) {
  return node != null && typeof node.type === 'string' && node.type in FlowVisitorKeys;
}

Try / catch

try {
  traverse(ast, visitor);
} catch (err) {
  if (err instanceof Error && err.message.startsWith('No visitor keys found')) {
    // AST contains a non-Flow node; reparse with the Flow parser before traversing
    ast = flowParse(originalSource);
    traverse(ast, visitor);
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: Calling traverse() on a Babel or TypeScript AST (e.g. TSTypeAnnotation, TSAsExpression nodes) instead of one parsed by the Flow parser; traversing an AST that contains custom synthetic nodes injected by another tool; using a flow-ast package version whose visitor keys lack types that flow-transform's traversal expects.

Common situations: Sharing codemod code between Babel and Flow projects and accidentally passing a Babel AST into flow-transform's traverse; upgrading one of flow-ast/flow-parser/flow-transform independently so the visitor-key table and emitted node types drift; inserting custom metadata nodes into the tree before traversal.

Related errors


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