Skip to content

Commit a509dab

Browse files
committed
Improved REMOVE object as a "falsey" + Parallel visitor supports editing
Related GraphQL-js commit: graphql/graphql-js@bd87fd3
1 parent 5cc0b18 commit a509dab

File tree

2 files changed

+128
-3
lines changed

2 files changed

+128
-3
lines changed

graphql/core/language/visitor.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,17 @@
55
from . import ast
66
from .visitor_meta import QUERY_DOCUMENT_KEYS, VisitorMeta
77

8+
9+
class Falsey(object):
10+
def __nonzero__(self):
11+
return False
12+
13+
def __bool__(self):
14+
return False
15+
16+
817
BREAK = object()
9-
REMOVE = object()
18+
REMOVE = Falsey()
1019

1120

1221
class Stack(object):
@@ -90,7 +99,7 @@ def visit(root, visitor, key_map=None):
9099
key = None
91100
node = new_root
92101

93-
if node is None:
102+
if node is REMOVE or node is None:
94103
continue
95104

96105
if parent:
@@ -179,15 +188,19 @@ def enter(self, node, key, parent, path, ancestors):
179188
self.skipping[i] = node
180189
elif result is BREAK:
181190
self.skipping[i] = BREAK
191+
elif result is not None:
192+
return result
182193

183194
def leave(self, node, key, parent, path, ancestors):
184195
for i, visitor in enumerate(self.visitors):
185196
if not self.skipping[i]:
186197
result = visitor.leave(node, key, parent, path, ancestors)
187198
if result is BREAK:
188199
self.skipping[i] = BREAK
200+
elif result is not None and result is not False:
201+
return result
189202
elif self.skipping[i] == node:
190-
self.skipping[i] = None
203+
self.skipping[i] = REMOVE
191204

192205

193206
class TypeInfoVisitor(Visitor):

tests/core_language/test_visitor.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,3 +674,115 @@ def leave(self, node, key, parent, *args):
674674
['break-b', 'leave', 'SelectionSet', None],
675675
['break-b', 'leave', 'Field', None]
676676
]
677+
678+
679+
def test_visits_in_pararell_allows_for_editing_on_enter():
680+
visited = []
681+
ast = parse('{ a, b, c { a, b, c } }', no_location=True)
682+
683+
class TestVisitor1(Visitor):
684+
685+
def enter(self, node, key, parent, *args):
686+
if type(node).__name__ == 'Field' and node.name.value == 'b':
687+
return REMOVE
688+
689+
class TestVisitor2(Visitor):
690+
691+
def enter(self, node, key, parent, *args):
692+
visited.append(
693+
['enter', type(node).__name__, getattr(node, 'value', None)])
694+
695+
def leave(self, node, key, parent, *args):
696+
visited.append(
697+
['leave', type(node).__name__, getattr(node, 'value', None)])
698+
699+
edited_ast = visit(ast, ParallelVisitor([TestVisitor1(), TestVisitor2()]))
700+
701+
assert ast == parse('{ a, b, c { a, b, c } }', no_location=True)
702+
assert edited_ast == parse('{ a, c { a, c } }', no_location=True)
703+
704+
assert visited == [
705+
['enter', 'Document', None],
706+
['enter', 'OperationDefinition', None],
707+
['enter', 'SelectionSet', None],
708+
['enter', 'Field', None],
709+
['enter', 'Name', 'a'],
710+
['leave', 'Name', 'a'],
711+
['leave', 'Field', None],
712+
['enter', 'Field', None],
713+
['enter', 'Name', 'c'],
714+
['leave', 'Name', 'c'],
715+
['enter', 'SelectionSet', None],
716+
['enter', 'Field', None],
717+
['enter', 'Name', 'a'],
718+
['leave', 'Name', 'a'],
719+
['leave', 'Field', None],
720+
['enter', 'Field', None],
721+
['enter', 'Name', 'c'],
722+
['leave', 'Name', 'c'],
723+
['leave', 'Field', None],
724+
['leave', 'SelectionSet', None],
725+
['leave', 'Field', None],
726+
['leave', 'SelectionSet', None],
727+
['leave', 'OperationDefinition', None],
728+
['leave', 'Document', None]
729+
]
730+
731+
732+
def test_visits_in_pararell_allows_for_editing_on_leave():
733+
visited = []
734+
ast = parse('{ a, b, c { a, b, c } }', no_location=True)
735+
736+
class TestVisitor1(Visitor):
737+
738+
def leave(self, node, key, parent, *args):
739+
if type(node).__name__ == 'Field' and node.name.value == 'b':
740+
return REMOVE
741+
742+
class TestVisitor2(Visitor):
743+
744+
def enter(self, node, key, parent, *args):
745+
visited.append(
746+
['enter', type(node).__name__, getattr(node, 'value', None)])
747+
748+
def leave(self, node, key, parent, *args):
749+
visited.append(
750+
['leave', type(node).__name__, getattr(node, 'value', None)])
751+
752+
edited_ast = visit(ast, ParallelVisitor([TestVisitor1(), TestVisitor2()]))
753+
754+
assert ast == parse('{ a, b, c { a, b, c } }', no_location=True)
755+
assert edited_ast == parse('{ a, c { a, c } }', no_location=True)
756+
757+
assert visited == [
758+
['enter', 'Document', None],
759+
['enter', 'OperationDefinition', None],
760+
['enter', 'SelectionSet', None],
761+
['enter', 'Field', None],
762+
['enter', 'Name', 'a'],
763+
['leave', 'Name', 'a'],
764+
['leave', 'Field', None],
765+
['enter', 'Field', None],
766+
['enter', 'Name', 'b'],
767+
['leave', 'Name', 'b'],
768+
['enter', 'Field', None],
769+
['enter', 'Name', 'c'],
770+
['leave', 'Name', 'c'],
771+
['enter', 'SelectionSet', None],
772+
['enter', 'Field', None],
773+
['enter', 'Name', 'a'],
774+
['leave', 'Name', 'a'],
775+
['leave', 'Field', None],
776+
['enter', 'Field', None],
777+
['enter', 'Name', 'b'],
778+
['leave', 'Name', 'b'],
779+
['enter', 'Field', None],
780+
['enter', 'Name', 'c'],
781+
['leave', 'Name', 'c'],
782+
['leave', 'Field', None],
783+
['leave', 'SelectionSet', None],
784+
['leave', 'Field', None],
785+
['leave', 'SelectionSet', None],
786+
['leave', 'OperationDefinition', None],
787+
['leave', 'Document', None]
788+
]

0 commit comments

Comments
 (0)