traverser.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. /**
  2. * @fileoverview Traverser to traverse AST trees.
  3. * @author Nicholas C. Zakas
  4. * @author Toru Nagashima
  5. */
  6. "use strict";
  7. //------------------------------------------------------------------------------
  8. // Requirements
  9. //------------------------------------------------------------------------------
  10. const vk = require("eslint-visitor-keys");
  11. const debug = require("debug")("eslint:traverser");
  12. //------------------------------------------------------------------------------
  13. // Helpers
  14. //------------------------------------------------------------------------------
  15. /**
  16. * Do nothing.
  17. * @returns {void}
  18. */
  19. function noop() {
  20. // do nothing.
  21. }
  22. /**
  23. * Check whether the given value is an ASTNode or not.
  24. * @param {any} x The value to check.
  25. * @returns {boolean} `true` if the value is an ASTNode.
  26. */
  27. function isNode(x) {
  28. return x !== null && typeof x === "object" && typeof x.type === "string";
  29. }
  30. /**
  31. * Get the visitor keys of a given node.
  32. * @param {Object} visitorKeys The map of visitor keys.
  33. * @param {ASTNode} node The node to get their visitor keys.
  34. * @returns {string[]} The visitor keys of the node.
  35. */
  36. function getVisitorKeys(visitorKeys, node) {
  37. let keys = visitorKeys[node.type];
  38. if (!keys) {
  39. keys = vk.getKeys(node);
  40. debug(
  41. 'Unknown node type "%s": Estimated visitor keys %j',
  42. node.type,
  43. keys,
  44. );
  45. }
  46. return keys;
  47. }
  48. /**
  49. * The traverser class to traverse AST trees.
  50. */
  51. class Traverser {
  52. constructor() {
  53. this._current = null;
  54. this._parents = [];
  55. this._skipped = false;
  56. this._broken = false;
  57. this._visitorKeys = null;
  58. this._enter = null;
  59. this._leave = null;
  60. }
  61. /**
  62. * Gives current node.
  63. * @returns {ASTNode} The current node.
  64. */
  65. current() {
  66. return this._current;
  67. }
  68. /**
  69. * Gives a copy of the ancestor nodes.
  70. * @returns {ASTNode[]} The ancestor nodes.
  71. */
  72. parents() {
  73. return this._parents.slice(0);
  74. }
  75. /**
  76. * Break the current traversal.
  77. * @returns {void}
  78. */
  79. break() {
  80. this._broken = true;
  81. }
  82. /**
  83. * Skip child nodes for the current traversal.
  84. * @returns {void}
  85. */
  86. skip() {
  87. this._skipped = true;
  88. }
  89. /**
  90. * Traverse the given AST tree.
  91. * @param {ASTNode} node The root node to traverse.
  92. * @param {Object} options The option object.
  93. * @param {Object} [options.visitorKeys=DEFAULT_VISITOR_KEYS] The keys of each node types to traverse child nodes. Default is `./default-visitor-keys.json`.
  94. * @param {Function} [options.enter=noop] The callback function which is called on entering each node.
  95. * @param {Function} [options.leave=noop] The callback function which is called on leaving each node.
  96. * @returns {void}
  97. */
  98. traverse(node, options) {
  99. this._current = null;
  100. this._parents = [];
  101. this._skipped = false;
  102. this._broken = false;
  103. this._visitorKeys = options.visitorKeys || vk.KEYS;
  104. this._enter = options.enter || noop;
  105. this._leave = options.leave || noop;
  106. this._traverse(node, null);
  107. }
  108. /**
  109. * Traverse the given AST tree recursively.
  110. * @param {ASTNode} node The current node.
  111. * @param {ASTNode|null} parent The parent node.
  112. * @returns {void}
  113. * @private
  114. */
  115. _traverse(node, parent) {
  116. if (!isNode(node)) {
  117. return;
  118. }
  119. this._current = node;
  120. this._skipped = false;
  121. this._enter(node, parent);
  122. if (!this._skipped && !this._broken) {
  123. const keys = getVisitorKeys(this._visitorKeys, node);
  124. if (keys.length >= 1) {
  125. this._parents.push(node);
  126. for (let i = 0; i < keys.length && !this._broken; ++i) {
  127. const child = node[keys[i]];
  128. if (Array.isArray(child)) {
  129. for (
  130. let j = 0;
  131. j < child.length && !this._broken;
  132. ++j
  133. ) {
  134. this._traverse(child[j], node);
  135. }
  136. } else {
  137. this._traverse(child, node);
  138. }
  139. }
  140. this._parents.pop();
  141. }
  142. }
  143. if (!this._broken) {
  144. this._leave(node, parent);
  145. }
  146. this._current = parent;
  147. }
  148. /**
  149. * Calculates the keys to use for traversal.
  150. * @param {ASTNode} node The node to read keys from.
  151. * @returns {string[]} An array of keys to visit on the node.
  152. * @private
  153. */
  154. static getKeys(node) {
  155. return vk.getKeys(node);
  156. }
  157. /**
  158. * Traverse the given AST tree.
  159. * @param {ASTNode} node The root node to traverse.
  160. * @param {Object} options The option object.
  161. * @param {Object} [options.visitorKeys=DEFAULT_VISITOR_KEYS] The keys of each node types to traverse child nodes. Default is `./default-visitor-keys.json`.
  162. * @param {Function} [options.enter=noop] The callback function which is called on entering each node.
  163. * @param {Function} [options.leave=noop] The callback function which is called on leaving each node.
  164. * @returns {void}
  165. */
  166. static traverse(node, options) {
  167. new Traverser().traverse(node, options);
  168. }
  169. /**
  170. * The default visitor keys.
  171. * @type {Object}
  172. */
  173. static get DEFAULT_VISITOR_KEYS() {
  174. return vk.KEYS;
  175. }
  176. }
  177. module.exports = Traverser;