196 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			196 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * @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;
 |