UNPKG

5.53 kBJavaScriptView Raw
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;