11import { getLocation , requireGraphQLSchemaFromContext , requireSiblingsOperations } from '../utils' ;
22import { 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' ;
45import { getBaseType , GraphQLESTreeNode } from '../estree-parser' ;
56
6- const REQUIRE_ID_WHEN_AVAILABLE = 'REQUIRE_ID_WHEN_AVAILABLE' ;
7- const DEFAULT_ID_FIELD_NAME = 'id' ;
7+ export type RequireIdWhenAvailableRuleConfig = { fieldName : string | string [ ] } ;
88
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' ;
1012
1113const rule : GraphQLESLintRule < [ RequireIdWhenAvailableRuleConfig ] , true > = {
1214 meta : {
1315 type : 'suggestion' ,
1416 docs : {
1517 category : 'Operations' ,
1618 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` ,
1820 requiresSchema : true ,
1921 requiresSiblings : true ,
2022 examples : [
@@ -28,7 +30,7 @@ const rule: GraphQLESLintRule<[RequireIdWhenAvailableRuleConfig], true> = {
2830 }
2931
3032 # Query
31- query user {
33+ query {
3234 user {
3335 name
3436 }
@@ -45,7 +47,7 @@ const rule: GraphQLESLintRule<[RequireIdWhenAvailableRuleConfig], true> = {
4547 }
4648
4749 # Query
48- query user {
50+ query {
4951 user {
5052 id
5153 name
@@ -57,7 +59,7 @@ const rule: GraphQLESLintRule<[RequireIdWhenAvailableRuleConfig], true> = {
5759 recommended : true ,
5860 } ,
5961 messages : {
60- [ REQUIRE_ID_WHEN_AVAILABLE ] : [
62+ [ MESSAGE_ID ] : [
6163 `Field {{ fieldName }} must be selected when it's available on a type. Please make sure to include it in your selection set!` ,
6264 `If you are using fragments, make sure that all used fragments {{ checkedFragments }}specifies the field {{ fieldName }}.` ,
6365 ] . join ( '\n' ) ,
@@ -88,21 +90,23 @@ const rule: GraphQLESLintRule<[RequireIdWhenAvailableRuleConfig], true> = {
8890 } ,
8991 } ,
9092 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 ) ;
9395 const { fieldName = DEFAULT_ID_FIELD_NAME } = context . options [ 0 ] || { } ;
94- const idNames = Array . isArray ( fieldName ) ? fieldName : [ fieldName ] ;
96+ const idNames = asArray ( fieldName ) ;
9597
9698 const isFound = ( s : GraphQLESTreeNode < SelectionNode > | SelectionNode ) =>
9799 s . kind === Kind . FIELD && idNames . includes ( s . name . value ) ;
98100
101+ // Skip check selections in FragmentDefinition
102+ const selector = 'OperationDefinition SelectionSet[parent.kind!=OperationDefinition]' ;
103+
99104 return {
100- SelectionSet ( node ) {
105+ [ selector ] ( node : GraphQLESTreeNode < SelectionSetNode , true > ) {
101106 const typeInfo = node . typeInfo ( ) ;
102107 if ( ! typeInfo . gqlType ) {
103108 return ;
104109 }
105-
106110 const rawType = getBaseType ( typeInfo . gqlType ) ;
107111 const isObjectType = rawType instanceof GraphQLObjectType ;
108112 const isInterfaceType = rawType instanceof GraphQLInterfaceType ;
@@ -115,47 +119,43 @@ const rule: GraphQLESLintRule<[RequireIdWhenAvailableRuleConfig], true> = {
115119 if ( ! hasIdFieldInType ) {
116120 return ;
117121 }
118-
119122 const checkedFragmentSpreads = new Set < string > ( ) ;
120- let found = false ;
121123
122124 for ( const selection of node . selections ) {
123125 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 ) {
128132 const [ foundSpread ] = siblings . getFragment ( selection . name . value ) ;
129-
130133 if ( foundSpread ) {
131134 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+ }
133138 }
134139 }
135-
136- if ( found ) {
137- break ;
138- }
139140 }
140141
141142 const { parent } = node as any ;
142143 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 ;
158149 }
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+ } ) ;
159159 } ,
160160 } ;
161161 } ,
0 commit comments