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.
195 lines
5.4 KiB
195 lines
5.4 KiB
/** |
|
* @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 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;
|
|
|