Skip to content

Commit b3c73dc

Browse files
Enforce require-selections on FragmentSpreads within GraphQLUnionTypes (#2319)
* Add failing test * Support fragment spreads in union types in `require-selections` * Add changeset * upd snapshot * more * more * more * more --------- Co-authored-by: Dimitri POSTOLOV <[email protected]>
1 parent 0bc742b commit b3c73dc

File tree

6 files changed

+130
-3
lines changed

6 files changed

+130
-3
lines changed

.changeset/empty-horses-relate.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@
22
'@graphql-eslint/eslint-plugin': patch
33
---
44

5-
fix false positive cases for `require-import-fragment` on Windows, when `graphql-config`'s `documents` key contained glob pattern => source file path of document contained always forward slashes
5+
fix false positive cases for `require-import-fragment` on Windows, when `graphql-config`'s
6+
`documents` key contained glob pattern => source file path of document contained always forward
7+
slashes

.changeset/empty-singers-develop.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
"@graphql-eslint/eslint-plugin": minor
2+
'@graphql-eslint/eslint-plugin': minor
33
---
44

55
feat: add a new option `{` for alphabetize rule to sort fields `selection set`

.changeset/twenty-tables-help.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@graphql-eslint/eslint-plugin': major
3+
---
4+
5+
Enforce `require-selections` on `FragmentSpread`s within `GraphQLUnionType`s

packages/plugin/src/rules/require-selections/index.test.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,19 @@ const TEST_SCHEMA = /* GraphQL */ `
77
noId: NoId!
88
vehicles: [Vehicle!]!
99
flying: [Flying!]!
10+
noIdOrNoId2: NoIdOrNoId2!
1011
}
1112
1213
type NoId {
1314
name: String!
1415
}
1516
17+
type NoId2 {
18+
name: String!
19+
}
20+
21+
union NoIdOrNoId2 = NoId | NoId2
22+
1623
interface Vehicle {
1724
id: ID!
1825
}
@@ -310,6 +317,22 @@ ruleTester.run<RuleOptions, true>('require-selections', rule, {
310317
...WITH_SCHEMA,
311318
code: '{ hasId { id: name } }',
312319
},
320+
{
321+
name: 'should work when union has no `id` field to select',
322+
...WITH_SCHEMA,
323+
code: /* GraphQL */ `
324+
{
325+
noIdOrNoId2 {
326+
... on NoId {
327+
name
328+
}
329+
... on NoId2 {
330+
name
331+
}
332+
}
333+
}
334+
`,
335+
},
313336
],
314337
invalid: [
315338
{
@@ -429,5 +452,53 @@ ruleTester.run<RuleOptions, true>('require-selections', rule, {
429452
},
430453
},
431454
},
455+
{
456+
name: 'should report an error with union and non-inline fragment',
457+
errors: [MESSAGE_ID],
458+
code: /* GraphQL */ `
459+
{
460+
userOrPost {
461+
...UnionFragment
462+
}
463+
}
464+
`,
465+
parserOptions: {
466+
graphQLConfig: {
467+
schema: USER_POST_SCHEMA,
468+
documents: /* GraphQL */ `
469+
fragment UnionFragment on UserOrPost {
470+
... on User {
471+
name
472+
}
473+
}
474+
`,
475+
},
476+
},
477+
},
478+
{
479+
name: 'should report an error with union and non-inline fragment and nested fragment',
480+
errors: [MESSAGE_ID],
481+
code: /* GraphQL */ `
482+
{
483+
userOrPost {
484+
...UnionFragment
485+
}
486+
}
487+
`,
488+
parserOptions: {
489+
graphQLConfig: {
490+
schema: USER_POST_SCHEMA,
491+
documents: /* GraphQL */ `
492+
fragment UnionFragment on UserOrPost {
493+
...UserFields
494+
}
495+
496+
fragment UserFields on User {
497+
name
498+
}
499+
`,
500+
},
501+
},
502+
},
432503
],
433504
});

packages/plugin/src/rules/require-selections/index.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,12 +171,25 @@ export const rule: GraphQLESLintRule<RuleOptions, true> = {
171171
checkFields(rawType);
172172
} else if (rawType instanceof GraphQLUnionType) {
173173
for (const selection of node.selections) {
174+
const types = rawType.getTypes();
174175
if (selection.kind === Kind.INLINE_FRAGMENT) {
175-
const types = rawType.getTypes();
176176
const t = types.find(t => t.name === selection.typeCondition!.name.value);
177177
if (t) {
178178
checkFields(t);
179179
}
180+
} else if (selection.kind === Kind.FRAGMENT_SPREAD) {
181+
const [foundSpread] = siblings.getFragment(selection.name.value);
182+
if (!foundSpread) return;
183+
const fragmentSpread = foundSpread.document;
184+
185+
// the fragment is either for the union type itself or one of the types in the union
186+
const t =
187+
fragmentSpread.typeCondition.name.value === rawType.name
188+
? rawType
189+
: types.find(t => t.name === fragmentSpread.typeCondition.name.value)!;
190+
checkedFragmentSpreads.add(fragmentSpread.name.value);
191+
192+
checkSelections(fragmentSpread.selectionSet, t, loc, parent, checkedFragmentSpreads);
180193
}
181194
}
182195
}

packages/plugin/src/rules/require-selections/snapshot.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,42 @@ exports[`require-selections > invalid > should report an error with union and fr
170170
7 | }
171171
`;
172172

173+
exports[`require-selections > invalid > should report an error with union and non-inline fragment 1`] = `
174+
#### ⌨️ Code
175+
176+
1 | {
177+
2 | userOrPost {
178+
3 | ...UnionFragment
179+
4 | }
180+
5 | }
181+
182+
#### ❌ Error
183+
184+
1 | {
185+
> 2 | userOrPost {
186+
| ^ Field \`userOrPost.id\` must be selected when it's available on a type.
187+
Include it in your selection set or add to used fragment \`UnionFragment\`.
188+
3 | ...UnionFragment
189+
`;
190+
191+
exports[`require-selections > invalid > should report an error with union and non-inline fragment and nested fragment 1`] = `
192+
#### ⌨️ Code
193+
194+
1 | {
195+
2 | userOrPost {
196+
3 | ...UnionFragment
197+
4 | }
198+
5 | }
199+
200+
#### ❌ Error
201+
202+
1 | {
203+
> 2 | userOrPost {
204+
| ^ Field \`userOrPost.id\` must be selected when it's available on a type.
205+
Include it in your selection set or add to used fragments \`UnionFragment\` or \`UserFields\`.
206+
3 | ...UnionFragment
207+
`;
208+
173209
exports[`require-selections > invalid > support multiple id field names 1`] = `
174210
#### ⌨️ Code
175211

0 commit comments

Comments
 (0)