From 7dd0bbd650eeeee9244cae6dafdf94df9c9150e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Gorej?= Date: Tue, 2 Apr 2024 11:31:21 +0200 Subject: [PATCH] feat(ast): introduce async version of visitor merging mechanism (#3993) Refs #3832 --- packages/apidom-ast/src/index.ts | 3 +- packages/apidom-ast/src/traversal/visitor.ts | 99 +++++++++++++++++++- 2 files changed, 100 insertions(+), 2 deletions(-) diff --git a/packages/apidom-ast/src/index.ts b/packages/apidom-ast/src/index.ts index f231270137..a1cd4602a2 100644 --- a/packages/apidom-ast/src/index.ts +++ b/packages/apidom-ast/src/index.ts @@ -71,9 +71,10 @@ export { default as ParseResult } from './ParseResult'; export { isParseResult, isLiteral, isPoint, isPosition } from './predicates'; // AST traversal related exports export { - getVisitFn, + customPromisifySymbol, BREAK, mergeAll as mergeAllVisitors, + getVisitFn, visit, getNodeType, isNode, diff --git a/packages/apidom-ast/src/traversal/visitor.ts b/packages/apidom-ast/src/traversal/visitor.ts index 1b147fb878..cb4f5c82ce 100644 --- a/packages/apidom-ast/src/traversal/visitor.ts +++ b/packages/apidom-ast/src/traversal/visitor.ts @@ -6,6 +6,8 @@ import { ApiDOMStructuredError } from '@swagger-api/apidom-error'; * SPDX-License-Identifier: MIT */ +export const customPromisifySymbol: unique symbol = Symbol.for('nodejs.util.promisify.custom'); + // getVisitFn :: (Visitor, String, Boolean) -> Function export const getVisitFn = (visitor: any, type: string, isLeaving: boolean) => { const typeVisitor = visitor[type]; @@ -57,7 +59,31 @@ export const cloneNode = (node: any) => * If a prior visitor edits a node, no following visitors will see that node. * `exposeEdits=true` can be used to exoise the edited node from the previous visitors. */ -export const mergeAll = ( + +interface MergeAllBase { + ( + visitors: any[], + options?: { + visitFnGetter?: typeof getVisitFn; + nodeTypeGetter?: typeof getNodeType; + breakSymbol?: typeof BREAK; + deleteNodeSymbol?: any; + skipVisitingNodeSymbol?: boolean; + exposeEdits?: boolean; + }, + ): { + enter: (node: any, ...rest: any[]) => any; + leave: (node: any, ...rest: any[]) => any; + }; +} + +interface MergeAllPromisify { + [customPromisifySymbol]: MergeAllBase; +} + +type MergeAll = MergeAllBase & MergeAllPromisify; + +export const mergeAll: MergeAll = (( visitors: any[], { visitFnGetter = getVisitFn, @@ -121,6 +147,77 @@ export const mergeAll = ( } } + return undefined; + }, + }; +}) as MergeAll; + +mergeAll[customPromisifySymbol] = ( + visitors: any[], + { + visitFnGetter = getVisitFn, + nodeTypeGetter = getNodeType, + breakSymbol = BREAK, + deleteNodeSymbol = null, + skipVisitingNodeSymbol = false, + exposeEdits = false, + } = {}, +) => { + const skipSymbol = Symbol('skip'); + const skipping = new Array(visitors.length).fill(skipSymbol); + + return { + async enter(node: any, ...rest: any[]) { + let currentNode = node; + let hasChanged = false; + + for (let i = 0; i < visitors.length; i += 1) { + if (skipping[i] === skipSymbol) { + const visitFn = visitFnGetter(visitors[i], nodeTypeGetter(currentNode), false); + + if (typeof visitFn === 'function') { + // eslint-disable-next-line no-await-in-loop + const result: any = await visitFn.call(visitors[i], currentNode, ...rest); + + if (result === skipVisitingNodeSymbol) { + skipping[i] = node; + } else if (result === breakSymbol) { + skipping[i] = breakSymbol; + } else if (result === deleteNodeSymbol) { + return result; + } else if (result !== undefined) { + if (exposeEdits) { + currentNode = result; + hasChanged = true; + } else { + return result; + } + } + } + } + } + + return hasChanged ? currentNode : undefined; + }, + async leave(node: any, ...rest: any[]) { + for (let i = 0; i < visitors.length; i += 1) { + if (skipping[i] === skipSymbol) { + const visitFn = visitFnGetter(visitors[i], nodeTypeGetter(node), true); + + if (typeof visitFn === 'function') { + // eslint-disable-next-line no-await-in-loop + const result = await visitFn.call(visitors[i], node, ...rest); + if (result === breakSymbol) { + skipping[i] = breakSymbol; + } else if (result !== undefined && result !== skipVisitingNodeSymbol) { + return result; + } + } + } else if (skipping[i] === node) { + skipping[i] = skipSymbol; + } + } + return undefined; }, };