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
3 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;
|