You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

196 lines
5.4 KiB

/**
* @fileoverview Traverser to traverse AST trees.
* @author Nicholas C. Zakas
* @author Toru Nagashima
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const vk = require("eslint-visitor-keys");
const debug = require("debug")("eslint:traverser");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Do nothing.
* @returns {void}
*/
function noop() {
// do nothing.
}
/**
* Check whether the given value is an ASTNode or not.
* @param {any} x The value to check.
* @returns {boolean} `true` if the value is an ASTNode.
*/
function isNode(x) {
return x !== null && typeof x === "object" && typeof x.type === "string";
}
/**
* Get the visitor keys of a given node.
* @param {Object} visitorKeys The map of visitor keys.
* @param {ASTNode} node The node to get their visitor keys.
* @returns {string[]} The visitor keys of the node.
*/
function getVisitorKeys(visitorKeys, node) {
let keys = visitorKeys[node.type];
if (!keys) {
keys = vk.getKeys(node);
debug("Unknown node type \"%s\": Estimated visitor keys %j", node.type, keys);
}
return keys;
}
/**
* The traverser class to traverse AST trees.
*/
class Traverser {
constructor() {
this._current = null;
this._parents = [];
this._skipped = false;
this._broken = false;
this._visitorKeys = null;
this._enter = null;
this._leave = null;
}
/**
* Gives current node.
* @returns {ASTNode} The current node.
*/
current() {
return this._current;
}
/**
* Gives a copy of the ancestor nodes.
* @returns {ASTNode[]} The ancestor nodes.
*/
parents() {
return this._parents.slice(0);
}
/**
* Break the current traversal.
* @returns {void}
*/
break() {
this._broken = true;
}
/**
* Skip child nodes for the current traversal.
* @returns {void}
*/
skip() {
this._skipped = true;
}
/**
* Traverse the given AST tree.
* @param {ASTNode} node The root node to traverse.
* @param {Object} options The option object.
* @param {Object} [options.visitorKeys=DEFAULT_VISITOR_KEYS] The keys of each node types to traverse child nodes. Default is `./default-visitor-keys.json`.
* @param {Function} [options.enter=noop] The callback function which is called on entering each node.
* @param {Function} [options.leave=noop] The callback function which is called on leaving each node.
* @returns {void}
*/
traverse(node, options) {
this._current = null;
this._parents = [];
this._skipped = false;
this._broken = false;
this._visitorKeys = options.visitorKeys || vk.KEYS;
this._enter = options.enter || noop;
this._leave = options.leave || noop;
this._traverse(node, null);
}
/**
* Traverse the given AST tree recursively.
* @param {ASTNode} node The current node.
* @param {ASTNode|null} parent The parent node.
* @returns {void}
* @private
*/
_traverse(node, parent) {
if (!isNode(node)) {
return;
}
this._current = node;
this._skipped = false;
this._enter(node, parent);
if (!this._skipped && !this._broken) {
const keys = getVisitorKeys(this._visitorKeys, node);
if (keys.length >= 1) {
this._parents.push(node);
for (let i = 0; i < keys.length && !this._broken; ++i) {
const child = node[keys[i]];
if (Array.isArray(child)) {
for (let j = 0; j < child.length && !this._broken; ++j) {
this._traverse(child[j], node);
}
} else {
this._traverse(child, node);
}
}
this._parents.pop();
}
}
if (!this._broken) {
this._leave(node, parent);
}
this._current = parent;
}
/**
* Calculates the keys to use for traversal.
* @param {ASTNode} node The node to read keys from.
* @returns {string[]} An array of keys to visit on the node.
* @private
*/
static getKeys(node) {
return vk.getKeys(node);
}
/**
* Traverse the given AST tree.
* @param {ASTNode} node The root node to traverse.
* @param {Object} options The option object.
* @param {Object} [options.visitorKeys=DEFAULT_VISITOR_KEYS] The keys of each node types to traverse child nodes. Default is `./default-visitor-keys.json`.
* @param {Function} [options.enter=noop] The callback function which is called on entering each node.
* @param {Function} [options.leave=noop] The callback function which is called on leaving each node.
* @returns {void}
*/
static traverse(node, options) {
new Traverser().traverse(node, options);
}
/**
* The default visitor keys.
* @type {Object}
*/
static get DEFAULT_VISITOR_KEYS() {
return vk.KEYS;
}
}
module.exports = Traverser;