Skip to content

Commit b2bdae0

Browse files
committed
Support breaking in parallel visitor
1 parent 699dc10 commit b2bdae0

File tree

2 files changed

+240
-2
lines changed

2 files changed

+240
-2
lines changed

src/language/__tests__/visitor.js

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,42 @@ describe('Visitor', () => {
156156
]);
157157
});
158158

159+
it('allows early exit while leaving', () => {
160+
161+
var visited = [];
162+
163+
var ast = parse('{ a, b { x }, c }');
164+
visit(ast, {
165+
enter(node) {
166+
visited.push([ 'enter', node.kind, node.value ]);
167+
},
168+
169+
leave(node) {
170+
visited.push([ 'leave', node.kind, node.value ]);
171+
if (node.kind === 'Name' && node.value === 'x') {
172+
return BREAK;
173+
}
174+
}
175+
});
176+
177+
expect(visited).to.deep.equal([
178+
[ 'enter', 'Document', undefined ],
179+
[ 'enter', 'OperationDefinition', undefined ],
180+
[ 'enter', 'SelectionSet', undefined ],
181+
[ 'enter', 'Field', undefined ],
182+
[ 'enter', 'Name', 'a' ],
183+
[ 'leave', 'Name', 'a' ],
184+
[ 'leave', 'Field', undefined ],
185+
[ 'enter', 'Field', undefined ],
186+
[ 'enter', 'Name', 'b' ],
187+
[ 'leave', 'Name', 'b' ],
188+
[ 'enter', 'SelectionSet', undefined ],
189+
[ 'enter', 'Field', undefined ],
190+
[ 'enter', 'Name', 'x' ],
191+
[ 'leave', 'Name', 'x' ]
192+
]);
193+
});
194+
159195
it('allows a named functions visitor API', () => {
160196

161197
var visited = [];
@@ -618,6 +654,203 @@ describe('Visitor', () => {
618654
]);
619655
});
620656

