Skip to content

Commit 1ef33d8

Browse files
committed
Implement NoUnusedFragments
- unit tests as well. #6
1 parent 1f0c725 commit 1ef33d8

File tree

2 files changed

+192
-1
lines changed

2 files changed

+192
-1
lines changed

graphql/core/validation/rules.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,55 @@ class KnownFragmentNames(ValidationRule):
171171

172172

173173
class NoUnusedFragments(ValidationRule):
174-
pass
174+
def __init__(self, context):
175+
super(NoUnusedFragments, self).__init__(context)
176+
self.fragment_definitions = []
177+
self.spreads_within_operation = []
178+
self.fragment_adjacencies = {}
179+
self.spread_names = set()
180+
181+
def enter_OperationDefinition(self, *args):
182+
self.spread_names = set()
183+
self.spreads_within_operation.append(self.spread_names)
184+
185+
def enter_FragmentDefinition(self, node, *args):
186+
self.fragment_definitions.append(node)
187+
self.spread_names = set()
188+
self.fragment_adjacencies[node.name.value] = self.spread_names
189+
190+
def enter_FragmentSpread(self, node, *args):
191+
self.spread_names.add(node.name.value)
192+
193+
def leave_Document(self, *args):
194+
fragment_names_used = set()
195+
196+
def reduce_spread_fragments(spreads):
197+
for fragment_name in spreads:
198+
if fragment_name in fragment_names_used:
199+
continue
200+
201+
fragment_names_used.add(fragment_name)
202+
if fragment_name in self.fragment_adjacencies:
203+
reduce_spread_fragments(self.fragment_adjacencies[fragment_name])
204+
205+
for spreads in self.spreads_within_operation:
206+
reduce_spread_fragments(spreads)
207+
208+
errors = [
209+
GraphQLError(
210+
self.unused_fragment_message(fragment_definition.name.value),
211+
[fragment_definition]
212+
)
213+
for fragment_definition in self.fragment_definitions
214+
if fragment_definition.name.value not in fragment_names_used
215+
]
216+
217+
if errors:
218+
return errors
219+
220+
@staticmethod
221+
def unused_fragment_message(fragment_name):
222+
return 'Fragment "{}" is never used.'.format(fragment_name)
175223

176224

177225
class PossibleFragmentSpreads(ValidationRule):
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
from graphql.core.language.location import SourceLocation
2+
from graphql.core.validation.rules import NoUnusedFragments
3+
from utils import expect_passes_rule, expect_fails_rule
4+
5+
6+
def unused_fragment(fragment_name, line, column):
7+
return {
8+
'message': NoUnusedFragments.unused_fragment_message(fragment_name),
9+
'locations': [SourceLocation(line, column)]
10+
}
11+
12+
13+
def test_all_fragment_names_are_used():
14+
expect_passes_rule(NoUnusedFragments, '''
15+
{
16+
human(id: 4) {
17+
...HumanFields1
18+
... on Human {
19+
...HumanFields2
20+
}
21+
}
22+
}
23+
fragment HumanFields1 on Human {
24+
name
25+
...HumanFields3
26+
}
27+
fragment HumanFields2 on Human {
28+
name
29+
}
30+
fragment HumanFields3 on Human {
31+
name
32+
}
33+
''')
34+
35+
36+
def test_all_fragment_names_are_used_by_multiple_operations():
37+
expect_passes_rule(NoUnusedFragments, '''
38+
query Foo {
39+
human(id: 4) {
40+
...HumanFields1
41+
}
42+
}
43+
query Bar {
44+
human(id: 4) {
45+
...HumanFields2
46+
}
47+
}
48+
fragment HumanFields1 on Human {
49+
name
50+
...HumanFields3
51+
}
52+
fragment HumanFields2 on Human {
53+
name
54+
}
55+
fragment HumanFields3 on Human {
56+
name
57+
}
58+
''')
59+
60+
61+
def test_contains_unknown_fragments():
62+
expect_fails_rule(NoUnusedFragments, '''
63+
query Foo {
64+
human(id: 4) {
65+
...HumanFields1
66+
}
67+
}
68+
query Bar {
69+
human(id: 4) {
70+
...HumanFields2
71+
}
72+
}
73+
fragment HumanFields1 on Human {
74+
name
75+
...HumanFields3
76+
}
77+
fragment HumanFields2 on Human {
78+
name
79+
}
80+
fragment HumanFields3 on Human {
81+
name
82+
}
83+
fragment Unused1 on Human {
84+
name
85+
}
86+
fragment Unused2 on Human {
87+
name
88+
}
89+
''', [
90+
unused_fragment('Unused1', 22, 7),
91+
unused_fragment('Unused2', 25, 7),
92+
])
93+
94+
95+
def test_contains_unknown_fragments_with_ref_cycle():
96+
expect_fails_rule(NoUnusedFragments, '''
97+
query Foo {
98+
human(id: 4) {
99+
...HumanFields1
100+
}
101+
}
102+
query Bar {
103+
human(id: 4) {
104+
...HumanFields2
105+
}
106+
}
107+
fragment HumanFields1 on Human {
108+
name
109+
...HumanFields3
110+
}
111+
fragment HumanFields2 on Human {
112+
name
113+
}
114+
fragment HumanFields3 on Human {
115+
name
116+
}
117+
fragment Unused1 on Human {
118+
name
119+
...Unused2
120+
}
121+
fragment Unused2 on Human {
122+
name
123+
...Unused1
124+
}
125+
''', [
126+
unused_fragment('Unused1', 22, 7),
127+
unused_fragment('Unused2', 26, 7),
128+
])
129+
130+
131+
def test_contains_unknown_and_undefined_fragments():
132+
expect_fails_rule(NoUnusedFragments, '''
133+
query Foo {
134+
human(id: 4) {
135+
...bar
136+
}
137+
}
138+
fragment foo on Human {
139+
name
140+
}
141+
''', [
142+
unused_fragment('foo', 7, 7)
143+
])

0 commit comments

Comments
 (0)