Skip to content

Commit

Permalink
feat(ast): introduce async version of visitor merging mechanism (#3993)
Browse files Browse the repository at this point in the history
Refs #3832
  • Loading branch information
char0n committed Apr 2, 2024
1 parent 3050713 commit 7dd0bbd
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 2 deletions.
3 changes: 2 additions & 1 deletion packages/apidom-ast/src/index.ts
Expand Up @@ -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,
Expand Down
99 changes: 98 additions & 1 deletion packages/apidom-ast/src/traversal/visitor.ts
Expand Up @@ -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];
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
},
};
Expand Down

0 comments on commit 7dd0bbd

Please sign in to comment.