| /** |
| * @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; |