1 | /**
|
2 | * @fileoverview Traverser to traverse AST trees.
|
3 | * @author Nicholas C. Zakas
|
4 | * @author Toru Nagashima
|
5 | */
|
6 | ;
|
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 |
|
68 | /**
|
69 | * @returns {ASTNode} The current node.
|
70 | */
|
71 | current() {
|
72 | return this._current;
|
73 | }
|
74 |
|
75 | /**
|
76 | * @returns {ASTNode[]} The ancestor nodes.
|
77 | */
|
78 | parents() {
|
79 | return this._parents.slice(0);
|
80 | }
|
81 |
|
82 | /**
|
83 | * Break the current traversal.
|
84 | * @returns {void}
|
85 | */
|
86 | break() {
|
87 | this._broken = true;
|
88 | }
|
89 |
|
90 | /**
|
91 | * Skip child nodes for the current traversal.
|
92 | * @returns {void}
|
93 | */
|
94 | skip() {
|
95 | this._skipped = true;
|
96 | }
|
97 |
|
98 | /**
|
99 | * Traverse the given AST tree.
|
100 | * @param {ASTNode} node The root node to traverse.
|
101 | * @param {Object} options The option object.
|
102 | * @param {Object} [options.visitorKeys=DEFAULT_VISITOR_KEYS] The keys of each node types to traverse child nodes. Default is `./default-visitor-keys.json`.
|
103 | * @param {Function} [options.enter=noop] The callback function which is called on entering each node.
|
104 | * @param {Function} [options.leave=noop] The callback function which is called on leaving each node.
|
105 | * @returns {void}
|
106 | */
|
107 | traverse(node, options) {
|
108 | this._current = null;
|
109 | this._parents = [];
|
110 | this._skipped = false;
|
111 | this._broken = false;
|
112 | this._visitorKeys = options.visitorKeys || vk.KEYS;
|
113 | this._enter = options.enter || noop;
|
114 | this._leave = options.leave || noop;
|
115 | this._traverse(node, null);
|
116 | }
|
117 |
|
118 | /**
|
119 | * Traverse the given AST tree recursively.
|
120 | * @param {ASTNode} node The current node.
|
121 | * @param {ASTNode|null} parent The parent node.
|
122 | * @returns {void}
|
123 | * @private
|
124 | */
|
125 | _traverse(node, parent) {
|
126 | if (!isNode(node)) {
|
127 | return;
|
128 | }
|
129 |
|
130 | this._current = node;
|
131 | this._skipped = false;
|
132 | this._enter(node, parent);
|
133 |
|
134 | if (!this._skipped && !this._broken) {
|
135 | const keys = getVisitorKeys(this._visitorKeys, node);
|
136 |
|
137 | if (keys.length >= 1) {
|
138 | this._parents.push(node);
|
139 | for (let i = 0; i < keys.length && !this._broken; ++i) {
|
140 | const child = node[keys[i]];
|
141 |
|
142 | if (Array.isArray(child)) {
|
143 | for (let j = 0; j < child.length && !this._broken; ++j) {
|
144 | this._traverse(child[j], node);
|
145 | }
|
146 | } else {
|
147 | this._traverse(child, node);
|
148 | }
|
149 | }
|
150 | this._parents.pop();
|
151 | }
|
152 | }
|
153 |
|
154 | if (!this._broken) {
|
155 | this._leave(node, parent);
|
156 | }
|
157 |
|
158 | this._current = parent;
|
159 | }
|
160 |
|
161 | /**
|
162 | * Calculates the keys to use for traversal.
|
163 | * @param {ASTNode} node The node to read keys from.
|
164 | * @returns {string[]} An array of keys to visit on the node.
|
165 | * @private
|
166 | */
|
167 | static getKeys(node) {
|
168 | return vk.getKeys(node);
|
169 | }
|
170 |
|
171 | /**
|
172 | * Traverse the given AST tree.
|
173 | * @param {ASTNode} node The root node to traverse.
|
174 | * @param {Object} options The option object.
|
175 | * @param {Object} [options.visitorKeys=DEFAULT_VISITOR_KEYS] The keys of each node types to traverse child nodes. Default is `./default-visitor-keys.json`.
|
176 | * @param {Function} [options.enter=noop] The callback function which is called on entering each node.
|
177 | * @param {Function} [options.leave=noop] The callback function which is called on leaving each node.
|
178 | * @returns {void}
|
179 | */
|
180 | static traverse(node, options) {
|
181 | new Traverser().traverse(node, options);
|
182 | }
|
183 |
|
184 | /**
|
185 | * The default visitor keys.
|
186 | * @type {Object}
|
187 | */
|
188 | static get DEFAULT_VISITOR_KEYS() {
|
189 | return vk.KEYS;
|
190 | }
|
191 | }
|
192 |
|
193 | module.exports = Traverser;
|