657+
// Note: nearly identical to the above test of the same test but
658+
// using visitInParallel.
659+
it('allows early exit while visiting', () => {
660+
661+
var visited = [];
662+
663+
var ast = parse('{ a, b { x }, c }');
664+
visit(ast, visitInParallel([ {
665+
enter(node) {
666+
visited.push([ 'enter', node.kind, node.value ]);
667+
if (node.kind === 'Name' && node.value === 'x') {
668+
return BREAK;
669+
}
670+
},
671+
leave(node) {
672+
visited.push([ 'leave', node.kind, node.value ]);
673+
}
674+
} ]));
675+
676+
expect(visited).to.deep.equal([
677+
[ 'enter', 'Document', undefined ],
678+
[ 'enter', 'OperationDefinition', undefined ],
679+
[ 'enter', 'SelectionSet', undefined ],
680+
[ 'enter', 'Field', undefined ],
681+
[ 'enter', 'Name', 'a' ],
682+
[ 'leave', 'Name', 'a' ],
683+
[ 'leave', 'Field', undefined ],
684+
[ 'enter', 'Field', undefined ],
685+
[ 'enter', 'Name', 'b' ],
686+
[ 'leave', 'Name', 'b' ],
687+
[ 'enter', 'SelectionSet', undefined ],
688+
[ 'enter', 'Field', undefined ],
689+
[ 'enter', 'Name', 'x' ]
690+
]);
691+
});
692+
693+
it('allows early exit from different points', () => {
694+
695+
var visited = [];
696+
697+
var ast = parse('{ a { y }, b { x } }');
698+
visit(ast, visitInParallel([
699+
{
700+
enter(node) {
701+
visited.push([ 'break-a', 'enter', node.kind, node.value ]);
702+
if (node.kind === 'Name' && node.value === 'a') {
703+
return BREAK;
704+
}
705+
},
706+
leave(node) {
707+
visited.push([ 'break-a', 'leave', node.kind, node.value ]);
708+
}
709+
},
710+
{
711+
enter(node) {
712+
visited.push([ 'break-b', 'enter', node.kind, node.value ]);
713+
if (node.kind === 'Name' && node.value === 'b') {
714+
return BREAK;
715+
}
716+
},
717+
leave(node) {
718+
visited.push([ 'break-b', 'leave', node.kind, node.value ]);
719+
}
720+
},
721+
]));
722+
723+
expect(visited).to.deep.equal([
724+
[ 'break-a', 'enter', 'Document', undefined ],
725+
[ 'break-b', 'enter', 'Document', undefined ],
726+
[ 'break-a', 'enter', 'OperationDefinition', undefined ],
727+
[ 'break-b', 'enter', 'OperationDefinition', undefined ],
728+
[ 'break-a', 'enter', 'SelectionSet', undefined ],
729+
[ 'break-b', 'enter', 'SelectionSet', undefined ],
730+
[ 'break-a', 'enter', 'Field', undefined ],
731+
[ 'break-b', 'enter', 'Field', undefined ],
732+
[ 'break-a', 'enter', 'Name', 'a' ],
733+
[ 'break-b', 'enter', 'Name', 'a' ],
734+
[ 'break-b', 'leave', 'Name', 'a' ],
735+
[ 'break-b', 'enter', 'SelectionSet', undefined ],
736+
[ 'break-b', 'enter', 'Field', undefined ],
737+
[ 'break-b', 'enter', 'Name', 'y' ],
738+
[ 'break-b', 'leave', 'Name', 'y' ],
739+
[ 'break-b', 'leave', 'Field', undefined ],
740+
[ 'break-b', 'leave', 'SelectionSet', undefined ],
741+
[ 'break-b', 'leave', 'Field', undefined ],
742+
[ 'break-b', 'enter', 'Field', undefined ],
743+
[ 'break-b', 'enter', 'Name', 'b' ]
744+
]);
745+
});
746+
747+
// Note: nearly identical to the above test of the same test but
748+
// using visitInParallel.
749+
it('allows early exit while leaving', () => {
750+
751+
var visited = [];
752+
753+
var ast = parse('{ a, b { x }, c }');
754+
visit(ast, visitInParallel([ {
755+
enter(node) {
756+
visited.push([ 'enter', node.kind, node.value ]);
757+
},
758+
leave(node) {
759+
visited.push([ 'leave', node.kind, node.value ]);
760+
if (node.kind === 'Name' && node.value === 'x') {
761+
return BREAK;
762+
}
763+
}
764+
} ]));
765+
766+
expect(visited).to.deep.equal([
767+
[ 'enter', 'Document', undefined ],
768+
[ 'enter', 'OperationDefinition', undefined ],
769+
[ 'enter', 'SelectionSet', undefined ],
770+
[ 'enter', 'Field', undefined ],
771+
[ 'enter', 'Name', 'a' ],
772+
[ 'leave', 'Name', 'a' ],
773+
[ 'leave', 'Field', undefined ],
774+
[ 'enter', 'Field', undefined ],
775+
[ 'enter', 'Name', 'b' ],
776+
[ 'leave', 'Name', 'b' ],
777+
[ 'enter', 'SelectionSet', undefined ],
778+
[ 'enter', 'Field', undefined ],
779+
[ 'enter', 'Name', 'x' ],
780+
[ 'leave', 'Name', 'x' ]
781+
]);
782+
});
783+
784+
it('allows early exit from leaving different points', () => {
785+
786+
var visited = [];
787+
788+
var ast = parse('{ a { y }, b { x } }');
789+
visit(ast, visitInParallel([
790+
{
791+
enter(node) {
792+
visited.push([ 'break-a', 'enter', node.kind, node.value ]);
793+
},
794+
leave(node) {
795+
visited.push([ 'break-a', 'leave', node.kind, node.value ]);
796+
if (node.kind === 'Field' && node.name.value === 'a') {
797+
return BREAK;
798+
}
799+
}
800+
},
801+
{
802+
enter(node) {
803+
visited.push([ 'break-b', 'enter', node.kind, node.value ]);
804+
},
805+
leave(node) {
806+
visited.push([ 'break-b', 'leave', node.kind, node.value ]);
807+
if (node.kind === 'Field' && node.name.value === 'b') {
808+
return BREAK;
809+
}
810+
}
811+
},
812+
]));
813+
814+
expect(visited).to.deep.equal([
815+
[ 'break-a', 'enter', 'Document', undefined ],
816+
[ 'break-b', 'enter', 'Document', undefined ],
817+
[ 'break-a', 'enter', 'OperationDefinition', undefined ],
818+
[ 'break-b', 'enter', 'OperationDefinition', undefined ],
819+
[ 'break-a', 'enter', 'SelectionSet', undefined ],
820+
[ 'break-b', 'enter', 'SelectionSet', undefined ],
821+
[ 'break-a', 'enter', 'Field', undefined ],
822+
[ 'break-b', 'enter', 'Field', undefined ],
823+
[ 'break-a', 'enter', 'Name', 'a' ],
824+
[ 'break-b', 'enter', 'Name', 'a' ],
825+
[ 'break-a', 'leave', 'Name', 'a' ],
826+
[ 'break-b', 'leave', 'Name', 'a' ],
827+
[ 'break-a', 'enter', 'SelectionSet', undefined ],
828+
[ 'break-b', 'enter', 'SelectionSet', undefined ],
829+
[ 'break-a', 'enter', 'Field', undefined ],
830+
[ 'break-b', 'enter', 'Field', undefined ],
831+
[ 'break-a', 'enter', 'Name', 'y' ],
832+
[ 'break-b', 'enter', 'Name', 'y' ],
833+
[ 'break-a', 'leave', 'Name', 'y' ],
834+
[ 'break-b', 'leave', 'Name', 'y' ],
835+
[ 'break-a', 'leave', 'Field', undefined ],
836+
[ 'break-b', 'leave', 'Field', undefined ],
837+
[ 'break-a', 'leave', 'SelectionSet', undefined ],
838+
[ 'break-b', 'leave', 'SelectionSet', undefined ],
839+
[ 'break-a', 'leave', 'Field', undefined ],
840+
[ 'break-b', 'leave', 'Field', undefined ],
841+
[ 'break-b', 'enter', 'Field', undefined ],
842+
[ 'break-b', 'enter', 'Name', 'b' ],
843+
[ 'break-b', 'leave', 'Name', 'b' ],
844+
[ 'break-b', 'enter', 'SelectionSet', undefined ],
845+
[ 'break-b', 'enter', 'Field', undefined ],
846+
[ 'break-b', 'enter', 'Name', 'x' ],
847+
[ 'break-b', 'leave', 'Name', 'x' ],
848+
[ 'break-b', 'leave', 'Field', undefined ],
849+
[ 'break-b', 'leave', 'SelectionSet', undefined ],
850+
[ 'break-b', 'leave', 'Field', undefined ]
851+
]);
852+
});
853+
621854
});
622855

