visitor.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', {
  3. value: true,
  4. });
  5. exports.BREAK = void 0;
  6. exports.getEnterLeaveForKind = getEnterLeaveForKind;
  7. exports.getVisitFn = getVisitFn;
  8. exports.visit = visit;
  9. exports.visitInParallel = visitInParallel;
  10. var _devAssert = require('../jsutils/devAssert.js');
  11. var _inspect = require('../jsutils/inspect.js');
  12. var _ast = require('./ast.js');
  13. var _kinds = require('./kinds.js');
  14. const BREAK = Object.freeze({});
  15. /**
  16. * visit() will walk through an AST using a depth-first traversal, calling
  17. * the visitor's enter function at each node in the traversal, and calling the
  18. * leave function after visiting that node and all of its child nodes.
  19. *
  20. * By returning different values from the enter and leave functions, the
  21. * behavior of the visitor can be altered, including skipping over a sub-tree of
  22. * the AST (by returning false), editing the AST by returning a value or null
  23. * to remove the value, or to stop the whole traversal by returning BREAK.
  24. *
  25. * When using visit() to edit an AST, the original AST will not be modified, and
  26. * a new version of the AST with the changes applied will be returned from the
  27. * visit function.
  28. *
  29. * ```ts
  30. * const editedAST = visit(ast, {
  31. * enter(node, key, parent, path, ancestors) {
  32. * // @return
  33. * // undefined: no action
  34. * // false: skip visiting this node
  35. * // visitor.BREAK: stop visiting altogether
  36. * // null: delete this node
  37. * // any value: replace this node with the returned value
  38. * },
  39. * leave(node, key, parent, path, ancestors) {
  40. * // @return
  41. * // undefined: no action
  42. * // false: no action
  43. * // visitor.BREAK: stop visiting altogether
  44. * // null: delete this node
  45. * // any value: replace this node with the returned value
  46. * }
  47. * });
  48. * ```
  49. *
  50. * Alternatively to providing enter() and leave() functions, a visitor can
  51. * instead provide functions named the same as the kinds of AST nodes, or
  52. * enter/leave visitors at a named key, leading to three permutations of the
  53. * visitor API:
  54. *
  55. * 1) Named visitors triggered when entering a node of a specific kind.
  56. *
  57. * ```ts
  58. * visit(ast, {
  59. * Kind(node) {
  60. * // enter the "Kind" node
  61. * }
  62. * })
  63. * ```
  64. *
  65. * 2) Named visitors that trigger upon entering and leaving a node of a specific kind.
  66. *
  67. * ```ts
  68. * visit(ast, {
  69. * Kind: {
  70. * enter(node) {
  71. * // enter the "Kind" node
  72. * }
  73. * leave(node) {
  74. * // leave the "Kind" node
  75. * }
  76. * }
  77. * })
  78. * ```
  79. *
  80. * 3) Generic visitors that trigger upon entering and leaving any node.
  81. *
  82. * ```ts
  83. * visit(ast, {
  84. * enter(node) {
  85. * // enter any node
  86. * },
  87. * leave(node) {
  88. * // leave any node
  89. * }
  90. * })
  91. * ```
  92. */
  93. exports.BREAK = BREAK;
  94. function visit(root, visitor, visitorKeys = _ast.QueryDocumentKeys) {
  95. const enterLeaveMap = new Map();
  96. for (const kind of Object.values(_kinds.Kind)) {
  97. enterLeaveMap.set(kind, getEnterLeaveForKind(visitor, kind));
  98. }
  99. /* eslint-disable no-undef-init */
  100. let stack = undefined;
  101. let inArray = Array.isArray(root);
  102. let keys = [root];
  103. let index = -1;
  104. let edits = [];
  105. let node = root;
  106. let key = undefined;
  107. let parent = undefined;
  108. const path = [];
  109. const ancestors = [];
  110. /* eslint-enable no-undef-init */
  111. do {
  112. index++;
  113. const isLeaving = index === keys.length;
  114. const isEdited = isLeaving && edits.length !== 0;
  115. if (isLeaving) {
  116. key = ancestors.length === 0 ? undefined : path[path.length - 1];
  117. node = parent;
  118. parent = ancestors.pop();
  119. if (isEdited) {
  120. if (inArray) {
  121. node = node.slice();
  122. let editOffset = 0;
  123. for (const [editKey, editValue] of edits) {
  124. const arrayKey = editKey - editOffset;
  125. if (editValue === null) {
  126. node.splice(arrayKey, 1);
  127. editOffset++;
  128. } else {
  129. node[arrayKey] = editValue;
  130. }
  131. }
  132. } else {
  133. node = Object.defineProperties(
  134. {},
  135. Object.getOwnPropertyDescriptors(node),
  136. );
  137. for (const [editKey, editValue] of edits) {
  138. node[editKey] = editValue;
  139. }
  140. }
  141. }
  142. index = stack.index;
  143. keys = stack.keys;
  144. edits = stack.edits;
  145. inArray = stack.inArray;
  146. stack = stack.prev;
  147. } else if (parent) {
  148. key = inArray ? index : keys[index];
  149. node = parent[key];
  150. if (node === null || node === undefined) {
  151. continue;
  152. }
  153. path.push(key);
  154. }
  155. let result;
  156. if (!Array.isArray(node)) {
  157. var _enterLeaveMap$get, _enterLeaveMap$get2;
  158. (0, _ast.isNode)(node) ||
  159. (0, _devAssert.devAssert)(
  160. false,
  161. `Invalid AST Node: ${(0, _inspect.inspect)(node)}.`,
  162. );
  163. const visitFn = isLeaving
  164. ? (_enterLeaveMap$get = enterLeaveMap.get(node.kind)) === null ||
  165. _enterLeaveMap$get === void 0
  166. ? void 0
  167. : _enterLeaveMap$get.leave
  168. : (_enterLeaveMap$get2 = enterLeaveMap.get(node.kind)) === null ||
  169. _enterLeaveMap$get2 === void 0
  170. ? void 0
  171. : _enterLeaveMap$get2.enter;
  172. result =
  173. visitFn === null || visitFn === void 0
  174. ? void 0
  175. : visitFn.call(visitor, node, key, parent, path, ancestors);
  176. if (result === BREAK) {
  177. break;
  178. }
  179. if (result === false) {
  180. if (!isLeaving) {
  181. path.pop();
  182. continue;
  183. }
  184. } else if (result !== undefined) {
  185. edits.push([key, result]);
  186. if (!isLeaving) {
  187. if ((0, _ast.isNode)(result)) {
  188. node = result;
  189. } else {
  190. path.pop();
  191. continue;
  192. }
  193. }
  194. }
  195. }
  196. if (result === undefined && isEdited) {
  197. edits.push([key, node]);
  198. }
  199. if (isLeaving) {
  200. path.pop();
  201. } else {
  202. var _node$kind;
  203. stack = {
  204. inArray,
  205. index,
  206. keys,
  207. edits,
  208. prev: stack,
  209. };
  210. inArray = Array.isArray(node);
  211. keys = inArray
  212. ? node
  213. : (_node$kind = visitorKeys[node.kind]) !== null &&
  214. _node$kind !== void 0
  215. ? _node$kind
  216. : [];
  217. index = -1;
  218. edits = [];
  219. if (parent) {
  220. ancestors.push(parent);
  221. }
  222. parent = node;
  223. }
  224. } while (stack !== undefined);
  225. if (edits.length !== 0) {
  226. // New root
  227. return edits[edits.length - 1][1];
  228. }
  229. return root;
  230. }
  231. /**
  232. * Creates a new visitor instance which delegates to many visitors to run in
  233. * parallel. Each visitor will be visited for each node before moving on.
  234. *
  235. * If a prior visitor edits a node, no following visitors will see that node.
  236. */
  237. function visitInParallel(visitors) {
  238. const skipping = new Array(visitors.length).fill(null);
  239. const mergedVisitor = Object.create(null);
  240. for (const kind of Object.values(_kinds.Kind)) {
  241. let hasVisitor = false;
  242. const enterList = new Array(visitors.length).fill(undefined);
  243. const leaveList = new Array(visitors.length).fill(undefined);
  244. for (let i = 0; i < visitors.length; ++i) {
  245. const { enter, leave } = getEnterLeaveForKind(visitors[i], kind);
  246. hasVisitor || (hasVisitor = enter != null || leave != null);
  247. enterList[i] = enter;
  248. leaveList[i] = leave;
  249. }
  250. if (!hasVisitor) {
  251. continue;
  252. }
  253. const mergedEnterLeave = {
  254. enter(...args) {
  255. const node = args[0];
  256. for (let i = 0; i < visitors.length; i++) {
  257. if (skipping[i] === null) {
  258. var _enterList$i;
  259. const result =
  260. (_enterList$i = enterList[i]) === null || _enterList$i === void 0
  261. ? void 0
  262. : _enterList$i.apply(visitors[i], args);
  263. if (result === false) {
  264. skipping[i] = node;
  265. } else if (result === BREAK) {
  266. skipping[i] = BREAK;
  267. } else if (result !== undefined) {
  268. return result;
  269. }
  270. }
  271. }
  272. },
  273. leave(...args) {
  274. const node = args[0];
  275. for (let i = 0; i < visitors.length; i++) {
  276. if (skipping[i] === null) {
  277. var _leaveList$i;
  278. const result =
  279. (_leaveList$i = leaveList[i]) === null || _leaveList$i === void 0
  280. ? void 0
  281. : _leaveList$i.apply(visitors[i], args);
  282. if (result === BREAK) {
  283. skipping[i] = BREAK;
  284. } else if (result !== undefined && result !== false) {
  285. return result;
  286. }
  287. } else if (skipping[i] === node) {
  288. skipping[i] = null;
  289. }
  290. }
  291. },
  292. };
  293. mergedVisitor[kind] = mergedEnterLeave;
  294. }
  295. return mergedVisitor;
  296. }
  297. /**
  298. * Given a visitor instance and a node kind, return EnterLeaveVisitor for that kind.
  299. */
  300. function getEnterLeaveForKind(visitor, kind) {
  301. const kindVisitor = visitor[kind];
  302. if (typeof kindVisitor === 'object') {
  303. // { Kind: { enter() {}, leave() {} } }
  304. return kindVisitor;
  305. } else if (typeof kindVisitor === 'function') {
  306. // { Kind() {} }
  307. return {
  308. enter: kindVisitor,
  309. leave: undefined,
  310. };
  311. } // { enter() {}, leave() {} }
  312. return {
  313. enter: visitor.enter,
  314. leave: visitor.leave,
  315. };
  316. }
  317. /**
  318. * Given a visitor instance, if it is leaving or not, and a node kind, return
  319. * the function the visitor runtime should call.
  320. *
  321. * @deprecated Please use `getEnterLeaveForKind` instead. Will be removed in v17
  322. */
  323. /* c8 ignore next 8 */
  324. function getVisitFn(visitor, kind, isLeaving) {
  325. const { enter, leave } = getEnterLeaveForKind(visitor, kind);
  326. return isLeaving ? leave : enter;
  327. }