import type { ObjMap } from '../jsutils/ObjMap' ; import type { FieldNode , FragmentDefinitionNode , FragmentSpreadNode , InlineFragmentNode , SelectionSetNode , } from '../language/ast' ; import { Kind } from '../language/kinds' ; import type { GraphQLObjectType } from '../type/definition' ; import { isAbstractType } from '../type/definition' ; import { GraphQLIncludeDirective , GraphQLSkipDirective , } from '../type/directives' ; import type { GraphQLSchema } from '../type/schema' ; import { typeFromAST } from '../utilities/typeFromAST' ; import { getDirectiveValues } from './values' ; internal export function collectFields ( schema , fragments , variableValues , runtimeType , selectionSet ) { const fields = new Map ( ) ; collectFieldsImpl ( schema , fragments , variableValues , runtimeType , selectionSet , fields , new Set ( ) ) ; return fields ; } internal export function collectSubfields ( schema , fragments , variableValues , returnType , fieldNodes ) { const subFieldNodes = new Map ( ) ; const visitedFragmentNames = new Set < string > ( ) ; for ( const node of fieldNodes ) { if ( node . selectionSet ) { collectFieldsImpl ( schema , fragments , variableValues , returnType , node . selectionSet , subFieldNodes , visitedFragmentNames ) ; } } return subFieldNodes ; } function collectFieldsImpl ( schema , fragments , variableValues , runtimeType , selectionSet , fields , visitedFragmentNames ) { for ( const selection of selectionSet . selections ) { switch ( selection . kind ) { case Kind . FIELD : { if ( ! shouldIncludeNode ( variableValues , selection ) ) { continue ; } const name = getFieldEntryKey ( selection ) ; const fieldList = fields . get ( name ) ; if ( fieldList !== undefined ) { fieldList . push ( selection ) ; } else { fields . set ( name , [ selection ] ) ; } break ; } case Kind . INLINE_FRAGMENT : { if ( ! shouldIncludeNode ( variableValues , selection ) || ! doesFragmentConditionMatch ( schema , selection , runtimeType ) ) { continue ; } collectFieldsImpl ( schema , fragments , variableValues , runtimeType , selection . selectionSet , fields , visitedFragmentNames ) ; break ; } case Kind . FRAGMENT_SPREAD : { const fragName = selection . name . value ; if ( visitedFragmentNames . has ( fragName ) || ! shouldIncludeNode ( variableValues , selection ) ) { continue ; } visitedFragmentNames . add ( fragName ) ; const fragment = fragments [ fragName ] ; if ( ! fragment || ! doesFragmentConditionMatch ( schema , fragment , runtimeType ) ) { continue ; } collectFieldsImpl ( schema , fragments , variableValues , runtimeType , fragment . selectionSet , fields , visitedFragmentNames ) ; break ; } } } } /**
 * Determines if a field should be included based on the `@include` and `@skip`
 * directives, where `@skip` has higher precedence than `@include`.
 */ function shouldIncludeNode ( variableValues , node ) { const skip = getDirectiveValues ( GraphQLSkipDirective , node , variableValues ) ; if ( skip ?. if === true ) { return false ; } const include = getDirectiveValues ( GraphQLIncludeDirective , node , variableValues ) ; if ( include ?. if === false ) { return false ; } return true ; } /**
 * Determines if a fragment is applicable to the given type.
 */ function doesFragmentConditionMatch ( schema , fragment , type ) { const typeConditionNode = fragment . typeCondition ; if ( ! typeConditionNode ) { return true ; } const conditionalType = typeFromAST ( schema , typeConditionNode ) ; if ( conditionalType === type ) { return true ; } if ( isAbstractType ( conditionalType ) ) { return schema . isSubType ( conditionalType , type ) ; } return false ; } /**
 * Implements the logic to compute the key of a given field's entry
 */ function getFieldEntryKey ( node ) { return node . alias ? node . alias . value : node . name . value ; }