@@ -5,6 +5,7 @@ import { isSameSet } from '../jsutils/isSameSet.js';
55import type { ObjMap } from '../jsutils/ObjMap.js' ;
66
77import type {
8+ DirectiveNode ,
89 FieldNode ,
910 FragmentDefinitionNode ,
1011 FragmentSpreadNode ,
@@ -26,7 +27,7 @@ import type { GraphQLSchema } from '../type/schema.js';
2627
2728import { typeFromAST } from '../utilities/typeFromAST.js' ;
2829
29- import { getDirectiveValues } from './values.js' ;
30+ import { getArgumentValues , getDirectiveValues } from './values.js' ;
3031
3132export interface DeferUsage {
3233 label : string | undefined ;
@@ -60,6 +61,7 @@ export interface CollectFieldsResult {
6061 groupedFieldSet : GroupedFieldSet ;
6162 newGroupedFieldSetDetails : Map < DeferUsageSet , GroupedFieldSetDetails > ;
6263 newDeferUsages : ReadonlyArray < DeferUsage > ;
64+ forbiddenDirectiveInstances : ReadonlyArray < DirectiveNode > ;
6365}
6466
6567interface CollectFieldsContext {
@@ -72,6 +74,7 @@ interface CollectFieldsContext {
7274 fieldsByTarget : Map < Target , AccumulatorMap < string , FieldNode > > ;
7375 newDeferUsages : Array < DeferUsage > ;
7476 visitedFragmentNames : Set < string > ;
77+ forbiddenDirectiveInstances : Array < DirectiveNode > ;
7578}
7679
7780/**
@@ -100,16 +103,28 @@ export function collectFields(
100103 targetsByKey : new Map ( ) ,
101104 newDeferUsages : [ ] ,
102105 visitedFragmentNames : new Set ( ) ,
106+ forbiddenDirectiveInstances : [ ] ,
103107 } ;
104108
105109 collectFieldsImpl ( context , operation . selectionSet ) ;
106110
107111 return {
108112 ...buildGroupedFieldSets ( context . targetsByKey , context . fieldsByTarget ) ,
109113 newDeferUsages : context . newDeferUsages ,
114+ forbiddenDirectiveInstances : context . forbiddenDirectiveInstances ,
110115 } ;
111116}
112117
118+ /**
119+ * This variable is the empty variables used during the validation phase (where
120+ * no variables exist) for field collection; if a `@skip` or `@include`
121+ * directive is ever seen when `variableValues` is set to this, it should
122+ * throw.
123+ */
124+ export const VALIDATION_PHASE_EMPTY_VARIABLES : {
125+ [ variable : string ] : any ;
126+ } = Object . freeze ( Object . create ( null ) ) ;
127+
113128/**
114129 * Given an array of field nodes, collects all of the subfields of the passed
115130 * in fields, and returns them at the end.
@@ -139,6 +154,7 @@ export function collectSubfields(
139154 targetsByKey : new Map ( ) ,
140155 newDeferUsages : [ ] ,
141156 visitedFragmentNames : new Set ( ) ,
157+ forbiddenDirectiveInstances : [ ] ,
142158 } ;
143159
144160 for ( const fieldDetails of fieldGroup . fields ) {
@@ -155,6 +171,7 @@ export function collectSubfields(
155171 fieldGroup . targets ,
156172 ) ,
157173 newDeferUsages : context . newDeferUsages ,
174+ forbiddenDirectiveInstances : context . forbiddenDirectiveInstances ,
158175 } ;
159176}
160177
@@ -179,7 +196,7 @@ function collectFieldsImpl(
179196 for ( const selection of selectionSet . selections ) {
180197 switch ( selection . kind ) {
181198 case Kind . FIELD : {
182- if ( ! shouldIncludeNode ( variableValues , selection ) ) {
199+ if ( ! shouldIncludeNode ( context , variableValues , selection ) ) {
183200 continue ;
184201 }
185202 const key = getFieldEntryKey ( selection ) ;
@@ -200,7 +217,7 @@ function collectFieldsImpl(
200217 }
201218 case Kind . INLINE_FRAGMENT : {
202219 if (
203- ! shouldIncludeNode ( variableValues , selection ) ||
220+ ! shouldIncludeNode ( context , variableValues , selection ) ||
204221 ! doesFragmentConditionMatch ( schema , selection , runtimeType )
205222 ) {
206223 continue ;
@@ -232,7 +249,7 @@ function collectFieldsImpl(
232249 case Kind . FRAGMENT_SPREAD : {
233250 const fragName = selection . name . value ;
234251
235- if ( ! shouldIncludeNode ( variableValues , selection ) ) {
252+ if ( ! shouldIncludeNode ( context , variableValues , selection ) ) {
236253 continue ;
237254 }
238255
@@ -304,19 +321,44 @@ function getDeferValues(
304321 * directives, where `@skip` has higher precedence than `@include`.
305322 */
306323function shouldIncludeNode (
324+ context : CollectFieldsContext ,
307325 variableValues : { [ variable : string ] : unknown } ,
308326 node : FragmentSpreadNode | FieldNode | InlineFragmentNode ,
309327) : boolean {
310- const skip = getDirectiveValues ( GraphQLSkipDirective , node , variableValues ) ;
328+ const skipDirectiveNode = node . directives ?. find (
329+ ( directive ) => directive . name . value === GraphQLSkipDirective . name ,
330+ ) ;
331+ if (
332+ skipDirectiveNode &&
333+ variableValues === VALIDATION_PHASE_EMPTY_VARIABLES
334+ ) {
335+ context . forbiddenDirectiveInstances . push ( skipDirectiveNode ) ;
336+ return false ;
337+ }
338+ const skip = skipDirectiveNode
339+ ? getArgumentValues ( GraphQLSkipDirective , skipDirectiveNode , variableValues )
340+ : undefined ;
311341 if ( skip ?. if === true ) {
312342 return false ;
313343 }
314344
315- const include = getDirectiveValues (
316- GraphQLIncludeDirective ,
317- node ,
318- variableValues ,
345+ const includeDirectiveNode = node . directives ?. find (
346+ ( directive ) => directive . name . value === GraphQLIncludeDirective . name ,
319347 ) ;
348+ if (
349+ includeDirectiveNode &&
350+ variableValues === VALIDATION_PHASE_EMPTY_VARIABLES
351+ ) {
352+ context . forbiddenDirectiveInstances . push ( includeDirectiveNode ) ;
353+ return false ;
354+ }
355+ const include = includeDirectiveNode
356+ ? getArgumentValues (
357+ GraphQLIncludeDirective ,
358+ includeDirectiveNode ,
359+ variableValues ,
360+ )
361+ : undefined ;
320362 if ( include ?. if === false ) {
321363 return false ;
322364 }
0 commit comments