• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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;
196