1
1
import { getLocation , requireGraphQLSchemaFromContext , requireSiblingsOperations } from '../utils' ;
2
2
import { GraphQLESLintRule } from '../types' ;
3
- import { GraphQLInterfaceType , GraphQLObjectType , Kind , SelectionNode } from 'graphql' ;
3
+ import { GraphQLInterfaceType , GraphQLObjectType , Kind , SelectionNode , SelectionSetNode } from 'graphql' ;
4
+ import { asArray } from '@graphql-tools/utils' ;
4
5
import { getBaseType , GraphQLESTreeNode } from '../estree-parser' ;
5
6
6
- const REQUIRE_ID_WHEN_AVAILABLE = 'REQUIRE_ID_WHEN_AVAILABLE' ;
7
- const DEFAULT_ID_FIELD_NAME = 'id' ;
7
+ export type RequireIdWhenAvailableRuleConfig = { fieldName : string | string [ ] } ;
8
8
9
- type RequireIdWhenAvailableRuleConfig = { fieldName : string } ;
9
+ const RULE_ID = 'require-id-when-available' ;
10
+ const MESSAGE_ID = 'REQUIRE_ID_WHEN_AVAILABLE' ;
11
+ const DEFAULT_ID_FIELD_NAME = 'id' ;
10
12
11
13
const rule : GraphQLESLintRule < [ RequireIdWhenAvailableRuleConfig ] , true > = {
12
14
meta : {
13
15
type : 'suggestion' ,
14
16
docs : {
15
17
category : 'Operations' ,
16
18
description : 'Enforce selecting specific fields when they are available on the GraphQL type.' ,
17
- url : ' https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/require-id-when-available .md' ,
19
+ url : ` https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${ RULE_ID } .md` ,
18
20
requiresSchema : true ,
19
21
requiresSiblings : true ,
20
22
examples : [
@@ -28,7 +30,7 @@ const rule: GraphQLESLintRule<[RequireIdWhenAvailableRuleConfig], true> = {
28
30
}
29
31
30
32
# Query
31
- query user {
33
+ query {
32
34
user {
33
35
name
34
36
}
@@ -45,7 +47,7 @@ const rule: GraphQLESLintRule<[RequireIdWhenAvailableRuleConfig], true> = {
45
47
}
46
48
47
49
# Query
48
- query user {
50
+ query {
49
51
user {
50
52
id
51
53
name
@@ -57,7 +59,7 @@ const rule: GraphQLESLintRule<[RequireIdWhenAvailableRuleConfig], true> = {
57
59
recommended : true ,
58
60
} ,
59
61
messages : {
60
- [ REQUIRE_ID_WHEN_AVAILABLE ] : [
62
+ [ MESSAGE_ID ] : [
61
63
`Field {{ fieldName }} must be selected when it's available on a type. Please make sure to include it in your selection set!` ,
62
64
`If you are using fragments, make sure that all used fragments {{ checkedFragments }}specifies the field {{ fieldName }}.` ,
63
65
] . join ( '\n' ) ,
@@ -88,21 +90,23 @@ const rule: GraphQLESLintRule<[RequireIdWhenAvailableRuleConfig], true> = {
88
90
} ,
89
91
} ,
90
92
create ( context ) {
91
- requireGraphQLSchemaFromContext ( 'require-id-when-available' , context ) ;
92
- const siblings = requireSiblingsOperations ( 'require-id-when-available' , context ) ;
93
+ requireGraphQLSchemaFromContext ( RULE_ID , context ) ;
94
+ const siblings = requireSiblingsOperations ( RULE_ID , context ) ;
93
95
const { fieldName = DEFAULT_ID_FIELD_NAME } = context . options [ 0 ] || { } ;
94
- const idNames = Array . isArray ( fieldName ) ? fieldName : [ fieldName ] ;
96
+ const idNames = asArray ( fieldName ) ;
95
97
96
98
const isFound = ( s : GraphQLESTreeNode < SelectionNode > | SelectionNode ) =>
97
99
s . kind === Kind . FIELD && idNames . includes ( s . name . value ) ;
98
100
101
+ // Skip check selections in FragmentDefinition
102
+ const selector = 'OperationDefinition SelectionSet[parent.kind!=OperationDefinition]' ;
103
+
99
104
return {
100
- SelectionSet ( node ) {
105
+ [ selector ] ( node : GraphQLESTreeNode < SelectionSetNode , true > ) {
101
106
const typeInfo = node . typeInfo ( ) ;
102
107
if ( ! typeInfo . gqlType ) {
103
108
return ;
104
109
}
105
-
106
110
const rawType = getBaseType ( typeInfo . gqlType ) ;
107
111
const isObjectType = rawType instanceof GraphQLObjectType ;
108
112
const isInterfaceType = rawType instanceof GraphQLInterfaceType ;
@@ -115,47 +119,43 @@ const rule: GraphQLESLintRule<[RequireIdWhenAvailableRuleConfig], true> = {
115
119
if ( ! hasIdFieldInType ) {
116
120
return ;
117
121
}
118
-
119
122
const checkedFragmentSpreads = new Set < string > ( ) ;
120
- let found = false ;
121
123
122
124
for ( const selection of node . selections ) {
123
125
if ( isFound ( selection ) ) {
124
- found = true ;
125
- } else if ( selection . kind === Kind . INLINE_FRAGMENT ) {
126
- found = selection . selectionSet ?. selections . some ( s => isFound ( s ) ) ;
127
- } else if ( selection . kind === Kind . FRAGMENT_SPREAD ) {
126
+ return ;
127
+ }
128
+ if ( selection . kind === Kind . INLINE_FRAGMENT && selection . selectionSet . selections . some ( isFound ) ) {
129
+ return ;
130
+ }
131
+ if ( selection . kind === Kind . FRAGMENT_SPREAD ) {
128
132
const [ foundSpread ] = siblings . getFragment ( selection . name . value ) ;
129
-
130
133
if ( foundSpread ) {
131
134
checkedFragmentSpreads . add ( foundSpread . document . name . value ) ;
132
- found = foundSpread . document . selectionSet ?. selections . some ( s => isFound ( s ) ) ;
135
+ if ( foundSpread . document . selectionSet . selections . some ( isFound ) ) {
136
+ return ;
137
+ }
133
138
}
134
139
}
135
-
136
- if ( found ) {
137
- break ;
138
- }
139
140
}
140
141
141
142
const { parent } = node as any ;
142
143
const hasIdFieldInInterfaceSelectionSet =
143
- parent &&
144
- parent . kind === Kind . INLINE_FRAGMENT &&
145
- parent . parent &&
146
- parent . parent . kind === Kind . SELECTION_SET &&
147
- parent . parent . selections . some ( s => isFound ( s ) ) ;
148
-
149
- if ( ! found && ! hasIdFieldInInterfaceSelectionSet ) {
150
- context . report ( {
151
- loc : getLocation ( node . loc ) ,
152
- messageId : REQUIRE_ID_WHEN_AVAILABLE ,
153
- data : {
154
- checkedFragments : checkedFragmentSpreads . size === 0 ? '' : `(${ [ ...checkedFragmentSpreads ] . join ( ', ' ) } ) ` ,
155
- fieldName : idNames . map ( name => `"${ name } "` ) . join ( ' or ' ) ,
156
- } ,
157
- } ) ;
144
+ parent ?. kind === Kind . INLINE_FRAGMENT &&
145
+ parent . parent ?. kind === Kind . SELECTION_SET &&
146
+ parent . parent . selections . some ( isFound ) ;
147
+ if ( hasIdFieldInInterfaceSelectionSet ) {
148
+ return ;
158
149
}
150
+
151
+ context . report ( {
152
+ loc : getLocation ( node . loc ) ,
153
+ messageId : MESSAGE_ID ,
154
+ data : {
155
+ checkedFragments : checkedFragmentSpreads . size === 0 ? '' : `(${ [ ...checkedFragmentSpreads ] . join ( ', ' ) } ) ` ,
156
+ fieldName : idNames . map ( name => `"${ name } "` ) . join ( ' or ' ) ,
157
+ } ,
158
+ } ) ;
159
159
} ,
160
160
} ;
161
161
} ,
0 commit comments