623856
});

src/language/visitor.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ function isNode(maybeNode) {
269269
* parallel. Each visitor will be visited for each node before moving on.
270270
*
271271
* Visitors must not directly modify the AST nodes and only returning false to
272-
* skip sub-branches is supported.
272+
* skip sub-branches or BREAK to exit early is supported.
273273
*/
274274
export function visitInParallel(visitors) {
275275
const skipping = new Array(visitors.length);
@@ -283,6 +283,8 @@ export function visitInParallel(visitors) {
283283
const result = fn.apply(visitors[i], arguments);
284284
if (result === false) {
285285
skipping[i] = node;
286+
} else if (result === BREAK) {
287+
skipping[i] = BREAK;
286288
}
287289
}
288290
}
@@ -293,7 +295,10 @@ export function visitInParallel(visitors) {
293295
if (!skipping[i]) {
294296
const fn = getVisitFn(visitors[i], node.kind, /* isLeaving */ true);
295297
if (fn) {
296-
fn.apply(visitors[i], arguments);
298+
const result = fn.apply(visitors[i], arguments);
299+
if (result === BREAK) {
300+
skipping[i] = BREAK;
301+
}
297302
}
298303
} else if (skipping[i] === node) {
299304
skipping[i] = null;

0 commit comments

Comments
 (0)