Mirror: The small sibling of the graphql package, slimmed down for client-side libraries.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add upstream tests for visitor

+1372 -21
+1337
alias/language/__tests__/visitor.js
··· 1 + // See: https://github.com/graphql/graphql-js/blob/976d64b/src/language/__tests__/visitor-test.ts 2 + 3 + import { Kind, parse } from 'graphql'; 4 + import { visit, visitInParallel, BREAK } from '../visitor'; 5 + 6 + const kitchenSinkQuery: string = String.raw` 7 + query queryName($foo: ComplexType, $site: Site = MOBILE) @onQuery { 8 + whoever123is: node(id: [123, 456]) { 9 + id 10 + ... on User @onInlineFragment { 11 + field2 { 12 + id 13 + alias: field1(first: 10, after: $foo) @include(if: $foo) { 14 + id 15 + ...frag @onFragmentSpread 16 + } 17 + } 18 + } 19 + ... @skip(unless: $foo) { 20 + id 21 + } 22 + ... { 23 + id 24 + } 25 + } 26 + } 27 + mutation likeStory @onMutation { 28 + like(story: 123) @onField { 29 + story { 30 + id @onField 31 + } 32 + } 33 + } 34 + subscription StoryLikeSubscription( 35 + $input: StoryLikeSubscribeInput @onVariableDefinition 36 + ) 37 + @onSubscription { 38 + storyLikeSubscribe(input: $input) { 39 + story { 40 + likers { 41 + count 42 + } 43 + likeSentence { 44 + text 45 + } 46 + } 47 + } 48 + } 49 + fragment frag on Friend @onFragmentDefinition { 50 + foo( 51 + size: $size 52 + bar: $b 53 + obj: { 54 + key: "value" 55 + block: """ 56 + block string uses \""" 57 + """ 58 + } 59 + ) 60 + } 61 + { 62 + unnamed(truthy: true, falsy: false, nullish: null) 63 + query 64 + } 65 + query { 66 + __typename 67 + } 68 + `; 69 + 70 + function checkVisitorFnArgs(ast, args, isEdited = false) { 71 + const [node, key, parent, path, ancestors] = args; 72 + 73 + expect(node).toBeInstanceOf(Object); 74 + expect(Object.values(Kind)).toContain(node.kind); 75 + 76 + const isRoot = key === undefined; 77 + if (isRoot) { 78 + if (!isEdited) { 79 + expect(node).toEqual(ast); 80 + } 81 + expect(parent).toEqual(undefined); 82 + expect(path).toEqual([]); 83 + expect(ancestors).toEqual([]); 84 + return; 85 + } 86 + 87 + expect(typeof key).toMatch(/number|string/); 88 + 89 + expect(parent).toHaveProperty([key]); 90 + 91 + expect(path).toBeInstanceOf(Array); 92 + expect(path[path.length - 1]).toEqual(key); 93 + 94 + expect(ancestors).toBeInstanceOf(Array); 95 + expect(ancestors.length).toEqual(path.length - 1); 96 + 97 + if (!isEdited) { 98 + let currentNode = ast; 99 + for (let i = 0; i < ancestors.length; ++i) { 100 + expect(ancestors[i]).toEqual(currentNode); 101 + 102 + currentNode = currentNode[path[i]]; 103 + expect(currentNode).not.toEqual(undefined); 104 + } 105 + 106 + // expect(parent).toEqual(currentNode); 107 + // expect(parent[key]).toEqual(node); 108 + } 109 + } 110 + 111 + function isNode(node) { 112 + return node != null && typeof node.kind === 'string'; 113 + } 114 + 115 + function getValue(node) { 116 + return 'value' in node ? node.value : undefined; 117 + } 118 + 119 + describe('Visitor', () => { 120 + it('handles empty visitor', () => { 121 + const ast = parse('{ a }', { noLocation: true }); 122 + expect(() => visit(ast, {})).not.toThrow(); 123 + }); 124 + 125 + it('validates path argument', () => { 126 + const visited: Array<any> = []; 127 + 128 + const ast = parse('{ a }', { noLocation: true }); 129 + 130 + visit(ast, { 131 + enter(_node, _key, _parent, path) { 132 + checkVisitorFnArgs(ast, arguments); 133 + visited.push(['enter', path.slice()]); 134 + }, 135 + leave(_node, _key, _parent, path) { 136 + checkVisitorFnArgs(ast, arguments); 137 + visited.push(['leave', path.slice()]); 138 + }, 139 + }); 140 + 141 + expect(visited).toEqual([ 142 + ['enter', []], 143 + ['enter', ['definitions', 0]], 144 + ['enter', ['definitions', 0, 'selectionSet']], 145 + ['enter', ['definitions', 0, 'selectionSet', 'selections', 0]], 146 + ['enter', ['definitions', 0, 'selectionSet', 'selections', 0, 'name']], 147 + ['leave', ['definitions', 0, 'selectionSet', 'selections', 0, 'name']], 148 + ['leave', ['definitions', 0, 'selectionSet', 'selections', 0]], 149 + ['leave', ['definitions', 0, 'selectionSet']], 150 + ['leave', ['definitions', 0]], 151 + ['leave', []], 152 + ]); 153 + }); 154 + 155 + it('validates ancestors argument', () => { 156 + const ast = parse('{ a }', { noLocation: true }); 157 + const visitedNodes: Array<any> = []; 158 + 159 + visit(ast, { 160 + enter(node, key, parent, _path, ancestors) { 161 + const inArray = typeof key === 'number'; 162 + if (inArray) { 163 + visitedNodes.push(parent); 164 + } 165 + visitedNodes.push(node); 166 + 167 + const expectedAncestors = visitedNodes.slice(0, -2); 168 + expect(ancestors).toEqual(expectedAncestors); 169 + }, 170 + leave(_node, key, _parent, _path, ancestors) { 171 + const expectedAncestors = visitedNodes.slice(0, -2); 172 + expect(ancestors).toEqual(expectedAncestors); 173 + 174 + const inArray = typeof key === 'number'; 175 + if (inArray) { 176 + visitedNodes.pop(); 177 + } 178 + visitedNodes.pop(); 179 + }, 180 + }); 181 + }); 182 + 183 + it('allows editing a node both on enter and on leave', () => { 184 + const ast = parse('{ a, b, c { a, b, c } }', { noLocation: true }); 185 + 186 + let selectionSet: SelectionSetNode; 187 + 188 + const editedAST = visit(ast, { 189 + OperationDefinition: { 190 + enter(node) { 191 + checkVisitorFnArgs(ast, arguments); 192 + selectionSet = node.selectionSet; 193 + return { 194 + ...node, 195 + selectionSet: { 196 + kind: 'SelectionSet', 197 + selections: [], 198 + }, 199 + didEnter: true, 200 + }; 201 + }, 202 + leave(node) { 203 + checkVisitorFnArgs(ast, arguments, /* isEdited */ true); 204 + return { 205 + ...node, 206 + selectionSet, 207 + didLeave: true, 208 + }; 209 + }, 210 + }, 211 + }); 212 + 213 + expect(editedAST).toEqual({ 214 + ...ast, 215 + definitions: [ 216 + { 217 + ...ast.definitions[0], 218 + didEnter: true, 219 + didLeave: true, 220 + }, 221 + ], 222 + }); 223 + }); 224 + 225 + it('allows editing the root node on enter and on leave', () => { 226 + const ast = parse('{ a, b, c { a, b, c } }', { noLocation: true }); 227 + 228 + const { definitions } = ast; 229 + 230 + const editedAST = visit(ast, { 231 + Document: { 232 + enter(node) { 233 + checkVisitorFnArgs(ast, arguments); 234 + return { 235 + ...node, 236 + definitions: [], 237 + didEnter: true, 238 + }; 239 + }, 240 + leave(node) { 241 + checkVisitorFnArgs(ast, arguments, /* isEdited */ true); 242 + return { 243 + ...node, 244 + definitions, 245 + didLeave: true, 246 + }; 247 + }, 248 + }, 249 + }); 250 + 251 + expect(editedAST).toEqual({ 252 + ...ast, 253 + didEnter: true, 254 + didLeave: true, 255 + }); 256 + }); 257 + 258 + it('allows for editing on enter', () => { 259 + const ast = parse('{ a, b, c { a, b, c } }', { noLocation: true }); 260 + const editedAST = visit(ast, { 261 + enter(node) { 262 + checkVisitorFnArgs(ast, arguments); 263 + if (node.kind === 'Field' && node.name.value === 'b') { 264 + return null; 265 + } 266 + }, 267 + }); 268 + 269 + expect(ast).toEqual(parse('{ a, b, c { a, b, c } }', { noLocation: true })); 270 + 271 + expect(editedAST).toEqual( 272 + parse('{ a, c { a, c } }', { noLocation: true }) 273 + ); 274 + }); 275 + 276 + it('allows for editing on leave', () => { 277 + const ast = parse('{ a, b, c { a, b, c } }', { noLocation: true }); 278 + const editedAST = visit(ast, { 279 + leave(node) { 280 + checkVisitorFnArgs(ast, arguments, /* isEdited */ true); 281 + if (node.kind === 'Field' && node.name.value === 'b') { 282 + return null; 283 + } 284 + }, 285 + }); 286 + 287 + expect(ast).toEqual(parse('{ a, b, c { a, b, c } }', { noLocation: true })); 288 + 289 + expect(editedAST).toEqual( 290 + parse('{ a, c { a, c } }', { noLocation: true }) 291 + ); 292 + }); 293 + 294 + it('ignores false returned on leave', () => { 295 + const ast = parse('{ a, b, c { a, b, c } }', { noLocation: true }); 296 + const returnedAST = visit(ast, { 297 + leave() { 298 + return false; 299 + }, 300 + }); 301 + 302 + expect(returnedAST).toEqual( 303 + parse('{ a, b, c { a, b, c } }', { noLocation: true }) 304 + ); 305 + }); 306 + 307 + it('visits edited node', () => { 308 + const addedField = { 309 + kind: 'Field', 310 + name: { 311 + kind: 'Name', 312 + value: '__typename', 313 + }, 314 + }; 315 + 316 + let didVisitAddedField; 317 + 318 + const ast = parse('{ a { x } }', { noLocation: true }); 319 + visit(ast, { 320 + enter(node) { 321 + checkVisitorFnArgs(ast, arguments, /* isEdited */ true); 322 + if (node.kind === 'Field' && node.name.value === 'a') { 323 + return { 324 + kind: 'Field', 325 + selectionSet: [addedField, node.selectionSet], 326 + }; 327 + } 328 + if (node === addedField) { 329 + didVisitAddedField = true; 330 + } 331 + }, 332 + }); 333 + 334 + expect(didVisitAddedField).toEqual(true); 335 + }); 336 + 337 + it('allows skipping a sub-tree', () => { 338 + const visited: Array<any> = []; 339 + 340 + const ast = parse('{ a, b { x }, c }', { noLocation: true }); 341 + visit(ast, { 342 + enter(node) { 343 + checkVisitorFnArgs(ast, arguments); 344 + visited.push(['enter', node.kind, getValue(node)]); 345 + if (node.kind === 'Field' && node.name.value === 'b') { 346 + return false; 347 + } 348 + }, 349 + 350 + leave(node) { 351 + checkVisitorFnArgs(ast, arguments); 352 + visited.push(['leave', node.kind, getValue(node)]); 353 + }, 354 + }); 355 + 356 + expect(visited).toEqual([ 357 + ['enter', 'Document', undefined], 358 + ['enter', 'OperationDefinition', undefined], 359 + ['enter', 'SelectionSet', undefined], 360 + ['enter', 'Field', undefined], 361 + ['enter', 'Name', 'a'], 362 + ['leave', 'Name', 'a'], 363 + ['leave', 'Field', undefined], 364 + ['enter', 'Field', undefined], 365 + ['enter', 'Field', undefined], 366 + ['enter', 'Name', 'c'], 367 + ['leave', 'Name', 'c'], 368 + ['leave', 'Field', undefined], 369 + ['leave', 'SelectionSet', undefined], 370 + ['leave', 'OperationDefinition', undefined], 371 + ['leave', 'Document', undefined], 372 + ]); 373 + }); 374 + 375 + it('allows early exit while visiting', () => { 376 + const visited: Array<any> = []; 377 + 378 + const ast = parse('{ a, b { x }, c }', { noLocation: true }); 379 + visit(ast, { 380 + enter(node) { 381 + checkVisitorFnArgs(ast, arguments); 382 + visited.push(['enter', node.kind, getValue(node)]); 383 + if (node.kind === 'Name' && node.value === 'x') { 384 + return BREAK; 385 + } 386 + }, 387 + leave(node) { 388 + checkVisitorFnArgs(ast, arguments); 389 + visited.push(['leave', node.kind, getValue(node)]); 390 + }, 391 + }); 392 + 393 + expect(visited).toEqual([ 394 + ['enter', 'Document', undefined], 395 + ['enter', 'OperationDefinition', undefined], 396 + ['enter', 'SelectionSet', undefined], 397 + ['enter', 'Field', undefined], 398 + ['enter', 'Name', 'a'], 399 + ['leave', 'Name', 'a'], 400 + ['leave', 'Field', undefined], 401 + ['enter', 'Field', undefined], 402 + ['enter', 'Name', 'b'], 403 + ['leave', 'Name', 'b'], 404 + ['enter', 'SelectionSet', undefined], 405 + ['enter', 'Field', undefined], 406 + ['enter', 'Name', 'x'], 407 + ]); 408 + }); 409 + 410 + it('allows early exit while leaving', () => { 411 + const visited: Array<any> = []; 412 + 413 + const ast = parse('{ a, b { x }, c }', { noLocation: true }); 414 + visit(ast, { 415 + enter(node) { 416 + checkVisitorFnArgs(ast, arguments); 417 + visited.push(['enter', node.kind, getValue(node)]); 418 + }, 419 + 420 + leave(node) { 421 + checkVisitorFnArgs(ast, arguments); 422 + visited.push(['leave', node.kind, getValue(node)]); 423 + if (node.kind === 'Name' && node.value === 'x') { 424 + return BREAK; 425 + } 426 + }, 427 + }); 428 + 429 + expect(visited).toEqual([ 430 + ['enter', 'Document', undefined], 431 + ['enter', 'OperationDefinition', undefined], 432 + ['enter', 'SelectionSet', undefined], 433 + ['enter', 'Field', undefined], 434 + ['enter', 'Name', 'a'], 435 + ['leave', 'Name', 'a'], 436 + ['leave', 'Field', undefined], 437 + ['enter', 'Field', undefined], 438 + ['enter', 'Name', 'b'], 439 + ['leave', 'Name', 'b'], 440 + ['enter', 'SelectionSet', undefined], 441 + ['enter', 'Field', undefined], 442 + ['enter', 'Name', 'x'], 443 + ['leave', 'Name', 'x'], 444 + ]); 445 + }); 446 + 447 + it('allows a named functions visitor API', () => { 448 + const visited: Array<any> = []; 449 + 450 + const ast = parse('{ a, b { x }, c }', { noLocation: true }); 451 + visit(ast, { 452 + Name(node) { 453 + checkVisitorFnArgs(ast, arguments); 454 + visited.push(['enter', node.kind, getValue(node)]); 455 + }, 456 + SelectionSet: { 457 + enter(node) { 458 + checkVisitorFnArgs(ast, arguments); 459 + visited.push(['enter', node.kind, getValue(node)]); 460 + }, 461 + leave(node) { 462 + checkVisitorFnArgs(ast, arguments); 463 + visited.push(['leave', node.kind, getValue(node)]); 464 + }, 465 + }, 466 + }); 467 + 468 + expect(visited).toEqual([ 469 + ['enter', 'SelectionSet', undefined], 470 + ['enter', 'Name', 'a'], 471 + ['enter', 'Name', 'b'], 472 + ['enter', 'SelectionSet', undefined], 473 + ['enter', 'Name', 'x'], 474 + ['leave', 'SelectionSet', undefined], 475 + ['enter', 'Name', 'c'], 476 + ['leave', 'SelectionSet', undefined], 477 + ]); 478 + }); 479 + 480 + it('visits kitchen sink', () => { 481 + const ast = parse(kitchenSinkQuery); 482 + const visited: Array<any> = []; 483 + const argsStack: Array<any> = []; 484 + 485 + visit(ast, { 486 + enter(node, key, parent) { 487 + visited.push([ 488 + 'enter', 489 + node.kind, 490 + key, 491 + isNode(parent) ? parent.kind : undefined, 492 + ]); 493 + 494 + checkVisitorFnArgs(ast, arguments); 495 + argsStack.push([...arguments]); 496 + }, 497 + 498 + leave(node, key, parent) { 499 + visited.push([ 500 + 'leave', 501 + node.kind, 502 + key, 503 + isNode(parent) ? parent.kind : undefined, 504 + ]); 505 + 506 + expect(argsStack.pop()).toEqual([...arguments]); 507 + }, 508 + }); 509 + 510 + expect(argsStack).toEqual([]); 511 + expect(visited).toEqual([ 512 + ['enter', 'Document', undefined, undefined], 513 + ['enter', 'OperationDefinition', 0, undefined], 514 + ['enter', 'Name', 'name', 'OperationDefinition'], 515 + ['leave', 'Name', 'name', 'OperationDefinition'], 516 + ['enter', 'VariableDefinition', 0, undefined], 517 + ['enter', 'Variable', 'variable', 'VariableDefinition'], 518 + ['enter', 'Name', 'name', 'Variable'], 519 + ['leave', 'Name', 'name', 'Variable'], 520 + ['leave', 'Variable', 'variable', 'VariableDefinition'], 521 + ['enter', 'NamedType', 'type', 'VariableDefinition'], 522 + ['enter', 'Name', 'name', 'NamedType'], 523 + ['leave', 'Name', 'name', 'NamedType'], 524 + ['leave', 'NamedType', 'type', 'VariableDefinition'], 525 + ['leave', 'VariableDefinition', 0, undefined], 526 + ['enter', 'VariableDefinition', 1, undefined], 527 + ['enter', 'Variable', 'variable', 'VariableDefinition'], 528 + ['enter', 'Name', 'name', 'Variable'], 529 + ['leave', 'Name', 'name', 'Variable'], 530 + ['leave', 'Variable', 'variable', 'VariableDefinition'], 531 + ['enter', 'NamedType', 'type', 'VariableDefinition'], 532 + ['enter', 'Name', 'name', 'NamedType'], 533 + ['leave', 'Name', 'name', 'NamedType'], 534 + ['leave', 'NamedType', 'type', 'VariableDefinition'], 535 + ['enter', 'EnumValue', 'defaultValue', 'VariableDefinition'], 536 + ['leave', 'EnumValue', 'defaultValue', 'VariableDefinition'], 537 + ['leave', 'VariableDefinition', 1, undefined], 538 + ['enter', 'Directive', 0, undefined], 539 + ['enter', 'Name', 'name', 'Directive'], 540 + ['leave', 'Name', 'name', 'Directive'], 541 + ['leave', 'Directive', 0, undefined], 542 + ['enter', 'SelectionSet', 'selectionSet', 'OperationDefinition'], 543 + ['enter', 'Field', 0, undefined], 544 + ['enter', 'Name', 'alias', 'Field'], 545 + ['leave', 'Name', 'alias', 'Field'], 546 + ['enter', 'Name', 'name', 'Field'], 547 + ['leave', 'Name', 'name', 'Field'], 548 + ['enter', 'Argument', 0, undefined], 549 + ['enter', 'Name', 'name', 'Argument'], 550 + ['leave', 'Name', 'name', 'Argument'], 551 + ['enter', 'ListValue', 'value', 'Argument'], 552 + ['enter', 'IntValue', 0, undefined], 553 + ['leave', 'IntValue', 0, undefined], 554 + ['enter', 'IntValue', 1, undefined], 555 + ['leave', 'IntValue', 1, undefined], 556 + ['leave', 'ListValue', 'value', 'Argument'], 557 + ['leave', 'Argument', 0, undefined], 558 + ['enter', 'SelectionSet', 'selectionSet', 'Field'], 559 + ['enter', 'Field', 0, undefined], 560 + ['enter', 'Name', 'name', 'Field'], 561 + ['leave', 'Name', 'name', 'Field'], 562 + ['leave', 'Field', 0, undefined], 563 + ['enter', 'InlineFragment', 1, undefined], 564 + ['enter', 'NamedType', 'typeCondition', 'InlineFragment'], 565 + ['enter', 'Name', 'name', 'NamedType'], 566 + ['leave', 'Name', 'name', 'NamedType'], 567 + ['leave', 'NamedType', 'typeCondition', 'InlineFragment'], 568 + ['enter', 'Directive', 0, undefined], 569 + ['enter', 'Name', 'name', 'Directive'], 570 + ['leave', 'Name', 'name', 'Directive'], 571 + ['leave', 'Directive', 0, undefined], 572 + ['enter', 'SelectionSet', 'selectionSet', 'InlineFragment'], 573 + ['enter', 'Field', 0, undefined], 574 + ['enter', 'Name', 'name', 'Field'], 575 + ['leave', 'Name', 'name', 'Field'], 576 + ['enter', 'SelectionSet', 'selectionSet', 'Field'], 577 + ['enter', 'Field', 0, undefined], 578 + ['enter', 'Name', 'name', 'Field'], 579 + ['leave', 'Name', 'name', 'Field'], 580 + ['leave', 'Field', 0, undefined], 581 + ['enter', 'Field', 1, undefined], 582 + ['enter', 'Name', 'alias', 'Field'], 583 + ['leave', 'Name', 'alias', 'Field'], 584 + ['enter', 'Name', 'name', 'Field'], 585 + ['leave', 'Name', 'name', 'Field'], 586 + ['enter', 'Argument', 0, undefined], 587 + ['enter', 'Name', 'name', 'Argument'], 588 + ['leave', 'Name', 'name', 'Argument'], 589 + ['enter', 'IntValue', 'value', 'Argument'], 590 + ['leave', 'IntValue', 'value', 'Argument'], 591 + ['leave', 'Argument', 0, undefined], 592 + ['enter', 'Argument', 1, undefined], 593 + ['enter', 'Name', 'name', 'Argument'], 594 + ['leave', 'Name', 'name', 'Argument'], 595 + ['enter', 'Variable', 'value', 'Argument'], 596 + ['enter', 'Name', 'name', 'Variable'], 597 + ['leave', 'Name', 'name', 'Variable'], 598 + ['leave', 'Variable', 'value', 'Argument'], 599 + ['leave', 'Argument', 1, undefined], 600 + ['enter', 'Directive', 0, undefined], 601 + ['enter', 'Name', 'name', 'Directive'], 602 + ['leave', 'Name', 'name', 'Directive'], 603 + ['enter', 'Argument', 0, undefined], 604 + ['enter', 'Name', 'name', 'Argument'], 605 + ['leave', 'Name', 'name', 'Argument'], 606 + ['enter', 'Variable', 'value', 'Argument'], 607 + ['enter', 'Name', 'name', 'Variable'], 608 + ['leave', 'Name', 'name', 'Variable'], 609 + ['leave', 'Variable', 'value', 'Argument'], 610 + ['leave', 'Argument', 0, undefined], 611 + ['leave', 'Directive', 0, undefined], 612 + ['enter', 'SelectionSet', 'selectionSet', 'Field'], 613 + ['enter', 'Field', 0, undefined], 614 + ['enter', 'Name', 'name', 'Field'], 615 + ['leave', 'Name', 'name', 'Field'], 616 + ['leave', 'Field', 0, undefined], 617 + ['enter', 'FragmentSpread', 1, undefined], 618 + ['enter', 'Name', 'name', 'FragmentSpread'], 619 + ['leave', 'Name', 'name', 'FragmentSpread'], 620 + ['enter', 'Directive', 0, undefined], 621 + ['enter', 'Name', 'name', 'Directive'], 622 + ['leave', 'Name', 'name', 'Directive'], 623 + ['leave', 'Directive', 0, undefined], 624 + ['leave', 'FragmentSpread', 1, undefined], 625 + ['leave', 'SelectionSet', 'selectionSet', 'Field'], 626 + ['leave', 'Field', 1, undefined], 627 + ['leave', 'SelectionSet', 'selectionSet', 'Field'], 628 + ['leave', 'Field', 0, undefined], 629 + ['leave', 'SelectionSet', 'selectionSet', 'InlineFragment'], 630 + ['leave', 'InlineFragment', 1, undefined], 631 + ['enter', 'InlineFragment', 2, undefined], 632 + ['enter', 'Directive', 0, undefined], 633 + ['enter', 'Name', 'name', 'Directive'], 634 + ['leave', 'Name', 'name', 'Directive'], 635 + ['enter', 'Argument', 0, undefined], 636 + ['enter', 'Name', 'name', 'Argument'], 637 + ['leave', 'Name', 'name', 'Argument'], 638 + ['enter', 'Variable', 'value', 'Argument'], 639 + ['enter', 'Name', 'name', 'Variable'], 640 + ['leave', 'Name', 'name', 'Variable'], 641 + ['leave', 'Variable', 'value', 'Argument'], 642 + ['leave', 'Argument', 0, undefined], 643 + ['leave', 'Directive', 0, undefined], 644 + ['enter', 'SelectionSet', 'selectionSet', 'InlineFragment'], 645 + ['enter', 'Field', 0, undefined], 646 + ['enter', 'Name', 'name', 'Field'], 647 + ['leave', 'Name', 'name', 'Field'], 648 + ['leave', 'Field', 0, undefined], 649 + ['leave', 'SelectionSet', 'selectionSet', 'InlineFragment'], 650 + ['leave', 'InlineFragment', 2, undefined], 651 + ['enter', 'InlineFragment', 3, undefined], 652 + ['enter', 'SelectionSet', 'selectionSet', 'InlineFragment'], 653 + ['enter', 'Field', 0, undefined], 654 + ['enter', 'Name', 'name', 'Field'], 655 + ['leave', 'Name', 'name', 'Field'], 656 + ['leave', 'Field', 0, undefined], 657 + ['leave', 'SelectionSet', 'selectionSet', 'InlineFragment'], 658 + ['leave', 'InlineFragment', 3, undefined], 659 + ['leave', 'SelectionSet', 'selectionSet', 'Field'], 660 + ['leave', 'Field', 0, undefined], 661 + ['leave', 'SelectionSet', 'selectionSet', 'OperationDefinition'], 662 + ['leave', 'OperationDefinition', 0, undefined], 663 + ['enter', 'OperationDefinition', 1, undefined], 664 + ['enter', 'Name', 'name', 'OperationDefinition'], 665 + ['leave', 'Name', 'name', 'OperationDefinition'], 666 + ['enter', 'Directive', 0, undefined], 667 + ['enter', 'Name', 'name', 'Directive'], 668 + ['leave', 'Name', 'name', 'Directive'], 669 + ['leave', 'Directive', 0, undefined], 670 + ['enter', 'SelectionSet', 'selectionSet', 'OperationDefinition'], 671 + ['enter', 'Field', 0, undefined], 672 + ['enter', 'Name', 'name', 'Field'], 673 + ['leave', 'Name', 'name', 'Field'], 674 + ['enter', 'Argument', 0, undefined], 675 + ['enter', 'Name', 'name', 'Argument'], 676 + ['leave', 'Name', 'name', 'Argument'], 677 + ['enter', 'IntValue', 'value', 'Argument'], 678 + ['leave', 'IntValue', 'value', 'Argument'], 679 + ['leave', 'Argument', 0, undefined], 680 + ['enter', 'Directive', 0, undefined], 681 + ['enter', 'Name', 'name', 'Directive'], 682 + ['leave', 'Name', 'name', 'Directive'], 683 + ['leave', 'Directive', 0, undefined], 684 + ['enter', 'SelectionSet', 'selectionSet', 'Field'], 685 + ['enter', 'Field', 0, undefined], 686 + ['enter', 'Name', 'name', 'Field'], 687 + ['leave', 'Name', 'name', 'Field'], 688 + ['enter', 'SelectionSet', 'selectionSet', 'Field'], 689 + ['enter', 'Field', 0, undefined], 690 + ['enter', 'Name', 'name', 'Field'], 691 + ['leave', 'Name', 'name', 'Field'], 692 + ['enter', 'Directive', 0, undefined], 693 + ['enter', 'Name', 'name', 'Directive'], 694 + ['leave', 'Name', 'name', 'Directive'], 695 + ['leave', 'Directive', 0, undefined], 696 + ['leave', 'Field', 0, undefined], 697 + ['leave', 'SelectionSet', 'selectionSet', 'Field'], 698 + ['leave', 'Field', 0, undefined], 699 + ['leave', 'SelectionSet', 'selectionSet', 'Field'], 700 + ['leave', 'Field', 0, undefined], 701 + ['leave', 'SelectionSet', 'selectionSet', 'OperationDefinition'], 702 + ['leave', 'OperationDefinition', 1, undefined], 703 + ['enter', 'OperationDefinition', 2, undefined], 704 + ['enter', 'Name', 'name', 'OperationDefinition'], 705 + ['leave', 'Name', 'name', 'OperationDefinition'], 706 + ['enter', 'VariableDefinition', 0, undefined], 707 + ['enter', 'Variable', 'variable', 'VariableDefinition'], 708 + ['enter', 'Name', 'name', 'Variable'], 709 + ['leave', 'Name', 'name', 'Variable'], 710 + ['leave', 'Variable', 'variable', 'VariableDefinition'], 711 + ['enter', 'NamedType', 'type', 'VariableDefinition'], 712 + ['enter', 'Name', 'name', 'NamedType'], 713 + ['leave', 'Name', 'name', 'NamedType'], 714 + ['leave', 'NamedType', 'type', 'VariableDefinition'], 715 + ['enter', 'Directive', 0, undefined], 716 + ['enter', 'Name', 'name', 'Directive'], 717 + ['leave', 'Name', 'name', 'Directive'], 718 + ['leave', 'Directive', 0, undefined], 719 + ['leave', 'VariableDefinition', 0, undefined], 720 + ['enter', 'Directive', 0, undefined], 721 + ['enter', 'Name', 'name', 'Directive'], 722 + ['leave', 'Name', 'name', 'Directive'], 723 + ['leave', 'Directive', 0, undefined], 724 + ['enter', 'SelectionSet', 'selectionSet', 'OperationDefinition'], 725 + ['enter', 'Field', 0, undefined], 726 + ['enter', 'Name', 'name', 'Field'], 727 + ['leave', 'Name', 'name', 'Field'], 728 + ['enter', 'Argument', 0, undefined], 729 + ['enter', 'Name', 'name', 'Argument'], 730 + ['leave', 'Name', 'name', 'Argument'], 731 + ['enter', 'Variable', 'value', 'Argument'], 732 + ['enter', 'Name', 'name', 'Variable'], 733 + ['leave', 'Name', 'name', 'Variable'], 734 + ['leave', 'Variable', 'value', 'Argument'], 735 + ['leave', 'Argument', 0, undefined], 736 + ['enter', 'SelectionSet', 'selectionSet', 'Field'], 737 + ['enter', 'Field', 0, undefined], 738 + ['enter', 'Name', 'name', 'Field'], 739 + ['leave', 'Name', 'name', 'Field'], 740 + ['enter', 'SelectionSet', 'selectionSet', 'Field'], 741 + ['enter', 'Field', 0, undefined], 742 + ['enter', 'Name', 'name', 'Field'], 743 + ['leave', 'Name', 'name', 'Field'], 744 + ['enter', 'SelectionSet', 'selectionSet', 'Field'], 745 + ['enter', 'Field', 0, undefined], 746 + ['enter', 'Name', 'name', 'Field'], 747 + ['leave', 'Name', 'name', 'Field'], 748 + ['leave', 'Field', 0, undefined], 749 + ['leave', 'SelectionSet', 'selectionSet', 'Field'], 750 + ['leave', 'Field', 0, undefined], 751 + ['enter', 'Field', 1, undefined], 752 + ['enter', 'Name', 'name', 'Field'], 753 + ['leave', 'Name', 'name', 'Field'], 754 + ['enter', 'SelectionSet', 'selectionSet', 'Field'], 755 + ['enter', 'Field', 0, undefined], 756 + ['enter', 'Name', 'name', 'Field'], 757 + ['leave', 'Name', 'name', 'Field'], 758 + ['leave', 'Field', 0, undefined], 759 + ['leave', 'SelectionSet', 'selectionSet', 'Field'], 760 + ['leave', 'Field', 1, undefined], 761 + ['leave', 'SelectionSet', 'selectionSet', 'Field'], 762 + ['leave', 'Field', 0, undefined], 763 + ['leave', 'SelectionSet', 'selectionSet', 'Field'], 764 + ['leave', 'Field', 0, undefined], 765 + ['leave', 'SelectionSet', 'selectionSet', 'OperationDefinition'], 766 + ['leave', 'OperationDefinition', 2, undefined], 767 + ['enter', 'FragmentDefinition', 3, undefined], 768 + ['enter', 'Name', 'name', 'FragmentDefinition'], 769 + ['leave', 'Name', 'name', 'FragmentDefinition'], 770 + ['enter', 'NamedType', 'typeCondition', 'FragmentDefinition'], 771 + ['enter', 'Name', 'name', 'NamedType'], 772 + ['leave', 'Name', 'name', 'NamedType'], 773 + ['leave', 'NamedType', 'typeCondition', 'FragmentDefinition'], 774 + ['enter', 'Directive', 0, undefined], 775 + ['enter', 'Name', 'name', 'Directive'], 776 + ['leave', 'Name', 'name', 'Directive'], 777 + ['leave', 'Directive', 0, undefined], 778 + ['enter', 'SelectionSet', 'selectionSet', 'FragmentDefinition'], 779 + ['enter', 'Field', 0, undefined], 780 + ['enter', 'Name', 'name', 'Field'], 781 + ['leave', 'Name', 'name', 'Field'], 782 + ['enter', 'Argument', 0, undefined], 783 + ['enter', 'Name', 'name', 'Argument'], 784 + ['leave', 'Name', 'name', 'Argument'], 785 + ['enter', 'Variable', 'value', 'Argument'], 786 + ['enter', 'Name', 'name', 'Variable'], 787 + ['leave', 'Name', 'name', 'Variable'], 788 + ['leave', 'Variable', 'value', 'Argument'], 789 + ['leave', 'Argument', 0, undefined], 790 + ['enter', 'Argument', 1, undefined], 791 + ['enter', 'Name', 'name', 'Argument'], 792 + ['leave', 'Name', 'name', 'Argument'], 793 + ['enter', 'Variable', 'value', 'Argument'], 794 + ['enter', 'Name', 'name', 'Variable'], 795 + ['leave', 'Name', 'name', 'Variable'], 796 + ['leave', 'Variable', 'value', 'Argument'], 797 + ['leave', 'Argument', 1, undefined], 798 + ['enter', 'Argument', 2, undefined], 799 + ['enter', 'Name', 'name', 'Argument'], 800 + ['leave', 'Name', 'name', 'Argument'], 801 + ['enter', 'ObjectValue', 'value', 'Argument'], 802 + ['enter', 'ObjectField', 0, undefined], 803 + ['enter', 'Name', 'name', 'ObjectField'], 804 + ['leave', 'Name', 'name', 'ObjectField'], 805 + ['enter', 'StringValue', 'value', 'ObjectField'], 806 + ['leave', 'StringValue', 'value', 'ObjectField'], 807 + ['leave', 'ObjectField', 0, undefined], 808 + ['enter', 'ObjectField', 1, undefined], 809 + ['enter', 'Name', 'name', 'ObjectField'], 810 + ['leave', 'Name', 'name', 'ObjectField'], 811 + ['enter', 'StringValue', 'value', 'ObjectField'], 812 + ['leave', 'StringValue', 'value', 'ObjectField'], 813 + ['leave', 'ObjectField', 1, undefined], 814 + ['leave', 'ObjectValue', 'value', 'Argument'], 815 + ['leave', 'Argument', 2, undefined], 816 + ['leave', 'Field', 0, undefined], 817 + ['leave', 'SelectionSet', 'selectionSet', 'FragmentDefinition'], 818 + ['leave', 'FragmentDefinition', 3, undefined], 819 + ['enter', 'OperationDefinition', 4, undefined], 820 + ['enter', 'SelectionSet', 'selectionSet', 'OperationDefinition'], 821 + ['enter', 'Field', 0, undefined], 822 + ['enter', 'Name', 'name', 'Field'], 823 + ['leave', 'Name', 'name', 'Field'], 824 + ['enter', 'Argument', 0, undefined], 825 + ['enter', 'Name', 'name', 'Argument'], 826 + ['leave', 'Name', 'name', 'Argument'], 827 + ['enter', 'BooleanValue', 'value', 'Argument'], 828 + ['leave', 'BooleanValue', 'value', 'Argument'], 829 + ['leave', 'Argument', 0, undefined], 830 + ['enter', 'Argument', 1, undefined], 831 + ['enter', 'Name', 'name', 'Argument'], 832 + ['leave', 'Name', 'name', 'Argument'], 833 + ['enter', 'BooleanValue', 'value', 'Argument'], 834 + ['leave', 'BooleanValue', 'value', 'Argument'], 835 + ['leave', 'Argument', 1, undefined], 836 + ['enter', 'Argument', 2, undefined], 837 + ['enter', 'Name', 'name', 'Argument'], 838 + ['leave', 'Name', 'name', 'Argument'], 839 + ['enter', 'NullValue', 'value', 'Argument'], 840 + ['leave', 'NullValue', 'value', 'Argument'], 841 + ['leave', 'Argument', 2, undefined], 842 + ['leave', 'Field', 0, undefined], 843 + ['enter', 'Field', 1, undefined], 844 + ['enter', 'Name', 'name', 'Field'], 845 + ['leave', 'Name', 'name', 'Field'], 846 + ['leave', 'Field', 1, undefined], 847 + ['leave', 'SelectionSet', 'selectionSet', 'OperationDefinition'], 848 + ['leave', 'OperationDefinition', 4, undefined], 849 + ['enter', 'OperationDefinition', 5, undefined], 850 + ['enter', 'SelectionSet', 'selectionSet', 'OperationDefinition'], 851 + ['enter', 'Field', 0, undefined], 852 + ['enter', 'Name', 'name', 'Field'], 853 + ['leave', 'Name', 'name', 'Field'], 854 + ['leave', 'Field', 0, undefined], 855 + ['leave', 'SelectionSet', 'selectionSet', 'OperationDefinition'], 856 + ['leave', 'OperationDefinition', 5, undefined], 857 + ['leave', 'Document', undefined, undefined], 858 + ]); 859 + }); 860 + 861 + // TODO: Verify discrepancies 862 + describe('visitInParallel', () => { 863 + // Note: nearly identical to the above test of the same test but 864 + // using visitInParallel. 865 + it('allows skipping a sub-tree', () => { 866 + const visited: Array<any> = []; 867 + 868 + const ast = parse('{ a, b { x }, c }'); 869 + visit( 870 + ast, 871 + visitInParallel([ 872 + { 873 + enter(node) { 874 + checkVisitorFnArgs(ast, arguments); 875 + visited.push(['enter', node.kind, getValue(node)]); 876 + if (node.kind === 'Field' && node.name.value === 'b') { 877 + return false; 878 + } 879 + }, 880 + 881 + leave(node) { 882 + checkVisitorFnArgs(ast, arguments); 883 + visited.push(['leave', node.kind, getValue(node)]); 884 + }, 885 + }, 886 + ]) 887 + ); 888 + 889 + expect(visited).toEqual([ 890 + ['enter', 'Document', undefined], 891 + ['enter', 'OperationDefinition', undefined], 892 + ['enter', 'SelectionSet', undefined], 893 + ['enter', 'Field', undefined], 894 + ['enter', 'Name', 'a'], 895 + ['leave', 'Name', 'a'], 896 + ['leave', 'Field', undefined], 897 + ['enter', 'Field', undefined], 898 + ['enter', 'Field', undefined], 899 + ['enter', 'Name', 'c'], 900 + ['leave', 'Name', 'c'], 901 + ['leave', 'Field', undefined], 902 + ['leave', 'SelectionSet', undefined], 903 + ['leave', 'OperationDefinition', undefined], 904 + ['leave', 'Document', undefined], 905 + ]); 906 + }); 907 + 908 + it('allows skipping different sub-trees', () => { 909 + const visited: Array<any> = []; 910 + 911 + const ast = parse('{ a { x }, b { y} }'); 912 + visit( 913 + ast, 914 + visitInParallel([ 915 + { 916 + enter(node) { 917 + checkVisitorFnArgs(ast, arguments); 918 + visited.push(['no-a', 'enter', node.kind, getValue(node)]); 919 + if (node.kind === 'Field' && node.name.value === 'a') { 920 + return false; 921 + } 922 + }, 923 + leave(node) { 924 + checkVisitorFnArgs(ast, arguments); 925 + visited.push(['no-a', 'leave', node.kind, getValue(node)]); 926 + }, 927 + }, 928 + { 929 + enter(node) { 930 + checkVisitorFnArgs(ast, arguments); 931 + visited.push(['no-b', 'enter', node.kind, getValue(node)]); 932 + if (node.kind === 'Field' && node.name.value === 'b') { 933 + return false; 934 + } 935 + }, 936 + leave(node) { 937 + checkVisitorFnArgs(ast, arguments); 938 + visited.push(['no-b', 'leave', node.kind, getValue(node)]); 939 + }, 940 + }, 941 + ]) 942 + ); 943 + 944 + expect(visited).toEqual([ 945 + ['no-a', 'enter', 'Document', undefined], 946 + ['no-b', 'enter', 'Document', undefined], 947 + ['no-a', 'enter', 'OperationDefinition', undefined], 948 + ['no-b', 'enter', 'OperationDefinition', undefined], 949 + ['no-a', 'enter', 'SelectionSet', undefined], 950 + ['no-b', 'enter', 'SelectionSet', undefined], 951 + ['no-a', 'enter', 'Field', undefined], 952 + ['no-b', 'enter', 'Field', undefined], 953 + ['no-b', 'enter', 'Name', 'a'], 954 + ['no-b', 'leave', 'Name', 'a'], 955 + ['no-b', 'enter', 'SelectionSet', undefined], 956 + ['no-b', 'enter', 'Field', undefined], 957 + ['no-b', 'enter', 'Name', 'x'], 958 + ['no-b', 'leave', 'Name', 'x'], 959 + ['no-b', 'leave', 'Field', undefined], 960 + ['no-b', 'leave', 'SelectionSet', undefined], 961 + ['no-b', 'leave', 'Field', undefined], 962 + ['no-a', 'enter', 'Field', undefined], 963 + ['no-b', 'enter', 'Field', undefined], 964 + ['no-a', 'enter', 'Name', 'b'], 965 + ['no-a', 'leave', 'Name', 'b'], 966 + ['no-a', 'enter', 'SelectionSet', undefined], 967 + ['no-a', 'enter', 'Field', undefined], 968 + ['no-a', 'enter', 'Name', 'y'], 969 + ['no-a', 'leave', 'Name', 'y'], 970 + ['no-a', 'leave', 'Field', undefined], 971 + ['no-a', 'leave', 'SelectionSet', undefined], 972 + ['no-a', 'leave', 'Field', undefined], 973 + ['no-a', 'leave', 'SelectionSet', undefined], 974 + ['no-b', 'leave', 'SelectionSet', undefined], 975 + ['no-a', 'leave', 'OperationDefinition', undefined], 976 + ['no-b', 'leave', 'OperationDefinition', undefined], 977 + ['no-a', 'leave', 'Document', undefined], 978 + ['no-b', 'leave', 'Document', undefined], 979 + ]); 980 + }); 981 + 982 + // Note: nearly identical to the above test of the same test but 983 + // using visitInParallel. 984 + it('allows early exit while visiting', () => { 985 + const visited: Array<any> = []; 986 + 987 + const ast = parse('{ a, b { x }, c }'); 988 + visit( 989 + ast, 990 + visitInParallel([ 991 + { 992 + enter(node) { 993 + checkVisitorFnArgs(ast, arguments); 994 + visited.push(['enter', node.kind, getValue(node)]); 995 + if (node.kind === 'Name' && node.value === 'x') { 996 + return BREAK; 997 + } 998 + }, 999 + leave(node) { 1000 + checkVisitorFnArgs(ast, arguments); 1001 + visited.push(['leave', node.kind, getValue(node)]); 1002 + }, 1003 + }, 1004 + ]) 1005 + ); 1006 + 1007 + expect(visited).toEqual([ 1008 + ['enter', 'Document', undefined], 1009 + ['enter', 'OperationDefinition', undefined], 1010 + ['enter', 'SelectionSet', undefined], 1011 + ['enter', 'Field', undefined], 1012 + ['enter', 'Name', 'a'], 1013 + ['leave', 'Name', 'a'], 1014 + ['leave', 'Field', undefined], 1015 + ['enter', 'Field', undefined], 1016 + ['enter', 'Name', 'b'], 1017 + ['leave', 'Name', 'b'], 1018 + ['enter', 'SelectionSet', undefined], 1019 + ['enter', 'Field', undefined], 1020 + ['enter', 'Name', 'x'], 1021 + ]); 1022 + }); 1023 + 1024 + it('allows early exit from different points', () => { 1025 + const visited: Array<any> = []; 1026 + 1027 + const ast = parse('{ a { y }, b { x } }'); 1028 + visit( 1029 + ast, 1030 + visitInParallel([ 1031 + { 1032 + enter(node) { 1033 + checkVisitorFnArgs(ast, arguments); 1034 + visited.push(['break-a', 'enter', node.kind, getValue(node)]); 1035 + if (node.kind === 'Name' && node.value === 'a') { 1036 + return BREAK; 1037 + } 1038 + }, 1039 + // istanbul ignore next (Never called and used as a placeholder) 1040 + leave() { 1041 + expect.fail('Should not be called'); 1042 + }, 1043 + }, 1044 + { 1045 + enter(node) { 1046 + checkVisitorFnArgs(ast, arguments); 1047 + visited.push(['break-b', 'enter', node.kind, getValue(node)]); 1048 + if (node.kind === 'Name' && node.value === 'b') { 1049 + return BREAK; 1050 + } 1051 + }, 1052 + leave(node) { 1053 + checkVisitorFnArgs(ast, arguments); 1054 + visited.push(['break-b', 'leave', node.kind, getValue(node)]); 1055 + }, 1056 + }, 1057 + ]) 1058 + ); 1059 + 1060 + expect(visited).toEqual([ 1061 + ['break-a', 'enter', 'Document', undefined], 1062 + ['break-b', 'enter', 'Document', undefined], 1063 + ['break-a', 'enter', 'OperationDefinition', undefined], 1064 + ['break-b', 'enter', 'OperationDefinition', undefined], 1065 + ['break-a', 'enter', 'SelectionSet', undefined], 1066 + ['break-b', 'enter', 'SelectionSet', undefined], 1067 + ['break-a', 'enter', 'Field', undefined], 1068 + ['break-b', 'enter', 'Field', undefined], 1069 + ['break-a', 'enter', 'Name', 'a'], 1070 + ['break-b', 'enter', 'Name', 'a'], 1071 + ['break-b', 'leave', 'Name', 'a'], 1072 + ['break-b', 'enter', 'SelectionSet', undefined], 1073 + ['break-b', 'enter', 'Field', undefined], 1074 + ['break-b', 'enter', 'Name', 'y'], 1075 + ['break-b', 'leave', 'Name', 'y'], 1076 + ['break-b', 'leave', 'Field', undefined], 1077 + ['break-b', 'leave', 'SelectionSet', undefined], 1078 + ['break-b', 'leave', 'Field', undefined], 1079 + ['break-b', 'enter', 'Field', undefined], 1080 + ['break-b', 'enter', 'Name', 'b'], 1081 + ]); 1082 + }); 1083 + 1084 + // Note: nearly identical to the above test of the same test but 1085 + // using visitInParallel. 1086 + it('allows early exit while leaving', () => { 1087 + const visited: Array<any> = []; 1088 + 1089 + const ast = parse('{ a, b { x }, c }'); 1090 + visit( 1091 + ast, 1092 + visitInParallel([ 1093 + { 1094 + enter(node) { 1095 + checkVisitorFnArgs(ast, arguments); 1096 + visited.push(['enter', node.kind, getValue(node)]); 1097 + }, 1098 + leave(node) { 1099 + checkVisitorFnArgs(ast, arguments); 1100 + visited.push(['leave', node.kind, getValue(node)]); 1101 + if (node.kind === 'Name' && node.value === 'x') { 1102 + return BREAK; 1103 + } 1104 + }, 1105 + }, 1106 + ]) 1107 + ); 1108 + 1109 + expect(visited).toEqual([ 1110 + ['enter', 'Document', undefined], 1111 + ['enter', 'OperationDefinition', undefined], 1112 + ['enter', 'SelectionSet', undefined], 1113 + ['enter', 'Field', undefined], 1114 + ['enter', 'Name', 'a'], 1115 + ['leave', 'Name', 'a'], 1116 + ['leave', 'Field', undefined], 1117 + ['enter', 'Field', undefined], 1118 + ['enter', 'Name', 'b'], 1119 + ['leave', 'Name', 'b'], 1120 + ['enter', 'SelectionSet', undefined], 1121 + ['enter', 'Field', undefined], 1122 + ['enter', 'Name', 'x'], 1123 + ['leave', 'Name', 'x'], 1124 + ]); 1125 + }); 1126 + 1127 + it('allows early exit from leaving different points', () => { 1128 + const visited: Array<any> = []; 1129 + 1130 + const ast = parse('{ a { y }, b { x } }'); 1131 + visit( 1132 + ast, 1133 + visitInParallel([ 1134 + { 1135 + enter(node) { 1136 + checkVisitorFnArgs(ast, arguments); 1137 + visited.push(['break-a', 'enter', node.kind, getValue(node)]); 1138 + }, 1139 + leave(node) { 1140 + checkVisitorFnArgs(ast, arguments); 1141 + visited.push(['break-a', 'leave', node.kind, getValue(node)]); 1142 + if (node.kind === 'Field' && node.name.value === 'a') { 1143 + return BREAK; 1144 + } 1145 + }, 1146 + }, 1147 + { 1148 + enter(node) { 1149 + checkVisitorFnArgs(ast, arguments); 1150 + visited.push(['break-b', 'enter', node.kind, getValue(node)]); 1151 + }, 1152 + leave(node) { 1153 + checkVisitorFnArgs(ast, arguments); 1154 + visited.push(['break-b', 'leave', node.kind, getValue(node)]); 1155 + if (node.kind === 'Field' && node.name.value === 'b') { 1156 + return BREAK; 1157 + } 1158 + }, 1159 + }, 1160 + ]) 1161 + ); 1162 + 1163 + expect(visited).toEqual([ 1164 + ['break-a', 'enter', 'Document', undefined], 1165 + ['break-b', 'enter', 'Document', undefined], 1166 + ['break-a', 'enter', 'OperationDefinition', undefined], 1167 + ['break-b', 'enter', 'OperationDefinition', undefined], 1168 + ['break-a', 'enter', 'SelectionSet', undefined], 1169 + ['break-b', 'enter', 'SelectionSet', undefined], 1170 + ['break-a', 'enter', 'Field', undefined], 1171 + ['break-b', 'enter', 'Field', undefined], 1172 + ['break-a', 'enter', 'Name', 'a'], 1173 + ['break-b', 'enter', 'Name', 'a'], 1174 + ['break-a', 'leave', 'Name', 'a'], 1175 + ['break-b', 'leave', 'Name', 'a'], 1176 + ['break-a', 'enter', 'SelectionSet', undefined], 1177 + ['break-b', 'enter', 'SelectionSet', undefined], 1178 + ['break-a', 'enter', 'Field', undefined], 1179 + ['break-b', 'enter', 'Field', undefined], 1180 + ['break-a', 'enter', 'Name', 'y'], 1181 + ['break-b', 'enter', 'Name', 'y'], 1182 + ['break-a', 'leave', 'Name', 'y'], 1183 + ['break-b', 'leave', 'Name', 'y'], 1184 + ['break-a', 'leave', 'Field', undefined], 1185 + ['break-b', 'leave', 'Field', undefined], 1186 + ['break-a', 'leave', 'SelectionSet', undefined], 1187 + ['break-b', 'leave', 'SelectionSet', undefined], 1188 + ['break-a', 'leave', 'Field', undefined], 1189 + ['break-b', 'leave', 'Field', undefined], 1190 + ['break-b', 'enter', 'Field', undefined], 1191 + ['break-b', 'enter', 'Name', 'b'], 1192 + ['break-b', 'leave', 'Name', 'b'], 1193 + ['break-b', 'enter', 'SelectionSet', undefined], 1194 + ['break-b', 'enter', 'Field', undefined], 1195 + ['break-b', 'enter', 'Name', 'x'], 1196 + ['break-b', 'leave', 'Name', 'x'], 1197 + ['break-b', 'leave', 'Field', undefined], 1198 + ['break-b', 'leave', 'SelectionSet', undefined], 1199 + ['break-b', 'leave', 'Field', undefined], 1200 + ]); 1201 + }); 1202 + 1203 + it('allows for editing on enter', () => { 1204 + const visited: Array<any> = []; 1205 + 1206 + const ast = parse('{ a, b, c { a, b, c } }', { noLocation: true }); 1207 + const editedAST = visit( 1208 + ast, 1209 + visitInParallel([ 1210 + { 1211 + enter(node) { 1212 + checkVisitorFnArgs(ast, arguments); 1213 + if (node.kind === 'Field' && node.name.value === 'b') { 1214 + return null; 1215 + } 1216 + }, 1217 + }, 1218 + { 1219 + enter(node) { 1220 + checkVisitorFnArgs(ast, arguments); 1221 + visited.push(['enter', node.kind, getValue(node)]); 1222 + }, 1223 + leave(node) { 1224 + checkVisitorFnArgs(ast, arguments, /* isEdited */ true); 1225 + visited.push(['leave', node.kind, getValue(node)]); 1226 + }, 1227 + }, 1228 + ]) 1229 + ); 1230 + 1231 + expect(ast).toEqual( 1232 + parse('{ a, b, c { a, b, c } }', { noLocation: true }) 1233 + ); 1234 + 1235 + expect(editedAST).toEqual( 1236 + parse('{ a, c { a, c } }', { noLocation: true }) 1237 + ); 1238 + 1239 + expect(visited).toEqual([ 1240 + ['enter', 'Document', undefined], 1241 + ['enter', 'OperationDefinition', undefined], 1242 + ['enter', 'SelectionSet', undefined], 1243 + ['enter', 'Field', undefined], 1244 + ['enter', 'Name', 'a'], 1245 + ['leave', 'Name', 'a'], 1246 + ['leave', 'Field', undefined], 1247 + ['enter', 'Field', undefined], 1248 + ['enter', 'Name', 'c'], 1249 + ['leave', 'Name', 'c'], 1250 + ['enter', 'SelectionSet', undefined], 1251 + ['enter', 'Field', undefined], 1252 + ['enter', 'Name', 'a'], 1253 + ['leave', 'Name', 'a'], 1254 + ['leave', 'Field', undefined], 1255 + ['enter', 'Field', undefined], 1256 + ['enter', 'Name', 'c'], 1257 + ['leave', 'Name', 'c'], 1258 + ['leave', 'Field', undefined], 1259 + ['leave', 'SelectionSet', undefined], 1260 + ['leave', 'Field', undefined], 1261 + ['leave', 'SelectionSet', undefined], 1262 + ['leave', 'OperationDefinition', undefined], 1263 + ['leave', 'Document', undefined], 1264 + ]); 1265 + }); 1266 + 1267 + it('allows for editing on leave', () => { 1268 + const visited: Array<any> = []; 1269 + 1270 + const ast = parse('{ a, b, c { a, b, c } }', { noLocation: true }); 1271 + const editedAST = visit( 1272 + ast, 1273 + visitInParallel([ 1274 + { 1275 + leave(node) { 1276 + checkVisitorFnArgs(ast, arguments, /* isEdited */ true); 1277 + if (node.kind === 'Field' && node.name.value === 'b') { 1278 + return null; 1279 + } 1280 + }, 1281 + }, 1282 + { 1283 + enter(node) { 1284 + checkVisitorFnArgs(ast, arguments); 1285 + visited.push(['enter', node.kind, getValue(node)]); 1286 + }, 1287 + leave(node) { 1288 + checkVisitorFnArgs(ast, arguments, /* isEdited */ true); 1289 + visited.push(['leave', node.kind, getValue(node)]); 1290 + }, 1291 + }, 1292 + ]) 1293 + ); 1294 + 1295 + expect(ast).toEqual( 1296 + parse('{ a, b, c { a, b, c } }', { noLocation: true }) 1297 + ); 1298 + 1299 + expect(editedAST).toEqual( 1300 + parse('{ a, c { a, c } }', { noLocation: true }) 1301 + ); 1302 + 1303 + expect(visited).toEqual([ 1304 + ['enter', 'Document', undefined], 1305 + ['enter', 'OperationDefinition', undefined], 1306 + ['enter', 'SelectionSet', undefined], 1307 + ['enter', 'Field', undefined], 1308 + ['enter', 'Name', 'a'], 1309 + ['leave', 'Name', 'a'], 1310 + ['leave', 'Field', undefined], 1311 + ['enter', 'Field', undefined], 1312 + ['enter', 'Name', 'b'], 1313 + ['leave', 'Name', 'b'], 1314 + ['enter', 'Field', undefined], 1315 + ['enter', 'Name', 'c'], 1316 + ['leave', 'Name', 'c'], 1317 + ['enter', 'SelectionSet', undefined], 1318 + ['enter', 'Field', undefined], 1319 + ['enter', 'Name', 'a'], 1320 + ['leave', 'Name', 'a'], 1321 + ['leave', 'Field', undefined], 1322 + ['enter', 'Field', undefined], 1323 + ['enter', 'Name', 'b'], 1324 + ['leave', 'Name', 'b'], 1325 + ['enter', 'Field', undefined], 1326 + ['enter', 'Name', 'c'], 1327 + ['leave', 'Name', 'c'], 1328 + ['leave', 'Field', undefined], 1329 + ['leave', 'SelectionSet', undefined], 1330 + ['leave', 'Field', undefined], 1331 + ['leave', 'SelectionSet', undefined], 1332 + ['leave', 'OperationDefinition', undefined], 1333 + ['leave', 'Document', undefined], 1334 + ]); 1335 + }); 1336 + }); 1337 + });
+34 -21
alias/language/visitor.mjs
··· 17 17 18 18 function traverse(node, key, parent) { 19 19 let hasEdited = false; 20 - let result; 21 20 22 - result = callback(node, key, parent, false); 23 - if (result === false) { 24 - return; 25 - } else if (result && typeof result.kind === 'string') { 26 - hasEdited = true; 27 - node = result; 21 + const resultEnter = callback(node, key, parent, false); 22 + if (resultEnter === false) { 23 + return node; 24 + } else if (resultEnter === null) { 25 + return null; 26 + } else if (resultEnter && typeof resultEnter.kind === 'string') { 27 + hasEdited = resultEnter !== node; 28 + node = resultEnter; 28 29 } 29 30 30 - ancestors.push(node); 31 + if (parent) ancestors.push(parent); 32 + 33 + let result; 31 34 const copy = { ...node }; 32 - 33 35 for (const nodeKey in node) { 34 - let value = node[nodeKey]; 35 36 path.push(nodeKey); 37 + let value = node[nodeKey]; 36 38 if (Array.isArray(value)) { 37 - value = value.slice(); 39 + const newValue = []; 38 40 for (let index = 0; index < value.length; index++) { 39 41 if (value[index] != null && typeof value[index].kind === 'string') { 42 + ancestors.push(node); 40 43 path.push(index); 41 - result = traverse(value[index], index, node); 44 + result = traverse(value[index], index, value); 42 45 path.pop(); 43 - if (result !== undefined) { 44 - value[index] = result; 46 + ancestors.pop(); 47 + if (result === undefined) { 48 + newValue.push(value[index]); 49 + } else if (result === null) { 45 50 hasEdited = true; 51 + } else { 52 + hasEdited = hasEdited || result !== value[index]; 53 + newValue.push(result); 46 54 } 47 55 } 48 56 } 57 + value = newValue; 49 58 } else if (value != null && typeof value.kind === 'string') { 50 59 result = traverse(value, nodeKey, node); 51 60 if (result !== undefined) { 61 + hasEdited = hasEdited || value !== result; 52 62 value = result; 53 - hasEdited = true; 54 63 } 55 64 } 56 65 ··· 58 67 if (hasEdited) copy[nodeKey] = value; 59 68 } 60 69 61 - ancestors.pop(); 62 - 63 - if (hasEdited) node = copy; 64 - result = callback(node, key, parent, true); 65 - return hasEdited && result === undefined ? node : result; 70 + if (parent) ancestors.pop(); 71 + const resultLeave = callback(node, key, parent, true); 72 + if (resultLeave !== undefined) { 73 + return resultLeave; 74 + } else if (resultEnter !== undefined) { 75 + return resultEnter; 76 + } else { 77 + return hasEdited ? copy : node; 78 + } 66 79 } 67 80 68 81 try { 69 82 const result = traverse(node); 70 - return result !== undefined ? result : node; 83 + return result !== undefined && result !== false ? result : node; 71 84 } catch (error) { 72 85 if (error !== BREAK) throw error; 73 86 return node;
+1
package.json
··· 7 7 "scripts/buildenv" 8 8 ], 9 9 "scripts": { 10 + "test": "jest", 10 11 "build": "rollup -c scripts/rollup/config.js", 11 12 "size-check": "yarn workspace @graphql-web-lite/buildenv build" 12 13 },