estree-walker.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. (function (global, factory) {
  2. typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
  3. typeof define === 'function' && define.amd ? define(['exports'], factory) :
  4. (global = global || self, factory(global.estreeWalker = {}));
  5. }(this, (function (exports) { 'use strict';
  6. // @ts-check
  7. /** @typedef { import('estree').BaseNode} BaseNode */
  8. /** @typedef {{
  9. skip: () => void;
  10. remove: () => void;
  11. replace: (node: BaseNode) => void;
  12. }} WalkerContext */
  13. class WalkerBase {
  14. constructor() {
  15. /** @type {boolean} */
  16. this.should_skip = false;
  17. /** @type {boolean} */
  18. this.should_remove = false;
  19. /** @type {BaseNode | null} */
  20. this.replacement = null;
  21. /** @type {WalkerContext} */
  22. this.context = {
  23. skip: () => (this.should_skip = true),
  24. remove: () => (this.should_remove = true),
  25. replace: (node) => (this.replacement = node)
  26. };
  27. }
  28. /**
  29. *
  30. * @param {any} parent
  31. * @param {string} prop
  32. * @param {number} index
  33. * @param {BaseNode} node
  34. */
  35. replace(parent, prop, index, node) {
  36. if (parent) {
  37. if (index !== null) {
  38. parent[prop][index] = node;
  39. } else {
  40. parent[prop] = node;
  41. }
  42. }
  43. }
  44. /**
  45. *
  46. * @param {any} parent
  47. * @param {string} prop
  48. * @param {number} index
  49. */
  50. remove(parent, prop, index) {
  51. if (parent) {
  52. if (index !== null) {
  53. parent[prop].splice(index, 1);
  54. } else {
  55. delete parent[prop];
  56. }
  57. }
  58. }
  59. }
  60. // @ts-check
  61. /** @typedef { import('estree').BaseNode} BaseNode */
  62. /** @typedef { import('./walker.js').WalkerContext} WalkerContext */
  63. /** @typedef {(
  64. * this: WalkerContext,
  65. * node: BaseNode,
  66. * parent: BaseNode,
  67. * key: string,
  68. * index: number
  69. * ) => void} SyncHandler */
  70. class SyncWalker extends WalkerBase {
  71. /**
  72. *
  73. * @param {SyncHandler} enter
  74. * @param {SyncHandler} leave
  75. */
  76. constructor(enter, leave) {
  77. super();
  78. /** @type {SyncHandler} */
  79. this.enter = enter;
  80. /** @type {SyncHandler} */
  81. this.leave = leave;
  82. }
  83. /**
  84. *
  85. * @param {BaseNode} node
  86. * @param {BaseNode} parent
  87. * @param {string} [prop]
  88. * @param {number} [index]
  89. * @returns {BaseNode}
  90. */
  91. visit(node, parent, prop, index) {
  92. if (node) {
  93. if (this.enter) {
  94. const _should_skip = this.should_skip;
  95. const _should_remove = this.should_remove;
  96. const _replacement = this.replacement;
  97. this.should_skip = false;
  98. this.should_remove = false;
  99. this.replacement = null;
  100. this.enter.call(this.context, node, parent, prop, index);
  101. if (this.replacement) {
  102. node = this.replacement;
  103. this.replace(parent, prop, index, node);
  104. }
  105. if (this.should_remove) {
  106. this.remove(parent, prop, index);
  107. }
  108. const skipped = this.should_skip;
  109. const removed = this.should_remove;
  110. this.should_skip = _should_skip;
  111. this.should_remove = _should_remove;
  112. this.replacement = _replacement;
  113. if (skipped) return node;
  114. if (removed) return null;
  115. }
  116. for (const key in node) {
  117. const value = node[key];
  118. if (typeof value !== "object") {
  119. continue;
  120. } else if (Array.isArray(value)) {
  121. for (let i = 0; i < value.length; i += 1) {
  122. if (value[i] !== null && typeof value[i].type === 'string') {
  123. if (!this.visit(value[i], node, key, i)) {
  124. // removed
  125. i--;
  126. }
  127. }
  128. }
  129. } else if (value !== null && typeof value.type === "string") {
  130. this.visit(value, node, key, null);
  131. }
  132. }
  133. if (this.leave) {
  134. const _replacement = this.replacement;
  135. const _should_remove = this.should_remove;
  136. this.replacement = null;
  137. this.should_remove = false;
  138. this.leave.call(this.context, node, parent, prop, index);
  139. if (this.replacement) {
  140. node = this.replacement;
  141. this.replace(parent, prop, index, node);
  142. }
  143. if (this.should_remove) {
  144. this.remove(parent, prop, index);
  145. }
  146. const removed = this.should_remove;
  147. this.replacement = _replacement;
  148. this.should_remove = _should_remove;
  149. if (removed) return null;
  150. }
  151. }
  152. return node;
  153. }
  154. }
  155. // @ts-check
  156. /** @typedef { import('estree').BaseNode} BaseNode */
  157. /** @typedef { import('./walker').WalkerContext} WalkerContext */
  158. /** @typedef {(
  159. * this: WalkerContext,
  160. * node: BaseNode,
  161. * parent: BaseNode,
  162. * key: string,
  163. * index: number
  164. * ) => Promise<void>} AsyncHandler */
  165. class AsyncWalker extends WalkerBase {
  166. /**
  167. *
  168. * @param {AsyncHandler} enter
  169. * @param {AsyncHandler} leave
  170. */
  171. constructor(enter, leave) {
  172. super();
  173. /** @type {AsyncHandler} */
  174. this.enter = enter;
  175. /** @type {AsyncHandler} */
  176. this.leave = leave;
  177. }
  178. /**
  179. *
  180. * @param {BaseNode} node
  181. * @param {BaseNode} parent
  182. * @param {string} [prop]
  183. * @param {number} [index]
  184. * @returns {Promise<BaseNode>}
  185. */
  186. async visit(node, parent, prop, index) {
  187. if (node) {
  188. if (this.enter) {
  189. const _should_skip = this.should_skip;
  190. const _should_remove = this.should_remove;
  191. const _replacement = this.replacement;
  192. this.should_skip = false;
  193. this.should_remove = false;
  194. this.replacement = null;
  195. await this.enter.call(this.context, node, parent, prop, index);
  196. if (this.replacement) {
  197. node = this.replacement;
  198. this.replace(parent, prop, index, node);
  199. }
  200. if (this.should_remove) {
  201. this.remove(parent, prop, index);
  202. }
  203. const skipped = this.should_skip;
  204. const removed = this.should_remove;
  205. this.should_skip = _should_skip;
  206. this.should_remove = _should_remove;
  207. this.replacement = _replacement;
  208. if (skipped) return node;
  209. if (removed) return null;
  210. }
  211. for (const key in node) {
  212. const value = node[key];
  213. if (typeof value !== "object") {
  214. continue;
  215. } else if (Array.isArray(value)) {
  216. for (let i = 0; i < value.length; i += 1) {
  217. if (value[i] !== null && typeof value[i].type === 'string') {
  218. if (!(await this.visit(value[i], node, key, i))) {
  219. // removed
  220. i--;
  221. }
  222. }
  223. }
  224. } else if (value !== null && typeof value.type === "string") {
  225. await this.visit(value, node, key, null);
  226. }
  227. }
  228. if (this.leave) {
  229. const _replacement = this.replacement;
  230. const _should_remove = this.should_remove;
  231. this.replacement = null;
  232. this.should_remove = false;
  233. await this.leave.call(this.context, node, parent, prop, index);
  234. if (this.replacement) {
  235. node = this.replacement;
  236. this.replace(parent, prop, index, node);
  237. }
  238. if (this.should_remove) {
  239. this.remove(parent, prop, index);
  240. }
  241. const removed = this.should_remove;
  242. this.replacement = _replacement;
  243. this.should_remove = _should_remove;
  244. if (removed) return null;
  245. }
  246. }
  247. return node;
  248. }
  249. }
  250. // @ts-check
  251. /** @typedef { import('estree').BaseNode} BaseNode */
  252. /** @typedef { import('./sync.js').SyncHandler} SyncHandler */
  253. /** @typedef { import('./async.js').AsyncHandler} AsyncHandler */
  254. /**
  255. *
  256. * @param {BaseNode} ast
  257. * @param {{
  258. * enter?: SyncHandler
  259. * leave?: SyncHandler
  260. * }} walker
  261. * @returns {BaseNode}
  262. */
  263. function walk(ast, { enter, leave }) {
  264. const instance = new SyncWalker(enter, leave);
  265. return instance.visit(ast, null);
  266. }
  267. /**
  268. *
  269. * @param {BaseNode} ast
  270. * @param {{
  271. * enter?: AsyncHandler
  272. * leave?: AsyncHandler
  273. * }} walker
  274. * @returns {Promise<BaseNode>}
  275. */
  276. async function asyncWalk(ast, { enter, leave }) {
  277. const instance = new AsyncWalker(enter, leave);
  278. return await instance.visit(ast, null);
  279. }
  280. exports.asyncWalk = asyncWalk;
  281. exports.walk = walk;
  282. Object.defineProperty(exports, '__esModule', { value: true });
  283. })));