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
			
		
		
			
		
	
	
					196 lines
				
				5.4 KiB
			| 
								 
											4 years ago
										 
									 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * @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;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // eslint-disable-next-line jsdoc/require-description
							 | 
						||
| 
								 | 
							
								    /**
							 | 
						||
| 
								 | 
							
								     * @returns {ASTNode} The current node.
							 | 
						||
| 
								 | 
							
								     */
							 | 
						||
| 
								 | 
							
								    current() {
							 | 
						||
| 
								 | 
							
								        return this._current;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // eslint-disable-next-line jsdoc/require-description
							 | 
						||
| 
								 | 
							
								    /**
							 | 
						||
| 
								 | 
							
								     * @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;
							 |