Spaces:
Running
Running
| ; | |
| Object.defineProperty(exports, '__esModule', { | |
| value: true, | |
| }); | |
| exports.BREAK = void 0; | |
| exports.getEnterLeaveForKind = getEnterLeaveForKind; | |
| exports.getVisitFn = getVisitFn; | |
| exports.visit = visit; | |
| exports.visitInParallel = visitInParallel; | |
| var _devAssert = require('../jsutils/devAssert.js'); | |
| var _inspect = require('../jsutils/inspect.js'); | |
| var _ast = require('./ast.js'); | |
| var _kinds = require('./kinds.js'); | |
| const BREAK = Object.freeze({}); | |
| /** | |
| * visit() will walk through an AST using a depth-first traversal, calling | |
| * the visitor's enter function at each node in the traversal, and calling the | |
| * leave function after visiting that node and all of its child nodes. | |
| * | |
| * By returning different values from the enter and leave functions, the | |
| * behavior of the visitor can be altered, including skipping over a sub-tree of | |
| * the AST (by returning false), editing the AST by returning a value or null | |
| * to remove the value, or to stop the whole traversal by returning BREAK. | |
| * | |
| * When using visit() to edit an AST, the original AST will not be modified, and | |
| * a new version of the AST with the changes applied will be returned from the | |
| * visit function. | |
| * | |
| * ```ts | |
| * const editedAST = visit(ast, { | |
| * enter(node, key, parent, path, ancestors) { | |
| * // @return | |
| * // undefined: no action | |
| * // false: skip visiting this node | |
| * // visitor.BREAK: stop visiting altogether | |
| * // null: delete this node | |
| * // any value: replace this node with the returned value | |
| * }, | |
| * leave(node, key, parent, path, ancestors) { | |
| * // @return | |
| * // undefined: no action | |
| * // false: no action | |
| * // visitor.BREAK: stop visiting altogether | |
| * // null: delete this node | |
| * // any value: replace this node with the returned value | |
| * } | |
| * }); | |
| * ``` | |
| * | |
| * Alternatively to providing enter() and leave() functions, a visitor can | |
| * instead provide functions named the same as the kinds of AST nodes, or | |
| * enter/leave visitors at a named key, leading to three permutations of the | |
| * visitor API: | |
| * | |
| * 1) Named visitors triggered when entering a node of a specific kind. | |
| * | |
| * ```ts | |
| * visit(ast, { | |
| * Kind(node) { | |
| * // enter the "Kind" node | |
| * } | |
| * }) | |
| * ``` | |
| * | |
| * 2) Named visitors that trigger upon entering and leaving a node of a specific kind. | |
| * | |
| * ```ts | |
| * visit(ast, { | |
| * Kind: { | |
| * enter(node) { | |
| * // enter the "Kind" node | |
| * } | |
| * leave(node) { | |
| * // leave the "Kind" node | |
| * } | |
| * } | |
| * }) | |
| * ``` | |
| * | |
| * 3) Generic visitors that trigger upon entering and leaving any node. | |
| * | |
| * ```ts | |
| * visit(ast, { | |
| * enter(node) { | |
| * // enter any node | |
| * }, | |
| * leave(node) { | |
| * // leave any node | |
| * } | |
| * }) | |
| * ``` | |
| */ | |
| exports.BREAK = BREAK; | |
| function visit(root, visitor, visitorKeys = _ast.QueryDocumentKeys) { | |
| const enterLeaveMap = new Map(); | |
| for (const kind of Object.values(_kinds.Kind)) { | |
| enterLeaveMap.set(kind, getEnterLeaveForKind(visitor, kind)); | |
| } | |
| /* eslint-disable no-undef-init */ | |
| let stack = undefined; | |
| let inArray = Array.isArray(root); | |
| let keys = [root]; | |
| let index = -1; | |
| let edits = []; | |
| let node = root; | |
| let key = undefined; | |
| let parent = undefined; | |
| const path = []; | |
| const ancestors = []; | |
| /* eslint-enable no-undef-init */ | |
| do { | |
| index++; | |
| const isLeaving = index === keys.length; | |
| const isEdited = isLeaving && edits.length !== 0; | |
| if (isLeaving) { | |
| key = ancestors.length === 0 ? undefined : path[path.length - 1]; | |
| node = parent; | |
| parent = ancestors.pop(); | |
| if (isEdited) { | |
| if (inArray) { | |
| node = node.slice(); | |
| let editOffset = 0; | |
| for (const [editKey, editValue] of edits) { | |
| const arrayKey = editKey - editOffset; | |
| if (editValue === null) { | |
| node.splice(arrayKey, 1); | |
| editOffset++; | |
| } else { | |
| node[arrayKey] = editValue; | |
| } | |
| } | |
| } else { | |
| node = Object.defineProperties( | |
| {}, | |
| Object.getOwnPropertyDescriptors(node), | |
| ); | |
| for (const [editKey, editValue] of edits) { | |
| node[editKey] = editValue; | |
| } | |
| } | |
| } | |
| index = stack.index; | |
| keys = stack.keys; | |
| edits = stack.edits; | |
| inArray = stack.inArray; | |
| stack = stack.prev; | |
| } else if (parent) { | |
| key = inArray ? index : keys[index]; | |
| node = parent[key]; | |
| if (node === null || node === undefined) { | |
| continue; | |
| } | |
| path.push(key); | |
| } | |
| let result; | |
| if (!Array.isArray(node)) { | |
| var _enterLeaveMap$get, _enterLeaveMap$get2; | |
| (0, _ast.isNode)(node) || | |
| (0, _devAssert.devAssert)( | |
| false, | |
| `Invalid AST Node: ${(0, _inspect.inspect)(node)}.`, | |
| ); | |
| const visitFn = isLeaving | |
| ? (_enterLeaveMap$get = enterLeaveMap.get(node.kind)) === null || | |
| _enterLeaveMap$get === void 0 | |
| ? void 0 | |
| : _enterLeaveMap$get.leave | |
| : (_enterLeaveMap$get2 = enterLeaveMap.get(node.kind)) === null || | |
| _enterLeaveMap$get2 === void 0 | |
| ? void 0 | |
| : _enterLeaveMap$get2.enter; | |
| result = | |
| visitFn === null || visitFn === void 0 | |
| ? void 0 | |
| : visitFn.call(visitor, node, key, parent, path, ancestors); | |
| if (result === BREAK) { | |
| break; | |
| } | |
| if (result === false) { | |
| if (!isLeaving) { | |
| path.pop(); | |
| continue; | |
| } | |
| } else if (result !== undefined) { | |
| edits.push([key, result]); | |
| if (!isLeaving) { | |
| if ((0, _ast.isNode)(result)) { | |
| node = result; | |
| } else { | |
| path.pop(); | |
| continue; | |
| } | |
| } | |
| } | |
| } | |
| if (result === undefined && isEdited) { | |
| edits.push([key, node]); | |
| } | |
| if (isLeaving) { | |
| path.pop(); | |
| } else { | |
| var _node$kind; | |
| stack = { | |
| inArray, | |
| index, | |
| keys, | |
| edits, | |
| prev: stack, | |
| }; | |
| inArray = Array.isArray(node); | |
| keys = inArray | |
| ? node | |
| : (_node$kind = visitorKeys[node.kind]) !== null && | |
| _node$kind !== void 0 | |
| ? _node$kind | |
| : []; | |
| index = -1; | |
| edits = []; | |
| if (parent) { | |
| ancestors.push(parent); | |
| } | |
| parent = node; | |
| } | |
| } while (stack !== undefined); | |
| if (edits.length !== 0) { | |
| // New root | |
| return edits[edits.length - 1][1]; | |
| } | |
| return root; | |
| } | |
| /** | |
| * Creates a new visitor instance which delegates to many visitors to run in | |
| * parallel. Each visitor will be visited for each node before moving on. | |
| * | |
| * If a prior visitor edits a node, no following visitors will see that node. | |
| */ | |
| function visitInParallel(visitors) { | |
| const skipping = new Array(visitors.length).fill(null); | |
| const mergedVisitor = Object.create(null); | |
| for (const kind of Object.values(_kinds.Kind)) { | |
| let hasVisitor = false; | |
| const enterList = new Array(visitors.length).fill(undefined); | |
| const leaveList = new Array(visitors.length).fill(undefined); | |
| for (let i = 0; i < visitors.length; ++i) { | |
| const { enter, leave } = getEnterLeaveForKind(visitors[i], kind); | |
| hasVisitor || (hasVisitor = enter != null || leave != null); | |
| enterList[i] = enter; | |
| leaveList[i] = leave; | |
| } | |
| if (!hasVisitor) { | |
| continue; | |
| } | |
| const mergedEnterLeave = { | |
| enter(...args) { | |
| const node = args[0]; | |
| for (let i = 0; i < visitors.length; i++) { | |
| if (skipping[i] === null) { | |
| var _enterList$i; | |
| const result = | |
| (_enterList$i = enterList[i]) === null || _enterList$i === void 0 | |
| ? void 0 | |
| : _enterList$i.apply(visitors[i], args); | |
| if (result === false) { | |
| skipping[i] = node; | |
| } else if (result === BREAK) { | |
| skipping[i] = BREAK; | |
| } else if (result !== undefined) { | |
| return result; | |
| } | |
| } | |
| } | |
| }, | |
| leave(...args) { | |
| const node = args[0]; | |
| for (let i = 0; i < visitors.length; i++) { | |
| if (skipping[i] === null) { | |
| var _leaveList$i; | |
| const result = | |
| (_leaveList$i = leaveList[i]) === null || _leaveList$i === void 0 | |
| ? void 0 | |
| : _leaveList$i.apply(visitors[i], args); | |
| if (result === BREAK) { | |
| skipping[i] = BREAK; | |
| } else if (result !== undefined && result !== false) { | |
| return result; | |
| } | |
| } else if (skipping[i] === node) { | |
| skipping[i] = null; | |
| } | |
| } | |
| }, | |
| }; | |
| mergedVisitor[kind] = mergedEnterLeave; | |
| } | |
| return mergedVisitor; | |
| } | |
| /** | |
| * Given a visitor instance and a node kind, return EnterLeaveVisitor for that kind. | |
| */ | |
| function getEnterLeaveForKind(visitor, kind) { | |
| const kindVisitor = visitor[kind]; | |
| if (typeof kindVisitor === 'object') { | |
| // { Kind: { enter() {}, leave() {} } } | |
| return kindVisitor; | |
| } else if (typeof kindVisitor === 'function') { | |
| // { Kind() {} } | |
| return { | |
| enter: kindVisitor, | |
| leave: undefined, | |
| }; | |
| } // { enter() {}, leave() {} } | |
| return { | |
| enter: visitor.enter, | |
| leave: visitor.leave, | |
| }; | |
| } | |
| /** | |
| * Given a visitor instance, if it is leaving or not, and a node kind, return | |
| * the function the visitor runtime should call. | |
| * | |
| * @deprecated Please use `getEnterLeaveForKind` instead. Will be removed in v17 | |
| */ | |
| /* c8 ignore next 8 */ | |
| function getVisitFn(visitor, kind, isLeaving) { | |
| const { enter, leave } = getEnterLeaveForKind(visitor, kind); | |
| return isLeaving ? leave : enter; | |
| } | |