···
+
// See: https://github.com/graphql/graphql-js/blob/976d64b/src/language/__tests__/visitor-test.ts
+
import { Kind, parse } from 'graphql';
+
import { visit, visitInParallel, BREAK } from '../visitor';
+
const kitchenSinkQuery: string = String.raw`
+
query queryName($foo: ComplexType, $site: Site = MOBILE) @onQuery {
+
whoever123is: node(id: [123, 456]) {
+
... on User @onInlineFragment {
+
alias: field1(first: 10, after: $foo) @include(if: $foo) {
+
...frag @onFragmentSpread
+
... @skip(unless: $foo) {
+
mutation likeStory @onMutation {
+
like(story: 123) @onField {
+
subscription StoryLikeSubscription(
+
$input: StoryLikeSubscribeInput @onVariableDefinition
+
storyLikeSubscribe(input: $input) {
+
fragment frag on Friend @onFragmentDefinition {
+
unnamed(truthy: true, falsy: false, nullish: null)
+
function checkVisitorFnArgs(ast, args, isEdited = false) {
+
const [node, key, parent, path, ancestors] = args;
+
expect(node).toBeInstanceOf(Object);
+
expect(Object.values(Kind)).toContain(node.kind);
+
const isRoot = key === undefined;
+
expect(node).toEqual(ast);
+
expect(parent).toEqual(undefined);
+
expect(path).toEqual([]);
+
expect(ancestors).toEqual([]);
+
expect(typeof key).toMatch(/number|string/);
+
expect(parent).toHaveProperty([key]);
+
expect(path).toBeInstanceOf(Array);
+
expect(path[path.length - 1]).toEqual(key);
+
expect(ancestors).toBeInstanceOf(Array);
+
expect(ancestors.length).toEqual(path.length - 1);
+
for (let i = 0; i < ancestors.length; ++i) {
+
expect(ancestors[i]).toEqual(currentNode);
+
currentNode = currentNode[path[i]];
+
expect(currentNode).not.toEqual(undefined);
+
// expect(parent).toEqual(currentNode);
+
// expect(parent[key]).toEqual(node);
+
function isNode(node) {
+
return node != null && typeof node.kind === 'string';
+
function getValue(node) {
+
return 'value' in node ? node.value : undefined;
+
describe('Visitor', () => {
+
it('handles empty visitor', () => {
+
const ast = parse('{ a }', { noLocation: true });
+
expect(() => visit(ast, {})).not.toThrow();
+
it('validates path argument', () => {
+
const visited: Array<any> = [];
+
const ast = parse('{ a }', { noLocation: true });
+
enter(_node, _key, _parent, path) {
+
checkVisitorFnArgs(ast, arguments);
+
visited.push(['enter', path.slice()]);
+
leave(_node, _key, _parent, path) {
+
checkVisitorFnArgs(ast, arguments);
+
visited.push(['leave', path.slice()]);
+
expect(visited).toEqual([
+
['enter', ['definitions', 0]],
+
['enter', ['definitions', 0, 'selectionSet']],
+
['enter', ['definitions', 0, 'selectionSet', 'selections', 0]],
+
['enter', ['definitions', 0, 'selectionSet', 'selections', 0, 'name']],
+
['leave', ['definitions', 0, 'selectionSet', 'selections', 0, 'name']],
+
['leave', ['definitions', 0, 'selectionSet', 'selections', 0]],
+
['leave', ['definitions', 0, 'selectionSet']],
+
['leave', ['definitions', 0]],
+
it('validates ancestors argument', () => {
+
const ast = parse('{ a }', { noLocation: true });
+
const visitedNodes: Array<any> = [];
+
enter(node, key, parent, _path, ancestors) {
+
const inArray = typeof key === 'number';
+
visitedNodes.push(parent);
+
visitedNodes.push(node);
+
const expectedAncestors = visitedNodes.slice(0, -2);
+
expect(ancestors).toEqual(expectedAncestors);
+
leave(_node, key, _parent, _path, ancestors) {
+
const expectedAncestors = visitedNodes.slice(0, -2);
+
expect(ancestors).toEqual(expectedAncestors);
+
const inArray = typeof key === 'number';
+
it('allows editing a node both on enter and on leave', () => {
+
const ast = parse('{ a, b, c { a, b, c } }', { noLocation: true });
+
let selectionSet: SelectionSetNode;
+
const editedAST = visit(ast, {
+
checkVisitorFnArgs(ast, arguments);
+
selectionSet = node.selectionSet;
+
checkVisitorFnArgs(ast, arguments, /* isEdited */ true);
+
expect(editedAST).toEqual({
+
it('allows editing the root node on enter and on leave', () => {
+
const ast = parse('{ a, b, c { a, b, c } }', { noLocation: true });
+
const { definitions } = ast;
+
const editedAST = visit(ast, {
+
checkVisitorFnArgs(ast, arguments);
+
checkVisitorFnArgs(ast, arguments, /* isEdited */ true);
+
expect(editedAST).toEqual({
+
it('allows for editing on enter', () => {
+
const ast = parse('{ a, b, c { a, b, c } }', { noLocation: true });
+
const editedAST = visit(ast, {
+
checkVisitorFnArgs(ast, arguments);
+
if (node.kind === 'Field' && node.name.value === 'b') {
+
expect(ast).toEqual(parse('{ a, b, c { a, b, c } }', { noLocation: true }));
+
expect(editedAST).toEqual(
+
parse('{ a, c { a, c } }', { noLocation: true })
+
it('allows for editing on leave', () => {
+
const ast = parse('{ a, b, c { a, b, c } }', { noLocation: true });
+
const editedAST = visit(ast, {
+
checkVisitorFnArgs(ast, arguments, /* isEdited */ true);
+
if (node.kind === 'Field' && node.name.value === 'b') {
+
expect(ast).toEqual(parse('{ a, b, c { a, b, c } }', { noLocation: true }));
+
expect(editedAST).toEqual(
+
parse('{ a, c { a, c } }', { noLocation: true })
+
it('ignores false returned on leave', () => {
+
const ast = parse('{ a, b, c { a, b, c } }', { noLocation: true });
+
const returnedAST = visit(ast, {
+
expect(returnedAST).toEqual(
+
parse('{ a, b, c { a, b, c } }', { noLocation: true })
+
it('visits edited node', () => {
+
let didVisitAddedField;
+
const ast = parse('{ a { x } }', { noLocation: true });
+
checkVisitorFnArgs(ast, arguments, /* isEdited */ true);
+
if (node.kind === 'Field' && node.name.value === 'a') {
+
selectionSet: [addedField, node.selectionSet],
+
if (node === addedField) {
+
didVisitAddedField = true;
+
expect(didVisitAddedField).toEqual(true);
+
it('allows skipping a sub-tree', () => {
+
const visited: Array<any> = [];
+
const ast = parse('{ a, b { x }, c }', { noLocation: true });
+
checkVisitorFnArgs(ast, arguments);
+
visited.push(['enter', node.kind, getValue(node)]);
+
if (node.kind === 'Field' && node.name.value === 'b') {
+
checkVisitorFnArgs(ast, arguments);
+
visited.push(['leave', node.kind, getValue(node)]);
+
expect(visited).toEqual([
+
['enter', 'Document', undefined],
+
['enter', 'OperationDefinition', undefined],
+
['enter', 'SelectionSet', undefined],
+
['enter', 'Field', undefined],
+
['enter', 'Name', 'a'],
+
['leave', 'Name', 'a'],
+
['leave', 'Field', undefined],
+
['enter', 'Field', undefined],
+
['enter', 'Field', undefined],
+
['enter', 'Name', 'c'],
+
['leave', 'Name', 'c'],
+
['leave', 'Field', undefined],
+
['leave', 'SelectionSet', undefined],
+
['leave', 'OperationDefinition', undefined],
+
['leave', 'Document', undefined],
+
it('allows early exit while visiting', () => {
+
const visited: Array<any> = [];
+
const ast = parse('{ a, b { x }, c }', { noLocation: true });
+
checkVisitorFnArgs(ast, arguments);
+
visited.push(['enter', node.kind, getValue(node)]);
+
if (node.kind === 'Name' && node.value === 'x') {
+
checkVisitorFnArgs(ast, arguments);
+
visited.push(['leave', node.kind, getValue(node)]);
+
expect(visited).toEqual([
+
['enter', 'Document', undefined],
+
['enter', 'OperationDefinition', undefined],
+
['enter', 'SelectionSet', undefined],
+
['enter', 'Field', undefined],
+
['enter', 'Name', 'a'],
+
['leave', 'Name', 'a'],
+
['leave', 'Field', undefined],
+
['enter', 'Field', undefined],
+
['enter', 'Name', 'b'],
+
['leave', 'Name', 'b'],
+
['enter', 'SelectionSet', undefined],
+
['enter', 'Field', undefined],
+
['enter', 'Name', 'x'],
+
it('allows early exit while leaving', () => {
+
const visited: Array<any> = [];
+
const ast = parse('{ a, b { x }, c }', { noLocation: true });
+
checkVisitorFnArgs(ast, arguments);
+
visited.push(['enter', node.kind, getValue(node)]);
+
checkVisitorFnArgs(ast, arguments);
+
visited.push(['leave', node.kind, getValue(node)]);
+
if (node.kind === 'Name' && node.value === 'x') {
+
expect(visited).toEqual([
+
['enter', 'Document', undefined],
+
['enter', 'OperationDefinition', undefined],
+
['enter', 'SelectionSet', undefined],
+
['enter', 'Field', undefined],
+
['enter', 'Name', 'a'],
+
['leave', 'Name', 'a'],
+
['leave', 'Field', undefined],
+
['enter', 'Field', undefined],
+
['enter', 'Name', 'b'],
+
['leave', 'Name', 'b'],
+
['enter', 'SelectionSet', undefined],
+
['enter', 'Field', undefined],
+
['enter', 'Name', 'x'],
+
['leave', 'Name', 'x'],
+
it('allows a named functions visitor API', () => {
+
const visited: Array<any> = [];
+
const ast = parse('{ a, b { x }, c }', { noLocation: true });
+
checkVisitorFnArgs(ast, arguments);
+
visited.push(['enter', node.kind, getValue(node)]);
+
checkVisitorFnArgs(ast, arguments);
+
visited.push(['enter', node.kind, getValue(node)]);
+
checkVisitorFnArgs(ast, arguments);
+
visited.push(['leave', node.kind, getValue(node)]);
+
expect(visited).toEqual([
+
['enter', 'SelectionSet', undefined],
+
['enter', 'Name', 'a'],
+
['enter', 'Name', 'b'],
+
['enter', 'SelectionSet', undefined],
+
['enter', 'Name', 'x'],
+
['leave', 'SelectionSet', undefined],
+
['enter', 'Name', 'c'],
+
['leave', 'SelectionSet', undefined],
+
it('visits kitchen sink', () => {
+
const ast = parse(kitchenSinkQuery);
+
const visited: Array<any> = [];
+
const argsStack: Array<any> = [];
+
enter(node, key, parent) {
+
isNode(parent) ? parent.kind : undefined,
+
checkVisitorFnArgs(ast, arguments);
+
argsStack.push([...arguments]);
+
leave(node, key, parent) {
+
isNode(parent) ? parent.kind : undefined,
+
expect(argsStack.pop()).toEqual([...arguments]);
+
expect(argsStack).toEqual([]);
+
expect(visited).toEqual([
+
['enter', 'Document', undefined, undefined],
+
['enter', 'OperationDefinition', 0, undefined],
+
['enter', 'Name', 'name', 'OperationDefinition'],
+
['leave', 'Name', 'name', 'OperationDefinition'],
+
['enter', 'VariableDefinition', 0, undefined],
+
['enter', 'Variable', 'variable', 'VariableDefinition'],
+
['enter', 'Name', 'name', 'Variable'],
+
['leave', 'Name', 'name', 'Variable'],
+
['leave', 'Variable', 'variable', 'VariableDefinition'],
+
['enter', 'NamedType', 'type', 'VariableDefinition'],
+
['enter', 'Name', 'name', 'NamedType'],
+
['leave', 'Name', 'name', 'NamedType'],
+
['leave', 'NamedType', 'type', 'VariableDefinition'],
+
['leave', 'VariableDefinition', 0, undefined],
+
['enter', 'VariableDefinition', 1, undefined],
+
['enter', 'Variable', 'variable', 'VariableDefinition'],
+
['enter', 'Name', 'name', 'Variable'],
+
['leave', 'Name', 'name', 'Variable'],
+
['leave', 'Variable', 'variable', 'VariableDefinition'],
+
['enter', 'NamedType', 'type', 'VariableDefinition'],
+
['enter', 'Name', 'name', 'NamedType'],
+
['leave', 'Name', 'name', 'NamedType'],
+
['leave', 'NamedType', 'type', 'VariableDefinition'],
+
['enter', 'EnumValue', 'defaultValue', 'VariableDefinition'],
+
['leave', 'EnumValue', 'defaultValue', 'VariableDefinition'],
+
['leave', 'VariableDefinition', 1, undefined],
+
['enter', 'Directive', 0, undefined],
+
['enter', 'Name', 'name', 'Directive'],
+
['leave', 'Name', 'name', 'Directive'],
+
['leave', 'Directive', 0, undefined],
+
['enter', 'SelectionSet', 'selectionSet', 'OperationDefinition'],
+
['enter', 'Field', 0, undefined],
+
['enter', 'Name', 'alias', 'Field'],
+
['leave', 'Name', 'alias', 'Field'],
+
['enter', 'Name', 'name', 'Field'],
+
['leave', 'Name', 'name', 'Field'],
+
['enter', 'Argument', 0, undefined],
+
['enter', 'Name', 'name', 'Argument'],
+
['leave', 'Name', 'name', 'Argument'],
+
['enter', 'ListValue', 'value', 'Argument'],
+
['enter', 'IntValue', 0, undefined],
+
['leave', 'IntValue', 0, undefined],
+
['enter', 'IntValue', 1, undefined],
+
['leave', 'IntValue', 1, undefined],
+
['leave', 'ListValue', 'value', 'Argument'],
+
['leave', 'Argument', 0, undefined],
+
['enter', 'SelectionSet', 'selectionSet', 'Field'],
+
['enter', 'Field', 0, undefined],
+
['enter', 'Name', 'name', 'Field'],
+
['leave', 'Name', 'name', 'Field'],
+
['leave', 'Field', 0, undefined],
+
['enter', 'InlineFragment', 1, undefined],
+
['enter', 'NamedType', 'typeCondition', 'InlineFragment'],
+
['enter', 'Name', 'name', 'NamedType'],
+
['leave', 'Name', 'name', 'NamedType'],
+
['leave', 'NamedType', 'typeCondition', 'InlineFragment'],
+
['enter', 'Directive', 0, undefined],
+
['enter', 'Name', 'name', 'Directive'],
+
['leave', 'Name', 'name', 'Directive'],
+
['leave', 'Directive', 0, undefined],
+
['enter', 'SelectionSet', 'selectionSet', 'InlineFragment'],
+
['enter', 'Field', 0, undefined],
+
['enter', 'Name', 'name', 'Field'],
+
['leave', 'Name', 'name', 'Field'],
+
['enter', 'SelectionSet', 'selectionSet', 'Field'],
+
['enter', 'Field', 0, undefined],
+
['enter', 'Name', 'name', 'Field'],
+
['leave', 'Name', 'name', 'Field'],
+
['leave', 'Field', 0, undefined],
+
['enter', 'Field', 1, undefined],
+
['enter', 'Name', 'alias', 'Field'],
+
['leave', 'Name', 'alias', 'Field'],
+
['enter', 'Name', 'name', 'Field'],
+
['leave', 'Name', 'name', 'Field'],
+
['enter', 'Argument', 0, undefined],
+
['enter', 'Name', 'name', 'Argument'],
+
['leave', 'Name', 'name', 'Argument'],
+
['enter', 'IntValue', 'value', 'Argument'],
+
['leave', 'IntValue', 'value', 'Argument'],
+
['leave', 'Argument', 0, undefined],
+
['enter', 'Argument', 1, undefined],
+
['enter', 'Name', 'name', 'Argument'],
+
['leave', 'Name', 'name', 'Argument'],
+
['enter', 'Variable', 'value', 'Argument'],
+
['enter', 'Name', 'name', 'Variable'],
+
['leave', 'Name', 'name', 'Variable'],
+
['leave', 'Variable', 'value', 'Argument'],
+
['leave', 'Argument', 1, undefined],
+
['enter', 'Directive', 0, undefined],
+
['enter', 'Name', 'name', 'Directive'],
+
['leave', 'Name', 'name', 'Directive'],
+
['enter', 'Argument', 0, undefined],
+
['enter', 'Name', 'name', 'Argument'],
+
['leave', 'Name', 'name', 'Argument'],
+
['enter', 'Variable', 'value', 'Argument'],
+
['enter', 'Name', 'name', 'Variable'],
+
['leave', 'Name', 'name', 'Variable'],
+
['leave', 'Variable', 'value', 'Argument'],
+
['leave', 'Argument', 0, undefined],
+
['leave', 'Directive', 0, undefined],
+
['enter', 'SelectionSet', 'selectionSet', 'Field'],
+
['enter', 'Field', 0, undefined],
+
['enter', 'Name', 'name', 'Field'],
+
['leave', 'Name', 'name', 'Field'],
+
['leave', 'Field', 0, undefined],
+
['enter', 'FragmentSpread', 1, undefined],
+
['enter', 'Name', 'name', 'FragmentSpread'],
+
['leave', 'Name', 'name', 'FragmentSpread'],
+
['enter', 'Directive', 0, undefined],
+
['enter', 'Name', 'name', 'Directive'],
+
['leave', 'Name', 'name', 'Directive'],
+
['leave', 'Directive', 0, undefined],
+
['leave', 'FragmentSpread', 1, undefined],
+
['leave', 'SelectionSet', 'selectionSet', 'Field'],
+
['leave', 'Field', 1, undefined],
+
['leave', 'SelectionSet', 'selectionSet', 'Field'],
+
['leave', 'Field', 0, undefined],
+
['leave', 'SelectionSet', 'selectionSet', 'InlineFragment'],
+
['leave', 'InlineFragment', 1, undefined],
+
['enter', 'InlineFragment', 2, undefined],
+
['enter', 'Directive', 0, undefined],
+
['enter', 'Name', 'name', 'Directive'],
+
['leave', 'Name', 'name', 'Directive'],
+
['enter', 'Argument', 0, undefined],
+
['enter', 'Name', 'name', 'Argument'],
+
['leave', 'Name', 'name', 'Argument'],
+
['enter', 'Variable', 'value', 'Argument'],
+
['enter', 'Name', 'name', 'Variable'],
+
['leave', 'Name', 'name', 'Variable'],
+
['leave', 'Variable', 'value', 'Argument'],
+
['leave', 'Argument', 0, undefined],
+
['leave', 'Directive', 0, undefined],
+
['enter', 'SelectionSet', 'selectionSet', 'InlineFragment'],
+
['enter', 'Field', 0, undefined],
+
['enter', 'Name', 'name', 'Field'],
+
['leave', 'Name', 'name', 'Field'],
+
['leave', 'Field', 0, undefined],
+
['leave', 'SelectionSet', 'selectionSet', 'InlineFragment'],
+
['leave', 'InlineFragment', 2, undefined],
+
['enter', 'InlineFragment', 3, undefined],
+
['enter', 'SelectionSet', 'selectionSet', 'InlineFragment'],
+
['enter', 'Field', 0, undefined],
+
['enter', 'Name', 'name', 'Field'],
+
['leave', 'Name', 'name', 'Field'],
+
['leave', 'Field', 0, undefined],
+
['leave', 'SelectionSet', 'selectionSet', 'InlineFragment'],
+
['leave', 'InlineFragment', 3, undefined],
+
['leave', 'SelectionSet', 'selectionSet', 'Field'],
+
['leave', 'Field', 0, undefined],
+
['leave', 'SelectionSet', 'selectionSet', 'OperationDefinition'],
+
['leave', 'OperationDefinition', 0, undefined],
+
['enter', 'OperationDefinition', 1, undefined],
+
['enter', 'Name', 'name', 'OperationDefinition'],
+
['leave', 'Name', 'name', 'OperationDefinition'],
+
['enter', 'Directive', 0, undefined],
+
['enter', 'Name', 'name', 'Directive'],
+
['leave', 'Name', 'name', 'Directive'],
+
['leave', 'Directive', 0, undefined],
+
['enter', 'SelectionSet', 'selectionSet', 'OperationDefinition'],
+
['enter', 'Field', 0, undefined],
+
['enter', 'Name', 'name', 'Field'],
+
['leave', 'Name', 'name', 'Field'],
+
['enter', 'Argument', 0, undefined],
+
['enter', 'Name', 'name', 'Argument'],
+
['leave', 'Name', 'name', 'Argument'],
+
['enter', 'IntValue', 'value', 'Argument'],
+
['leave', 'IntValue', 'value', 'Argument'],
+
['leave', 'Argument', 0, undefined],
+
['enter', 'Directive', 0, undefined],
+
['enter', 'Name', 'name', 'Directive'],
+
['leave', 'Name', 'name', 'Directive'],
+
['leave', 'Directive', 0, undefined],
+
['enter', 'SelectionSet', 'selectionSet', 'Field'],
+
['enter', 'Field', 0, undefined],
+
['enter', 'Name', 'name', 'Field'],
+
['leave', 'Name', 'name', 'Field'],
+
['enter', 'SelectionSet', 'selectionSet', 'Field'],
+
['enter', 'Field', 0, undefined],
+
['enter', 'Name', 'name', 'Field'],
+
['leave', 'Name', 'name', 'Field'],
+
['enter', 'Directive', 0, undefined],
+
['enter', 'Name', 'name', 'Directive'],
+
['leave', 'Name', 'name', 'Directive'],
+
['leave', 'Directive', 0, undefined],
+
['leave', 'Field', 0, undefined],
+
['leave', 'SelectionSet', 'selectionSet', 'Field'],
+
['leave', 'Field', 0, undefined],
+
['leave', 'SelectionSet', 'selectionSet', 'Field'],
+
['leave', 'Field', 0, undefined],
+
['leave', 'SelectionSet', 'selectionSet', 'OperationDefinition'],
+
['leave', 'OperationDefinition', 1, undefined],
+
['enter', 'OperationDefinition', 2, undefined],
+
['enter', 'Name', 'name', 'OperationDefinition'],
+
['leave', 'Name', 'name', 'OperationDefinition'],
+
['enter', 'VariableDefinition', 0, undefined],
+
['enter', 'Variable', 'variable', 'VariableDefinition'],
+
['enter', 'Name', 'name', 'Variable'],
+
['leave', 'Name', 'name', 'Variable'],
+
['leave', 'Variable', 'variable', 'VariableDefinition'],
+
['enter', 'NamedType', 'type', 'VariableDefinition'],
+
['enter', 'Name', 'name', 'NamedType'],
+
['leave', 'Name', 'name', 'NamedType'],
+
['leave', 'NamedType', 'type', 'VariableDefinition'],
+
['enter', 'Directive', 0, undefined],
+
['enter', 'Name', 'name', 'Directive'],
+
['leave', 'Name', 'name', 'Directive'],
+
['leave', 'Directive', 0, undefined],
+
['leave', 'VariableDefinition', 0, undefined],
+
['enter', 'Directive', 0, undefined],
+
['enter', 'Name', 'name', 'Directive'],
+
['leave', 'Name', 'name', 'Directive'],
+
['leave', 'Directive', 0, undefined],
+
['enter', 'SelectionSet', 'selectionSet', 'OperationDefinition'],
+
['enter', 'Field', 0, undefined],
+
['enter', 'Name', 'name', 'Field'],
+
['leave', 'Name', 'name', 'Field'],
+
['enter', 'Argument', 0, undefined],
+
['enter', 'Name', 'name', 'Argument'],
+
['leave', 'Name', 'name', 'Argument'],
+
['enter', 'Variable', 'value', 'Argument'],
+
['enter', 'Name', 'name', 'Variable'],
+
['leave', 'Name', 'name', 'Variable'],
+
['leave', 'Variable', 'value', 'Argument'],
+
['leave', 'Argument', 0, undefined],
+
['enter', 'SelectionSet', 'selectionSet', 'Field'],
+
['enter', 'Field', 0, undefined],
+
['enter', 'Name', 'name', 'Field'],
+
['leave', 'Name', 'name', 'Field'],
+
['enter', 'SelectionSet', 'selectionSet', 'Field'],
+
['enter', 'Field', 0, undefined],
+
['enter', 'Name', 'name', 'Field'],
+
['leave', 'Name', 'name', 'Field'],
+
['enter', 'SelectionSet', 'selectionSet', 'Field'],
+
['enter', 'Field', 0, undefined],
+
['enter', 'Name', 'name', 'Field'],
+
['leave', 'Name', 'name', 'Field'],
+
['leave', 'Field', 0, undefined],
+
['leave', 'SelectionSet', 'selectionSet', 'Field'],
+
['leave', 'Field', 0, undefined],
+
['enter', 'Field', 1, undefined],
+
['enter', 'Name', 'name', 'Field'],
+
['leave', 'Name', 'name', 'Field'],
+
['enter', 'SelectionSet', 'selectionSet', 'Field'],
+
['enter', 'Field', 0, undefined],
+
['enter', 'Name', 'name', 'Field'],
+
['leave', 'Name', 'name', 'Field'],
+
['leave', 'Field', 0, undefined],
+
['leave', 'SelectionSet', 'selectionSet', 'Field'],
+
['leave', 'Field', 1, undefined],
+
['leave', 'SelectionSet', 'selectionSet', 'Field'],
+
['leave', 'Field', 0, undefined],
+
['leave', 'SelectionSet', 'selectionSet', 'Field'],
+
['leave', 'Field', 0, undefined],
+
['leave', 'SelectionSet', 'selectionSet', 'OperationDefinition'],
+
['leave', 'OperationDefinition', 2, undefined],
+
['enter', 'FragmentDefinition', 3, undefined],
+
['enter', 'Name', 'name', 'FragmentDefinition'],
+
['leave', 'Name', 'name', 'FragmentDefinition'],
+
['enter', 'NamedType', 'typeCondition', 'FragmentDefinition'],
+
['enter', 'Name', 'name', 'NamedType'],
+
['leave', 'Name', 'name', 'NamedType'],
+
['leave', 'NamedType', 'typeCondition', 'FragmentDefinition'],
+
['enter', 'Directive', 0, undefined],
+
['enter', 'Name', 'name', 'Directive'],
+
['leave', 'Name', 'name', 'Directive'],
+
['leave', 'Directive', 0, undefined],
+
['enter', 'SelectionSet', 'selectionSet', 'FragmentDefinition'],
+
['enter', 'Field', 0, undefined],
+
['enter', 'Name', 'name', 'Field'],
+
['leave', 'Name', 'name', 'Field'],
+
['enter', 'Argument', 0, undefined],
+
['enter', 'Name', 'name', 'Argument'],
+
['leave', 'Name', 'name', 'Argument'],
+
['enter', 'Variable', 'value', 'Argument'],
+
['enter', 'Name', 'name', 'Variable'],
+
['leave', 'Name', 'name', 'Variable'],
+
['leave', 'Variable', 'value', 'Argument'],
+
['leave', 'Argument', 0, undefined],
+
['enter', 'Argument', 1, undefined],
+
['enter', 'Name', 'name', 'Argument'],
+
['leave', 'Name', 'name', 'Argument'],
+
['enter', 'Variable', 'value', 'Argument'],
+
['enter', 'Name', 'name', 'Variable'],
+
['leave', 'Name', 'name', 'Variable'],
+
['leave', 'Variable', 'value', 'Argument'],
+
['leave', 'Argument', 1, undefined],
+
['enter', 'Argument', 2, undefined],
+
['enter', 'Name', 'name', 'Argument'],
+
['leave', 'Name', 'name', 'Argument'],
+
['enter', 'ObjectValue', 'value', 'Argument'],
+
['enter', 'ObjectField', 0, undefined],
+
['enter', 'Name', 'name', 'ObjectField'],
+
['leave', 'Name', 'name', 'ObjectField'],
+
['enter', 'StringValue', 'value', 'ObjectField'],
+
['leave', 'StringValue', 'value', 'ObjectField'],
+
['leave', 'ObjectField', 0, undefined],
+
['enter', 'ObjectField', 1, undefined],
+
['enter', 'Name', 'name', 'ObjectField'],
+
['leave', 'Name', 'name', 'ObjectField'],
+
['enter', 'StringValue', 'value', 'ObjectField'],
+
['leave', 'StringValue', 'value', 'ObjectField'],
+
['leave', 'ObjectField', 1, undefined],
+
['leave', 'ObjectValue', 'value', 'Argument'],
+
['leave', 'Argument', 2, undefined],
+
['leave', 'Field', 0, undefined],
+
['leave', 'SelectionSet', 'selectionSet', 'FragmentDefinition'],
+
['leave', 'FragmentDefinition', 3, undefined],
+
['enter', 'OperationDefinition', 4, undefined],
+
['enter', 'SelectionSet', 'selectionSet', 'OperationDefinition'],
+
['enter', 'Field', 0, undefined],
+
['enter', 'Name', 'name', 'Field'],
+
['leave', 'Name', 'name', 'Field'],
+
['enter', 'Argument', 0, undefined],
+
['enter', 'Name', 'name', 'Argument'],
+
['leave', 'Name', 'name', 'Argument'],
+
['enter', 'BooleanValue', 'value', 'Argument'],
+
['leave', 'BooleanValue', 'value', 'Argument'],
+
['leave', 'Argument', 0, undefined],
+
['enter', 'Argument', 1, undefined],
+
['enter', 'Name', 'name', 'Argument'],
+
['leave', 'Name', 'name', 'Argument'],
+
['enter', 'BooleanValue', 'value', 'Argument'],
+
['leave', 'BooleanValue', 'value', 'Argument'],
+
['leave', 'Argument', 1, undefined],
+
['enter', 'Argument', 2, undefined],
+
['enter', 'Name', 'name', 'Argument'],
+
['leave', 'Name', 'name', 'Argument'],
+
['enter', 'NullValue', 'value', 'Argument'],
+
['leave', 'NullValue', 'value', 'Argument'],
+
['leave', 'Argument', 2, undefined],
+
['leave', 'Field', 0, undefined],
+
['enter', 'Field', 1, undefined],
+
['enter', 'Name', 'name', 'Field'],
+
['leave', 'Name', 'name', 'Field'],
+
['leave', 'Field', 1, undefined],
+
['leave', 'SelectionSet', 'selectionSet', 'OperationDefinition'],
+
['leave', 'OperationDefinition', 4, undefined],
+
['enter', 'OperationDefinition', 5, undefined],
+
['enter', 'SelectionSet', 'selectionSet', 'OperationDefinition'],
+
['enter', 'Field', 0, undefined],
+
['enter', 'Name', 'name', 'Field'],
+
['leave', 'Name', 'name', 'Field'],
+
['leave', 'Field', 0, undefined],
+
['leave', 'SelectionSet', 'selectionSet', 'OperationDefinition'],
+
['leave', 'OperationDefinition', 5, undefined],
+
['leave', 'Document', undefined, undefined],
+
// TODO: Verify discrepancies
+
describe('visitInParallel', () => {
+
// Note: nearly identical to the above test of the same test but
+
// using visitInParallel.
+
it('allows skipping a sub-tree', () => {
+
const visited: Array<any> = [];
+
const ast = parse('{ a, b { x }, c }');
+
checkVisitorFnArgs(ast, arguments);
+
visited.push(['enter', node.kind, getValue(node)]);
+
if (node.kind === 'Field' && node.name.value === 'b') {
+
checkVisitorFnArgs(ast, arguments);
+
visited.push(['leave', node.kind, getValue(node)]);
+
expect(visited).toEqual([
+
['enter', 'Document', undefined],
+
['enter', 'OperationDefinition', undefined],
+
['enter', 'SelectionSet', undefined],
+
['enter', 'Field', undefined],
+
['enter', 'Name', 'a'],
+
['leave', 'Name', 'a'],
+
['leave', 'Field', undefined],
+
['enter', 'Field', undefined],
+
['enter', 'Field', undefined],
+
['enter', 'Name', 'c'],
+
['leave', 'Name', 'c'],
+
['leave', 'Field', undefined],
+
['leave', 'SelectionSet', undefined],
+
['leave', 'OperationDefinition', undefined],
+
['leave', 'Document', undefined],
+
it('allows skipping different sub-trees', () => {
+
const visited: Array<any> = [];
+
const ast = parse('{ a { x }, b { y} }');
+
checkVisitorFnArgs(ast, arguments);
+
visited.push(['no-a', 'enter', node.kind, getValue(node)]);
+
if (node.kind === 'Field' && node.name.value === 'a') {
+
checkVisitorFnArgs(ast, arguments);
+
visited.push(['no-a', 'leave', node.kind, getValue(node)]);
+
checkVisitorFnArgs(ast, arguments);
+
visited.push(['no-b', 'enter', node.kind, getValue(node)]);
+
if (node.kind === 'Field' && node.name.value === 'b') {
+
checkVisitorFnArgs(ast, arguments);
+
visited.push(['no-b', 'leave', node.kind, getValue(node)]);
+
expect(visited).toEqual([
+
['no-a', 'enter', 'Document', undefined],
+
['no-b', 'enter', 'Document', undefined],
+
['no-a', 'enter', 'OperationDefinition', undefined],
+
['no-b', 'enter', 'OperationDefinition', undefined],
+
['no-a', 'enter', 'SelectionSet', undefined],
+
['no-b', 'enter', 'SelectionSet', undefined],
+
['no-a', 'enter', 'Field', undefined],
+
['no-b', 'enter', 'Field', undefined],
+
['no-b', 'enter', 'Name', 'a'],
+
['no-b', 'leave', 'Name', 'a'],
+
['no-b', 'enter', 'SelectionSet', undefined],
+
['no-b', 'enter', 'Field', undefined],
+
['no-b', 'enter', 'Name', 'x'],
+
['no-b', 'leave', 'Name', 'x'],
+
['no-b', 'leave', 'Field', undefined],
+
['no-b', 'leave', 'SelectionSet', undefined],
+
['no-b', 'leave', 'Field', undefined],
+
['no-a', 'enter', 'Field', undefined],
+
['no-b', 'enter', 'Field', undefined],
+
['no-a', 'enter', 'Name', 'b'],
+
['no-a', 'leave', 'Name', 'b'],
+
['no-a', 'enter', 'SelectionSet', undefined],
+
['no-a', 'enter', 'Field', undefined],
+
['no-a', 'enter', 'Name', 'y'],
+
['no-a', 'leave', 'Name', 'y'],
+
['no-a', 'leave', 'Field', undefined],
+
['no-a', 'leave', 'SelectionSet', undefined],
+
['no-a', 'leave', 'Field', undefined],
+
['no-a', 'leave', 'SelectionSet', undefined],
+
['no-b', 'leave', 'SelectionSet', undefined],
+
['no-a', 'leave', 'OperationDefinition', undefined],
+
['no-b', 'leave', 'OperationDefinition', undefined],
+
['no-a', 'leave', 'Document', undefined],
+
['no-b', 'leave', 'Document', undefined],
+
// Note: nearly identical to the above test of the same test but
+
// using visitInParallel.
+
it('allows early exit while visiting', () => {
+
const visited: Array<any> = [];
+
const ast = parse('{ a, b { x }, c }');
+
checkVisitorFnArgs(ast, arguments);
+
visited.push(['enter', node.kind, getValue(node)]);
+
if (node.kind === 'Name' && node.value === 'x') {
+
checkVisitorFnArgs(ast, arguments);
+
visited.push(['leave', node.kind, getValue(node)]);
+
expect(visited).toEqual([
+
['enter', 'Document', undefined],
+
['enter', 'OperationDefinition', undefined],
+
['enter', 'SelectionSet', undefined],
+
['enter', 'Field', undefined],
+
['enter', 'Name', 'a'],
+
['leave', 'Name', 'a'],
+
['leave', 'Field', undefined],
+
['enter', 'Field', undefined],
+
['enter', 'Name', 'b'],
+
['leave', 'Name', 'b'],
+
['enter', 'SelectionSet', undefined],
+
['enter', 'Field', undefined],
+
['enter', 'Name', 'x'],
+
it('allows early exit from different points', () => {
+
const visited: Array<any> = [];
+
const ast = parse('{ a { y }, b { x } }');
+
checkVisitorFnArgs(ast, arguments);
+
visited.push(['break-a', 'enter', node.kind, getValue(node)]);
+
if (node.kind === 'Name' && node.value === 'a') {
+
// istanbul ignore next (Never called and used as a placeholder)
+
expect.fail('Should not be called');
+
checkVisitorFnArgs(ast, arguments);
+
visited.push(['break-b', 'enter', node.kind, getValue(node)]);
+
if (node.kind === 'Name' && node.value === 'b') {
+
checkVisitorFnArgs(ast, arguments);
+
visited.push(['break-b', 'leave', node.kind, getValue(node)]);
+
expect(visited).toEqual([
+
['break-a', 'enter', 'Document', undefined],
+
['break-b', 'enter', 'Document', undefined],
+
['break-a', 'enter', 'OperationDefinition', undefined],
+
['break-b', 'enter', 'OperationDefinition', undefined],
+
['break-a', 'enter', 'SelectionSet', undefined],
+
['break-b', 'enter', 'SelectionSet', undefined],
+
['break-a', 'enter', 'Field', undefined],
+
['break-b', 'enter', 'Field', undefined],
+
['break-a', 'enter', 'Name', 'a'],
+
['break-b', 'enter', 'Name', 'a'],
+
['break-b', 'leave', 'Name', 'a'],
+
['break-b', 'enter', 'SelectionSet', undefined],
+
['break-b', 'enter', 'Field', undefined],
+
['break-b', 'enter', 'Name', 'y'],
+
['break-b', 'leave', 'Name', 'y'],
+
['break-b', 'leave', 'Field', undefined],
+
['break-b', 'leave', 'SelectionSet', undefined],
+
['break-b', 'leave', 'Field', undefined],
+
['break-b', 'enter', 'Field', undefined],
+
['break-b', 'enter', 'Name', 'b'],
+
// Note: nearly identical to the above test of the same test but
+
// using visitInParallel.
+
it('allows early exit while leaving', () => {
+
const visited: Array<any> = [];
+
const ast = parse('{ a, b { x }, c }');
+
checkVisitorFnArgs(ast, arguments);
+
visited.push(['enter', node.kind, getValue(node)]);
+
checkVisitorFnArgs(ast, arguments);
+
visited.push(['leave', node.kind, getValue(node)]);
+
if (node.kind === 'Name' && node.value === 'x') {
+
expect(visited).toEqual([
+
['enter', 'Document', undefined],
+
['enter', 'OperationDefinition', undefined],
+
['enter', 'SelectionSet', undefined],
+
['enter', 'Field', undefined],
+
['enter', 'Name', 'a'],
+
['leave', 'Name', 'a'],
+
['leave', 'Field', undefined],
+
['enter', 'Field', undefined],
+
['enter', 'Name', 'b'],
+
['leave', 'Name', 'b'],
+
['enter', 'SelectionSet', undefined],
+
['enter', 'Field', undefined],
+
['enter', 'Name', 'x'],
+
['leave', 'Name', 'x'],
+
it('allows early exit from leaving different points', () => {
+
const visited: Array<any> = [];
+
const ast = parse('{ a { y }, b { x } }');
+
checkVisitorFnArgs(ast, arguments);
+
visited.push(['break-a', 'enter', node.kind, getValue(node)]);
+
checkVisitorFnArgs(ast, arguments);
+
visited.push(['break-a', 'leave', node.kind, getValue(node)]);
+
if (node.kind === 'Field' && node.name.value === 'a') {
+
checkVisitorFnArgs(ast, arguments);
+
visited.push(['break-b', 'enter', node.kind, getValue(node)]);
+
checkVisitorFnArgs(ast, arguments);
+
visited.push(['break-b', 'leave', node.kind, getValue(node)]);
+
if (node.kind === 'Field' && node.name.value === 'b') {
+
expect(visited).toEqual([
+
['break-a', 'enter', 'Document', undefined],
+
['break-b', 'enter', 'Document', undefined],
+
['break-a', 'enter', 'OperationDefinition', undefined],
+
['break-b', 'enter', 'OperationDefinition', undefined],
+
['break-a', 'enter', 'SelectionSet', undefined],
+
['break-b', 'enter', 'SelectionSet', undefined],
+
['break-a', 'enter', 'Field', undefined],
+
['break-b', 'enter', 'Field', undefined],
+
['break-a', 'enter', 'Name', 'a'],
+
['break-b', 'enter', 'Name', 'a'],
+
['break-a', 'leave', 'Name', 'a'],
+
['break-b', 'leave', 'Name', 'a'],
+
['break-a', 'enter', 'SelectionSet', undefined],
+
['break-b', 'enter', 'SelectionSet', undefined],
+
['break-a', 'enter', 'Field', undefined],
+
['break-b', 'enter', 'Field', undefined],
+
['break-a', 'enter', 'Name', 'y'],
+
['break-b', 'enter', 'Name', 'y'],
+
['break-a', 'leave', 'Name', 'y'],
+
['break-b', 'leave', 'Name', 'y'],
+
['break-a', 'leave', 'Field', undefined],
+
['break-b', 'leave', 'Field', undefined],
+
['break-a', 'leave', 'SelectionSet', undefined],
+
['break-b', 'leave', 'SelectionSet', undefined],
+
['break-a', 'leave', 'Field', undefined],
+
['break-b', 'leave', 'Field', undefined],
+
['break-b', 'enter', 'Field', undefined],
+
['break-b', 'enter', 'Name', 'b'],
+
['break-b', 'leave', 'Name', 'b'],
+
['break-b', 'enter', 'SelectionSet', undefined],
+
['break-b', 'enter', 'Field', undefined],
+
['break-b', 'enter', 'Name', 'x'],
+
['break-b', 'leave', 'Name', 'x'],
+
['break-b', 'leave', 'Field', undefined],
+
['break-b', 'leave', 'SelectionSet', undefined],
+
['break-b', 'leave', 'Field', undefined],
+
it('allows for editing on enter', () => {
+
const visited: Array<any> = [];
+
const ast = parse('{ a, b, c { a, b, c } }', { noLocation: true });
+
const editedAST = visit(
+
checkVisitorFnArgs(ast, arguments);
+
if (node.kind === 'Field' && node.name.value === 'b') {
+
checkVisitorFnArgs(ast, arguments);
+
visited.push(['enter', node.kind, getValue(node)]);
+
checkVisitorFnArgs(ast, arguments, /* isEdited */ true);
+
visited.push(['leave', node.kind, getValue(node)]);
+
parse('{ a, b, c { a, b, c } }', { noLocation: true })
+
expect(editedAST).toEqual(
+
parse('{ a, c { a, c } }', { noLocation: true })
+
expect(visited).toEqual([
+
['enter', 'Document', undefined],
+
['enter', 'OperationDefinition', undefined],
+
['enter', 'SelectionSet', undefined],
+
['enter', 'Field', undefined],
+
['enter', 'Name', 'a'],
+
['leave', 'Name', 'a'],
+
['leave', 'Field', undefined],
+
['enter', 'Field', undefined],
+
['enter', 'Name', 'c'],
+
['leave', 'Name', 'c'],
+
['enter', 'SelectionSet', undefined],
+
['enter', 'Field', undefined],
+
['enter', 'Name', 'a'],
+
['leave', 'Name', 'a'],
+
['leave', 'Field', undefined],
+
['enter', 'Field', undefined],
+
['enter', 'Name', 'c'],
+
['leave', 'Name', 'c'],
+
['leave', 'Field', undefined],
+
['leave', 'SelectionSet', undefined],
+
['leave', 'Field', undefined],
+
['leave', 'SelectionSet', undefined],
+
['leave', 'OperationDefinition', undefined],
+
['leave', 'Document', undefined],
+
it('allows for editing on leave', () => {
+
const visited: Array<any> = [];
+
const ast = parse('{ a, b, c { a, b, c } }', { noLocation: true });
+
const editedAST = visit(
+
checkVisitorFnArgs(ast, arguments, /* isEdited */ true);
+
if (node.kind === 'Field' && node.name.value === 'b') {
+
checkVisitorFnArgs(ast, arguments);
+
visited.push(['enter', node.kind, getValue(node)]);
+
checkVisitorFnArgs(ast, arguments, /* isEdited */ true);
+
visited.push(['leave', node.kind, getValue(node)]);
+
parse('{ a, b, c { a, b, c } }', { noLocation: true })
+
expect(editedAST).toEqual(
+
parse('{ a, c { a, c } }', { noLocation: true })
+
expect(visited).toEqual([
+
['enter', 'Document', undefined],
+
['enter', 'OperationDefinition', undefined],
+
['enter', 'SelectionSet', undefined],
+
['enter', 'Field', undefined],
+
['enter', 'Name', 'a'],
+
['leave', 'Name', 'a'],
+
['leave', 'Field', undefined],
+
['enter', 'Field', undefined],
+
['enter', 'Name', 'b'],
+
['leave', 'Name', 'b'],
+
['enter', 'Field', undefined],
+
['enter', 'Name', 'c'],
+
['leave', 'Name', 'c'],
+
['enter', 'SelectionSet', undefined],
+
['enter', 'Field', undefined],
+
['enter', 'Name', 'a'],
+
['leave', 'Name', 'a'],
+
['leave', 'Field', undefined],
+
['enter', 'Field', undefined],
+
['enter', 'Name', 'b'],
+
['leave', 'Name', 'b'],
+
['enter', 'Field', undefined],
+
['enter', 'Name', 'c'],
+
['leave', 'Name', 'c'],
+
['leave', 'Field', undefined],
+
['leave', 'SelectionSet', undefined],
+
['leave', 'Field', undefined],
+
['leave', 'SelectionSet', undefined],
+
['leave', 'OperationDefinition', undefined],
+
['leave', 'Document', undefined],