]>
Commit | Line | Data |
---|---|---|
eb39fafa DC |
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 | ||
12 | const vk = require("eslint-visitor-keys"); | |
13 | const debug = require("debug")("eslint:traverser"); | |
14 | ||
15 | //------------------------------------------------------------------------------ | |
16 | // Helpers | |
17 | //------------------------------------------------------------------------------ | |
18 | ||
19 | /** | |
20 | * Do nothing. | |
21 | * @returns {void} | |
22 | */ | |
23 | function 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 | */ | |
33 | function 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 | */ | |
43 | function 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 | */ | |
57 | class 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 | ||
eb39fafa | 68 | /** |
609c276f | 69 | * Gives current node. |
eb39fafa DC |
70 | * @returns {ASTNode} The current node. |
71 | */ | |
72 | current() { | |
73 | return this._current; | |
74 | } | |
75 | ||
eb39fafa | 76 | /** |
609c276f | 77 | * Gives a a copy of the ancestor nodes. |
eb39fafa DC |
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 | ||
195 | module.exports = Traverser; |