123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377 |
- 'use strict';
- 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;
- }
|