UNPKG

5.41 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 /**
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
193module.exports = Traverser;