/** * @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; } /** * @returns {ASTNode} The current node. */ current() { return this._current; } /** * @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;