123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333 |
- // @ts-check
- /** @typedef { import('estree').BaseNode} BaseNode */
-
- /** @typedef {{
- skip: () => void;
- remove: () => void;
- replace: (node: BaseNode) => void;
- }} WalkerContext */
-
- class WalkerBase {
- constructor() {
- /** @type {boolean} */
- this.should_skip = false;
-
- /** @type {boolean} */
- this.should_remove = false;
-
- /** @type {BaseNode | null} */
- this.replacement = null;
-
- /** @type {WalkerContext} */
- this.context = {
- skip: () => (this.should_skip = true),
- remove: () => (this.should_remove = true),
- replace: (node) => (this.replacement = node)
- };
- }
-
- /**
- *
- * @param {any} parent
- * @param {string} prop
- * @param {number} index
- * @param {BaseNode} node
- */
- replace(parent, prop, index, node) {
- if (parent) {
- if (index !== null) {
- parent[prop][index] = node;
- } else {
- parent[prop] = node;
- }
- }
- }
-
- /**
- *
- * @param {any} parent
- * @param {string} prop
- * @param {number} index
- */
- remove(parent, prop, index) {
- if (parent) {
- if (index !== null) {
- parent[prop].splice(index, 1);
- } else {
- delete parent[prop];
- }
- }
- }
- }
-
- // @ts-check
-
- /** @typedef { import('estree').BaseNode} BaseNode */
- /** @typedef { import('./walker.js').WalkerContext} WalkerContext */
-
- /** @typedef {(
- * this: WalkerContext,
- * node: BaseNode,
- * parent: BaseNode,
- * key: string,
- * index: number
- * ) => void} SyncHandler */
-
- class SyncWalker extends WalkerBase {
- /**
- *
- * @param {SyncHandler} enter
- * @param {SyncHandler} leave
- */
- constructor(enter, leave) {
- super();
-
- /** @type {SyncHandler} */
- this.enter = enter;
-
- /** @type {SyncHandler} */
- this.leave = leave;
- }
-
- /**
- *
- * @param {BaseNode} node
- * @param {BaseNode} parent
- * @param {string} [prop]
- * @param {number} [index]
- * @returns {BaseNode}
- */
- visit(node, parent, prop, index) {
- if (node) {
- if (this.enter) {
- const _should_skip = this.should_skip;
- const _should_remove = this.should_remove;
- const _replacement = this.replacement;
- this.should_skip = false;
- this.should_remove = false;
- this.replacement = null;
-
- this.enter.call(this.context, node, parent, prop, index);
-
- if (this.replacement) {
- node = this.replacement;
- this.replace(parent, prop, index, node);
- }
-
- if (this.should_remove) {
- this.remove(parent, prop, index);
- }
-
- const skipped = this.should_skip;
- const removed = this.should_remove;
-
- this.should_skip = _should_skip;
- this.should_remove = _should_remove;
- this.replacement = _replacement;
-
- if (skipped) return node;
- if (removed) return null;
- }
-
- for (const key in node) {
- const value = node[key];
-
- if (typeof value !== "object") {
- continue;
- } else if (Array.isArray(value)) {
- for (let i = 0; i < value.length; i += 1) {
- if (value[i] !== null && typeof value[i].type === 'string') {
- if (!this.visit(value[i], node, key, i)) {
- // removed
- i--;
- }
- }
- }
- } else if (value !== null && typeof value.type === "string") {
- this.visit(value, node, key, null);
- }
- }
-
- if (this.leave) {
- const _replacement = this.replacement;
- const _should_remove = this.should_remove;
- this.replacement = null;
- this.should_remove = false;
-
- this.leave.call(this.context, node, parent, prop, index);
-
- if (this.replacement) {
- node = this.replacement;
- this.replace(parent, prop, index, node);
- }
-
- if (this.should_remove) {
- this.remove(parent, prop, index);
- }
-
- const removed = this.should_remove;
-
- this.replacement = _replacement;
- this.should_remove = _should_remove;
-
- if (removed) return null;
- }
- }
-
- return node;
- }
- }
-
- // @ts-check
-
- /** @typedef { import('estree').BaseNode} BaseNode */
- /** @typedef { import('./walker').WalkerContext} WalkerContext */
-
- /** @typedef {(
- * this: WalkerContext,
- * node: BaseNode,
- * parent: BaseNode,
- * key: string,
- * index: number
- * ) => Promise<void>} AsyncHandler */
-
- class AsyncWalker extends WalkerBase {
- /**
- *
- * @param {AsyncHandler} enter
- * @param {AsyncHandler} leave
- */
- constructor(enter, leave) {
- super();
-
- /** @type {AsyncHandler} */
- this.enter = enter;
-
- /** @type {AsyncHandler} */
- this.leave = leave;
- }
-
- /**
- *
- * @param {BaseNode} node
- * @param {BaseNode} parent
- * @param {string} [prop]
- * @param {number} [index]
- * @returns {Promise<BaseNode>}
- */
- async visit(node, parent, prop, index) {
- if (node) {
- if (this.enter) {
- const _should_skip = this.should_skip;
- const _should_remove = this.should_remove;
- const _replacement = this.replacement;
- this.should_skip = false;
- this.should_remove = false;
- this.replacement = null;
-
- await this.enter.call(this.context, node, parent, prop, index);
-
- if (this.replacement) {
- node = this.replacement;
- this.replace(parent, prop, index, node);
- }
-
- if (this.should_remove) {
- this.remove(parent, prop, index);
- }
-
- const skipped = this.should_skip;
- const removed = this.should_remove;
-
- this.should_skip = _should_skip;
- this.should_remove = _should_remove;
- this.replacement = _replacement;
-
- if (skipped) return node;
- if (removed) return null;
- }
-
- for (const key in node) {
- const value = node[key];
-
- if (typeof value !== "object") {
- continue;
- } else if (Array.isArray(value)) {
- for (let i = 0; i < value.length; i += 1) {
- if (value[i] !== null && typeof value[i].type === 'string') {
- if (!(await this.visit(value[i], node, key, i))) {
- // removed
- i--;
- }
- }
- }
- } else if (value !== null && typeof value.type === "string") {
- await this.visit(value, node, key, null);
- }
- }
-
- if (this.leave) {
- const _replacement = this.replacement;
- const _should_remove = this.should_remove;
- this.replacement = null;
- this.should_remove = false;
-
- await this.leave.call(this.context, node, parent, prop, index);
-
- if (this.replacement) {
- node = this.replacement;
- this.replace(parent, prop, index, node);
- }
-
- if (this.should_remove) {
- this.remove(parent, prop, index);
- }
-
- const removed = this.should_remove;
-
- this.replacement = _replacement;
- this.should_remove = _should_remove;
-
- if (removed) return null;
- }
- }
-
- return node;
- }
- }
-
- // @ts-check
-
- /** @typedef { import('estree').BaseNode} BaseNode */
- /** @typedef { import('./sync.js').SyncHandler} SyncHandler */
- /** @typedef { import('./async.js').AsyncHandler} AsyncHandler */
-
- /**
- *
- * @param {BaseNode} ast
- * @param {{
- * enter?: SyncHandler
- * leave?: SyncHandler
- * }} walker
- * @returns {BaseNode}
- */
- function walk(ast, { enter, leave }) {
- const instance = new SyncWalker(enter, leave);
- return instance.visit(ast, null);
- }
-
- /**
- *
- * @param {BaseNode} ast
- * @param {{
- * enter?: AsyncHandler
- * leave?: AsyncHandler
- * }} walker
- * @returns {Promise<BaseNode>}
- */
- async function asyncWalk(ast, { enter, leave }) {
- const instance = new AsyncWalker(enter, leave);
- return await instance.visit(ast, null);
- }
-
- export { asyncWalk, walk };
|