ESLint Visitor Keys provides constants and utilities for traversing JavaScript Abstract Syntax Trees (AST). It defines which properties of AST nodes contain child nodes, enabling systematic traversal of the syntax tree for static analysis tools.
import { getKeys, unionWith, KEYS } from "eslint-visitor-keys";For CommonJS:
const { getKeys, unionWith, KEYS } = require("eslint-visitor-keys");Default import:
import * as visitorKeys from "eslint-visitor-keys";import * as espree from "espree";
import { getKeys, KEYS } from "eslint-visitor-keys";
// Parse JavaScript code
const ast = espree.parse(`
function test(x) {
if (x > 0) {
return x + 1;
}
return 0;
}
`, { ecmaVersion: 2022 });
// Get visitor keys for the Program node
const programKeys = getKeys(ast);
console.log(programKeys); // ["body"]
// Get keys for a function declaration
const functionDecl = ast.body[0];
const functionKeys = getKeys(functionDecl);
console.log(functionKeys); // ["id", "params", "body"]
// Access complete visitor keys mapping
console.log(KEYS.FunctionDeclaration); // ["id", "params", "body"]
console.log(KEYS.IfStatement); // ["test", "consequent", "alternate"]Get visitor keys for individual AST nodes with automatic filtering of metadata properties.
/**
* Get visitor keys for an AST node
* @param {Object} node - AST node
* @returns {readonly string[]} Array of property names that contain child nodes
*/
function getKeys(node);Merge additional visitor keys with the default set for custom AST node types.
/**
* Merge additional keys with default visitor keys
* @param {VisitorKeys} additionalKeys - Additional visitor key mappings
* @returns {VisitorKeys} Combined visitor keys
*/
function unionWith(additionalKeys);Access to the complete mapping of AST node types to their child node properties.
/** Complete mapping of AST node types to their child node keys */
const KEYS: VisitorKeys;
interface VisitorKeys {
readonly [nodeType: string]: readonly string[];
}The visitor keys cover all standard ECMAScript AST node types and common extensions:
// Program and statements
Program: ["body"];
BlockStatement: ["body"];
ExpressionStatement: ["expression"];
EmptyStatement: [];
DebuggerStatement: [];
// Declarations
FunctionDeclaration: ["id", "params", "body"];
VariableDeclaration: ["declarations"];
VariableDeclarator: ["id", "init"];
// Control flow
IfStatement: ["test", "consequent", "alternate"];
SwitchStatement: ["discriminant", "cases"];
SwitchCase: ["test", "consequent"];
WhileStatement: ["test", "body"];
DoWhileStatement: ["body", "test"];
ForStatement: ["init", "test", "update", "body"];
ForInStatement: ["left", "right", "body"];// Arrow functions and classes
ArrowFunctionExpression: ["params", "body"];
ClassDeclaration: ["id", "superClass", "body"];
ClassExpression: ["id", "superClass", "body"];
ClassBody: ["body"];
MethodDefinition: ["key", "value"];
// Destructuring and spread
ObjectPattern: ["properties"];
ArrayPattern: ["elements"];
RestElement: ["argument"];
SpreadElement: ["argument"];
// Modules
ImportDeclaration: ["specifiers", "source"];
ImportSpecifier: ["imported", "local"];
ImportDefaultSpecifier: ["local"];
ImportNamespaceSpecifier: ["local"];
ExportNamedDeclaration: ["declaration", "specifiers", "source"];
ExportDefaultDeclaration: ["declaration"];
ExportAllDeclaration: ["source"];// ES2017+
AwaitExpression: ["argument"];
AsyncFunctionDeclaration: ["id", "params", "body"];
AsyncFunctionExpression: ["id", "params", "body"];
// ES2018+
ForOfStatement: ["left", "right", "body"];
ObjectExpression: ["properties"];
Property: ["key", "value"];
// ES2020+
ChainExpression: ["expression"];
ImportExpression: ["source"];
OptionalCallExpression: ["callee", "arguments"];
OptionalMemberExpression: ["object", "property"];
// ES2021+
PrivateIdentifier: [];
// ES2022+
StaticBlock: ["body"];
PropertyDefinition: ["key", "value"];// JSX nodes
JSXElement: ["openingElement", "children", "closingElement"];
JSXFragment: ["openingFragment", "children", "closingFragment"];
JSXOpeningElement: ["name", "attributes"];
JSXClosingElement: ["name"];
JSXAttribute: ["name", "value"];
JSXSpreadAttribute: ["argument"];
JSXExpressionContainer: ["expression"];
JSXText: [];// TypeScript-specific nodes
TSTypeAnnotation: ["typeAnnotation"];
TSTypeParameterDeclaration: ["params"];
TSTypeParameter: ["constraint", "default"];
TSInterfaceDeclaration: ["id", "typeParameters", "extends", "body"];
TSTypeAliasDeclaration: ["id", "typeParameters", "typeAnnotation"];Basic AST traversal:
import * as espree from "espree";
import { getKeys } from "eslint-visitor-keys";
function traverse(node, visitor) {
if (typeof visitor[node.type] === "function") {
visitor[node.type](node);
}
const keys = getKeys(node);
for (const key of keys) {
const child = node[key];
if (Array.isArray(child)) {
for (const item of child) {
if (item && typeof item === "object") {
traverse(item, visitor);
}
}
} else if (child && typeof child === "object") {
traverse(child, visitor);
}
}
}
const ast = espree.parse("function test() { return 42; }", { ecmaVersion: 2022 });
traverse(ast, {
FunctionDeclaration(node) {
console.log(`Found function: ${node.id.name}`);
},
ReturnStatement(node) {
console.log("Found return statement");
}
});Custom visitor keys:
import { unionWith, KEYS } from "eslint-visitor-keys";
// Add custom AST node types
const customKeys = unionWith({
CustomNode: ["customProperty", "children"],
AnotherCustomNode: ["value", "metadata"]
});
console.log(customKeys.CustomNode); // ["customProperty", "children"]
console.log(customKeys.FunctionDeclaration); // ["id", "params", "body"] (from default keys)Filtering metadata properties:
import { getKeys } from "eslint-visitor-keys";
const node = {
type: "Identifier",
name: "foo",
parent: null, // Filtered out
range: [0, 3], // Filtered out
loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 3 } }, // Filtered out
leadingComments: [], // Filtered out
trailingComments: [], // Filtered out
_custom: "data" // Filtered out (starts with underscore)
};
const keys = getKeys(node);
console.log(keys); // [] (no child node properties for Identifier)
// Compare with raw Object.keys()
console.log(Object.keys(node)); // ["type", "name", "parent", "range", "loc", "leadingComments", "trailingComments", "_custom"]Working with complex expressions:
import * as espree from "espree";
import { getKeys } from "eslint-visitor-keys";
const ast = espree.parse("obj?.prop?.method?.(arg1, arg2)", {
ecmaVersion: 2022,
sourceType: "module"
});
function printNodeStructure(node, depth = 0) {
const indent = " ".repeat(depth);
console.log(`${indent}${node.type}`);
const keys = getKeys(node);
for (const key of keys) {
const child = node[key];
console.log(`${indent} ${key}:`);
if (Array.isArray(child)) {
child.forEach((item, index) => {
if (item && typeof item === "object") {
console.log(`${indent} [${index}]:`);
printNodeStructure(item, depth + 3);
}
});
} else if (child && typeof child === "object") {
printNodeStructure(child, depth + 2);
} else {
console.log(`${indent} ${child}`);
}
}
}
printNodeStructure(ast.body[0].expression);
// Output shows the structure of the optional chaining expressionESLint rule integration:
// Example ESLint rule using visitor keys
module.exports = {
create(context) {
function checkNode(node) {
const keys = getKeys(node);
// Process each child node
keys.forEach(key => {
const child = node[key];
if (Array.isArray(child)) {
child.forEach(item => {
if (item && typeof item === "object") {
// Process child node
checkNode(item);
}
});
} else if (child && typeof child === "object") {
checkNode(child);
}
});
}
return {
Program(node) {
checkNode(node);
}
};
}
};The getKeys() function automatically filters out metadata properties that are not part of the AST structure:
parent - Parent node referenceleadingComments - Comments before the nodetrailingComments - Comments after the node_ - Private/internal propertiesloc, range, start, end) - Position metadataeslint-visitor-keys includes TypeScript type definitions:
import { getKeys, unionWith, KEYS, VisitorKeys } from "eslint-visitor-keys";
// Type definitions are included
const keys: readonly string[] = getKeys(node);
const customKeys: VisitorKeys = unionWith({ CustomNode: ["children"] });
const functionKeys: readonly string[] = KEYS.FunctionDeclaration;getKeys() performs minimal runtime filteringKEYS object provides O(1) lookup for known node typesunionWith() are merged efficientlyeslint-visitor-keys integrates seamlessly with popular AST processing tools: