Skip to content

Commit cc30367

Browse files
committed
separate_operations: distinguish query and fragment names
Replicates graphql/graphql-js@cd273ad
1 parent f1656ef commit cc30367

File tree

2 files changed

+194
-125
lines changed

2 files changed

+194
-125
lines changed
Lines changed: 47 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1-
from collections import defaultdict
2-
from typing import Any, DefaultDict, Dict, List, Set
1+
from typing import Any, Dict, List, Set
32

43
from ..language import (
54
DocumentNode,
65
FragmentDefinitionNode,
76
FragmentSpreadNode,
87
OperationDefinitionNode,
8+
SelectionSetNode,
99
Visitor,
1010
visit,
1111
)
1212

1313
__all__ = ["separate_operations"]
1414

1515

16-
DepGraph = DefaultDict[str, Set[str]]
16+
DepGraph = Dict[str, List[str]]
1717

1818

1919
def separate_operations(document_ast: DocumentNode) -> Dict[str, DocumentNode]:
@@ -23,19 +23,31 @@ def separate_operations(document_ast: DocumentNode) -> Dict[str, DocumentNode]:
2323
fragments and returns a collection of AST documents each of which contains a single
2424
operation as well the fragment definitions it refers to.
2525
"""
26+
operations: List[OperationDefinitionNode] = []
27+
dep_graph: DepGraph = {}
28+
2629
# Populate metadata and build a dependency graph.
27-
visitor = SeparateOperations()
28-
visit(document_ast, visitor)
29-
operations = visitor.operations
30-
dep_graph = visitor.dep_graph
30+
for definition_node in document_ast.definitions:
31+
if isinstance(definition_node, OperationDefinitionNode):
32+
operations.append(definition_node)
33+
elif isinstance(
34+
definition_node, FragmentDefinitionNode
35+
): # pragma: no cover else
36+
dep_graph[definition_node.name.value] = collect_dependencies(
37+
definition_node.selection_set
38+
)
3139

3240
# For each operation, produce a new synthesized AST which includes only what is
3341
# necessary for completing that operation.
34-
separated_document_asts = {}
42+
separated_document_asts: Dict[str, DocumentNode] = {}
3543
for operation in operations:
36-
operation_name = op_name(operation)
3744
dependencies: Set[str] = set()
38-
collect_transitive_dependencies(dependencies, dep_graph, operation_name)
45+
46+
for fragment_name in collect_dependencies(operation.selection_set):
47+
collect_transitive_dependencies(dependencies, dep_graph, fragment_name)
48+
49+
# Provides the empty string for anonymous operations.
50+
operation_name = operation.name.value if operation.name else ""
3951

4052
# The list of definition nodes to be included for this operation, sorted
4153
# to retain the same order as the original document.
@@ -54,36 +66,6 @@ def separate_operations(document_ast: DocumentNode) -> Dict[str, DocumentNode]:
5466
return separated_document_asts
5567

5668

57-
class SeparateOperations(Visitor):
58-
operations: List[OperationDefinitionNode]
59-
dep_graph: DepGraph
60-
from_name: str
61-
62-
def __init__(self) -> None:
63-
super().__init__()
64-
self.operations = []
65-
self.dep_graph = defaultdict(set)
66-
67-
def enter_operation_definition(
68-
self, node: OperationDefinitionNode, *_args: Any
69-
) -> None:
70-
self.from_name = op_name(node)
71-
self.operations.append(node)
72-
73-
def enter_fragment_definition(
74-
self, node: FragmentDefinitionNode, *_args: Any
75-
) -> None:
76-
self.from_name = node.name.value
77-
78-
def enter_fragment_spread(self, node: FragmentSpreadNode, *_args: Any) -> None:
79-
self.dep_graph[self.from_name].add(node.name.value)
80-
81-
82-
def op_name(operation: OperationDefinitionNode) -> str:
83-
"""Provide the empty string for anonymous operations."""
84-
return operation.name.value if operation.name else ""
85-
86-
8769
def collect_transitive_dependencies(
8870
collected: Set[str], dep_graph: DepGraph, from_name: str
8971
) -> None:
@@ -92,8 +74,28 @@ def collect_transitive_dependencies(
9274
From a dependency graph, collects a list of transitive dependencies by recursing
9375
through a dependency graph.
9476
"""
95-
immediate_deps = dep_graph[from_name]
96-
for to_name in immediate_deps:
97-
if to_name not in collected:
98-
collected.add(to_name)
99-
collect_transitive_dependencies(collected, dep_graph, to_name)
77+
if from_name not in collected:
78+
collected.add(from_name)
79+
80+
immediate_deps = dep_graph.get(from_name)
81+
if immediate_deps is not None:
82+
for to_name in immediate_deps:
83+
collect_transitive_dependencies(collected, dep_graph, to_name)
84+
85+
86+
class DependencyCollector(Visitor):
87+
dependencies: List[str]
88+
89+
def __init__(self) -> None:
90+
super().__init__()
91+
self.dependencies = []
92+
self.add_dependency = self.dependencies.append
93+
94+
def enter_fragment_spread(self, node: FragmentSpreadNode, *_args: Any) -> None:
95+
self.add_dependency(node.name.value)
96+
97+
98+
def collect_dependencies(selection_set: SelectionSetNode) -> List[str]:
99+
collector = DependencyCollector()
100+
visit(selection_set, collector)
101+
return collector.dependencies

0 commit comments

Comments
 (0)