Spaces:
Runtime error
Runtime error
import { WalkerBase } from './walker.js'; | |
/** | |
* @typedef { import('estree').Node} Node | |
* @typedef { import('./walker.js').WalkerContext} WalkerContext | |
* @typedef {( | |
* this: WalkerContext, | |
* node: Node, | |
* parent: Node | null, | |
* key: string | number | symbol | null | undefined, | |
* index: number | null | undefined | |
* ) => Promise<void>} AsyncHandler | |
*/ | |
export class AsyncWalker extends WalkerBase { | |
/** | |
* | |
* @param {AsyncHandler} [enter] | |
* @param {AsyncHandler} [leave] | |
*/ | |
constructor(enter, leave) { | |
super(); | |
/** @type {boolean} */ | |
this.should_skip = false; | |
/** @type {boolean} */ | |
this.should_remove = false; | |
/** @type {Node | null} */ | |
this.replacement = null; | |
/** @type {WalkerContext} */ | |
this.context = { | |
skip: () => (this.should_skip = true), | |
remove: () => (this.should_remove = true), | |
replace: (node) => (this.replacement = node) | |
}; | |
/** @type {AsyncHandler | undefined} */ | |
this.enter = enter; | |
/** @type {AsyncHandler | undefined} */ | |
this.leave = leave; | |
} | |
/** | |
* @template {Node} Parent | |
* @param {Node} node | |
* @param {Parent | null} parent | |
* @param {keyof Parent} [prop] | |
* @param {number | null} [index] | |
* @returns {Promise<Node | null>} | |
*/ | |
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; | |
} | |
/** @type {keyof Node} */ | |
let key; | |
for (key in node) { | |
/** @type {unknown} */ | |
const value = node[key]; | |
if (value && typeof value === 'object') { | |
if (Array.isArray(value)) { | |
const nodes = /** @type {Array<unknown>} */ (value); | |
for (let i = 0; i < nodes.length; i += 1) { | |
const item = nodes[i]; | |
if (isNode(item)) { | |
if (!(await this.visit(item, node, key, i))) { | |
// removed | |
i--; | |
} | |
} | |
} | |
} else if (isNode(value)) { | |
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; | |
} | |
} | |
/** | |
* Ducktype a node. | |
* | |
* @param {unknown} value | |
* @returns {value is Node} | |
*/ | |
function isNode(value) { | |
return ( | |
value !== null && typeof value === 'object' && 'type' in value && typeof value.type === 'string' | |
); | |
} | |