Node-Red configuration
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

estree-walker.js 7.0KB

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