1/** 2 * @fileoverview Traverser to traverse AST trees. 3 * @author Nicholas C. Zakas 4 * @author Toru Nagashima 5 */ 6"use strict"; 7 8//------------------------------------------------------------------------------ 9// Requirements 10//------------------------------------------------------------------------------ 11 12const vk = require("eslint-visitor-keys"); 13const debug = require("debug")("eslint:traverser"); 14 15//------------------------------------------------------------------------------ 16// Helpers 17//------------------------------------------------------------------------------ 18 19/** 20 * Do nothing. 21 * @returns {void} 22 */ 23function noop() { 24 25 // do nothing. 26} 27 28/** 29 * Check whether the given value is an ASTNode or not. 30 * @param {any} x The value to check. 31 * @returns {boolean} `true` if the value is an ASTNode. 32 */ 33function isNode(x) { 34 return x !== null && typeof x === "object" && typeof x.type === "string"; 35} 36 37/** 38 * Get the visitor keys of a given node. 39 * @param {Object} visitorKeys The map of visitor keys. 40 * @param {ASTNode} node The node to get their visitor keys. 41 * @returns {string[]} The visitor keys of the node. 42 */ 43function getVisitorKeys(visitorKeys, node) { 44 let keys = visitorKeys[node.type]; 45 46 if (!keys) { 47 keys = vk.getKeys(node); 48 debug("Unknown node type \"%s\": Estimated visitor keys %j", node.type, keys); 49 } 50 51 return keys; 52} 53 54/** 55 * The traverser class to traverse AST trees. 56 */ 57class Traverser { 58 constructor() { 59 this._current = null; 60 this._parents = []; 61 this._skipped = false; 62 this._broken = false; 63 this._visitorKeys = null; 64 this._enter = null; 65 this._leave = null; 66 } 67 68 // eslint-disable-next-line jsdoc/require-description 69 /** 70 * @returns {ASTNode} The current node. 71 */ 72 current() { 73 return this._current; 74 } 75 76 // eslint-disable-next-line jsdoc/require-description 77 /** 78 * @returns {ASTNode[]} The ancestor nodes. 79 */ 80 parents() { 81 return this._parents.slice(0); 82 } 83 84 /** 85 * Break the current traversal. 86 * @returns {void} 87 */ 88 break() { 89 this._broken = true; 90 } 91 92 /** 93 * Skip child nodes for the current traversal. 94 * @returns {void} 95 */ 96 skip() { 97 this._skipped = true; 98 } 99 100 /** 101 * Traverse the given AST tree. 102 * @param {ASTNode} node The root node to traverse. 103 * @param {Object} options The option object. 104 * @param {Object} [options.visitorKeys=DEFAULT_VISITOR_KEYS] The keys of each node types to traverse child nodes. Default is `./default-visitor-keys.json`. 105 * @param {Function} [options.enter=noop] The callback function which is called on entering each node. 106 * @param {Function} [options.leave=noop] The callback function which is called on leaving each node. 107 * @returns {void} 108 */ 109 traverse(node, options) { 110 this._current = null; 111 this._parents = []; 112 this._skipped = false; 113 this._broken = false; 114 this._visitorKeys = options.visitorKeys || vk.KEYS; 115 this._enter = options.enter || noop; 116 this._leave = options.leave || noop; 117 this._traverse(node, null); 118 } 119 120 /** 121 * Traverse the given AST tree recursively. 122 * @param {ASTNode} node The current node. 123 * @param {ASTNode|null} parent The parent node. 124 * @returns {void} 125 * @private 126 */ 127 _traverse(node, parent) { 128 if (!isNode(node)) { 129 return; 130 } 131 132 this._current = node; 133 this._skipped = false; 134 this._enter(node, parent); 135 136 if (!this._skipped && !this._broken) { 137 const keys = getVisitorKeys(this._visitorKeys, node); 138 139 if (keys.length >= 1) { 140 this._parents.push(node); 141 for (let i = 0; i < keys.length && !this._broken; ++i) { 142 const child = node[keys[i]]; 143 144 if (Array.isArray(child)) { 145 for (let j = 0; j < child.length && !this._broken; ++j) { 146 this._traverse(child[j], node); 147 } 148 } else { 149 this._traverse(child, node); 150 } 151 } 152 this._parents.pop(); 153 } 154 } 155 156 if (!this._broken) { 157 this._leave(node, parent); 158 } 159 160 this._current = parent; 161 } 162 163 /** 164 * Calculates the keys to use for traversal. 165 * @param {ASTNode} node The node to read keys from. 166 * @returns {string[]} An array of keys to visit on the node. 167 * @private 168 */ 169 static getKeys(node) { 170 return vk.getKeys(node); 171 } 172 173 /** 174 * Traverse the given AST tree. 175 * @param {ASTNode} node The root node to traverse. 176 * @param {Object} options The option object. 177 * @param {Object} [options.visitorKeys=DEFAULT_VISITOR_KEYS] The keys of each node types to traverse child nodes. Default is `./default-visitor-keys.json`. 178 * @param {Function} [options.enter=noop] The callback function which is called on entering each node. 179 * @param {Function} [options.leave=noop] The callback function which is called on leaving each node. 180 * @returns {void} 181 */ 182 static traverse(node, options) { 183 new Traverser().traverse(node, options); 184 } 185 186 /** 187 * The default visitor keys. 188 * @type {Object} 189 */ 190 static get DEFAULT_VISITOR_KEYS() { 191 return vk.KEYS; 192 } 193} 194 195module.exports = Traverser; 196