import type { Maybe } from '../jsutils/Maybe' ; import type { ASTNode , FieldNode } from '../language/ast' ; import { isNode } from '../language/ast' ; import { Kind } from '../language/kinds' ; import type { ASTVisitor } from '../language/visitor' ; import { getEnterLeaveForKind } from '../language/visitor' ; import type { GraphQLArgument , GraphQLCompositeType , GraphQLEnumValue , GraphQLField , GraphQLInputField , GraphQLInputType , GraphQLOutputType , GraphQLType , } from '../type/definition' ; import { getNamedType , getNullableType , isCompositeType , isEnumType , isInputObjectType , isInputType , isInterfaceType , isListType , isObjectType , isOutputType , } from '../type/definition' ; import type { GraphQLDirective } from '../type/directives' ; import { SchemaMetaFieldDef , TypeMetaFieldDef , TypeNameMetaFieldDef , } from '../type/introspection' ; import type { GraphQLSchema } from '../type/schema' ; import { typeFromAST } from './typeFromAST' ; /**
 * TypeInfo is a utility class which, given a GraphQL schema, can keep track
 * of the current field and type definitions at any point in a GraphQL document
 * AST during a recursive descent by calling `enter(node)` and `leave(node)`.
 */ export class TypeInfo { private _schema : GraphQLSchema ; private _typeStack : Array < Maybe < GraphQLOutputType > > ; private _parentTypeStack : Array < Maybe < GraphQLCompositeType > > ; private _inputTypeStack : Array < Maybe < GraphQLInputType > > ; private _fieldDefStack : Array < Maybe < GraphQLField < unknown , unknown > > > ; private _defaultValueStack : Array < Maybe < unknown > > ; private _directive : Maybe < GraphQLDirective > ; private _argument : Maybe < GraphQLArgument > ; private _enumValue : Maybe < GraphQLEnumValue > ; private _getFieldDef : GetFieldDefFn ; constructor ( schema , /**
     * Initial type may be provided in rare cases to facilitate traversals
     *  beginning somewhere other than documents.
     */ initialType ? , deprecated getFieldDefFn ? ) { this . _schema = schema ; this . _typeStack = [ ] ; this . _parentTypeStack = [ ] ; this . _inputTypeStack = [ ] ; this . _fieldDefStack = [ ] ; this . _defaultValueStack = [ ] ; this . _directive = null ; this . _argument = null ; this . _enumValue = null ; this . _getFieldDef = getFieldDefFn ?? getFieldDef ; if ( initialType ) { if ( isInputType ( initialType ) ) { this . _inputTypeStack . push ( initialType ) ; } if ( isCompositeType ( initialType ) ) { this . _parentTypeStack . push ( initialType ) ; } if ( isOutputType ( initialType ) ) { this . _typeStack . push ( initialType ) ; } } } get [ Symbol . toStringTag ] ( ) { return 'TypeInfo' ; } getType ( ) { if ( this . _typeStack . length > 0 ) { return this . _typeStack [ this . _typeStack . length - 1 ] ; } } getParentType ( ) { if ( this . _parentTypeStack . length > 0 ) { return this . _parentTypeStack [ this . _parentTypeStack . length - 1 ] ; } } getInputType ( ) { if ( this . _inputTypeStack . length > 0 ) { return this . _inputTypeStack [ this . _inputTypeStack . length - 1 ] ; } } getParentInputType ( ) { if ( this . _inputTypeStack . length > 1 ) { return this . _inputTypeStack [ this . _inputTypeStack . length - 2 ] ; } } getFieldDef ( ) { if ( this . _fieldDefStack . length > 0 ) { return this . _fieldDefStack [ this . _fieldDefStack . length - 1 ] ; } } getDefaultValue ( ) { if ( this . _defaultValueStack . length > 0 ) { return this . _defaultValueStack [ this . _defaultValueStack . length - 1 ] ; } } getDirective ( ) { return this . _directive ; } getArgument ( ) { return this . _argument ; } getEnumValue ( ) { return this . _enumValue ; } enter ( node ) { const schema = this . _schema ; switch ( node . kind ) { case Kind . SELECTION_SET : { const namedType = getNamedType ( this . getType ( ) ) ; this . _parentTypeStack . push ( isCompositeType ( namedType ) ? namedType : undefined ) ; break ; } case Kind . FIELD : { const parentType = this . getParentType ( ) ; let fieldDef ; let fieldType ; if ( parentType ) { fieldDef = this . _getFieldDef ( schema , parentType , node ) ; if ( fieldDef ) { fieldType = fieldDef . type ; } } this . _fieldDefStack . push ( fieldDef ) ; this . _typeStack . push ( isOutputType ( fieldType ) ? fieldType : undefined ) ; break ; } case Kind . DIRECTIVE : this . _directive = schema . getDirective ( node . name . value ) ; break ; case Kind . OPERATION_DEFINITION : { const rootType = schema . getRootType ( node . operation ) ; this . _typeStack . push ( isObjectType ( rootType ) ? rootType : undefined ) ; break ; } case Kind . INLINE_FRAGMENT : case Kind . FRAGMENT_DEFINITION : { const typeConditionAST = node . typeCondition ; const outputType = typeConditionAST ? typeFromAST ( schema , typeConditionAST ) : getNamedType ( this . getType ( ) ) ; this . _typeStack . push ( isOutputType ( outputType ) ? outputType : undefined ) ; break ; } case Kind . VARIABLE_DEFINITION : { const inputType = typeFromAST ( schema , node . type ) ; this . _inputTypeStack . push ( isInputType ( inputType ) ? inputType : undefined ) ; break ; } case Kind . ARGUMENT : { let argDef ; let argType ; const fieldOrDirective = this . getDirective ( ) ?? this . getFieldDef ( ) ; if ( fieldOrDirective ) { argDef = fieldOrDirective . args . find ( ( arg ) => arg . name === node . name . value ) ; if ( argDef ) { argType = argDef . type ; } } this . _argument = argDef ; this . _defaultValueStack . push ( argDef ? argDef . defaultValue : undefined ) ; this . _inputTypeStack . push ( isInputType ( argType ) ? argType : undefined ) ; break ; } case Kind . LIST : { const listType = getNullableType ( this . getInputType ( ) ) ; const itemType = isListType ( listType ) ? listType . ofType : listType ; this . _defaultValueStack . push ( undefined ) ; this . _inputTypeStack . push ( isInputType ( itemType ) ? itemType : undefined ) ; break ; } case Kind . OBJECT_FIELD : { const objectType = getNamedType ( this . getInputType ( ) ) ; let inputFieldType ; let inputField ; if ( isInputObjectType ( objectType ) ) { inputField = objectType . getFields ( ) [ node . name . value ] ; if ( inputField ) { inputFieldType = inputField . type ; } } this . _defaultValueStack . push ( inputField ? inputField . defaultValue : undefined ) ; this . _inputTypeStack . push ( isInputType ( inputFieldType ) ? inputFieldType : undefined ) ; break ; } case Kind . ENUM : { const enumType = getNamedType ( this . getInputType ( ) ) ; let enumValue ; if ( isEnumType ( enumType ) ) { enumValue = enumType . getValue ( node . value ) ; } this . _enumValue = enumValue ; break ; } default : } } leave ( node ) { switch ( node . kind ) { case Kind . SELECTION_SET : this . _parentTypeStack . pop ( ) ; break ; case Kind . FIELD : this . _fieldDefStack . pop ( ) ; this . _typeStack . pop ( ) ; break ; case Kind . DIRECTIVE : this . _directive = null ; break ; case Kind . OPERATION_DEFINITION : case Kind . INLINE_FRAGMENT : case Kind . FRAGMENT_DEFINITION : this . _typeStack . pop ( ) ; break ; case Kind . VARIABLE_DEFINITION : this . _inputTypeStack . pop ( ) ; break ; case Kind . ARGUMENT : this . _argument = null ; this . _defaultValueStack . pop ( ) ; this . _inputTypeStack . pop ( ) ; break ; case Kind . LIST : case Kind . OBJECT_FIELD : this . _defaultValueStack . pop ( ) ; this . _inputTypeStack . pop ( ) ; break ; case Kind . ENUM : this . _enumValue = null ; break ; default : } } } type GetFieldDefFn = ( schema : GraphQLSchema , parentType : GraphQLType , fieldNode : FieldNode ) => Maybe < GraphQLField < unknown , unknown > > ; /**
 * Not exactly the same as the executor's definition of getFieldDef, in this
 * statically evaluated environment we do not always have an Object type,
 * and need to handle Interface and Union types.
 */ function getFieldDef ( schema , parentType , fieldNode ) { const name = fieldNode . name . value ; if ( name === SchemaMetaFieldDef . name && schema . getQueryType ( ) === parentType ) { return SchemaMetaFieldDef ; } if ( name === TypeMetaFieldDef . name && schema . getQueryType ( ) === parentType ) { return TypeMetaFieldDef ; } if ( name === TypeNameMetaFieldDef . name && isCompositeType ( parentType ) ) { return TypeNameMetaFieldDef ; } if ( isObjectType ( parentType ) || isInterfaceType ( parentType ) ) { return parentType . getFields ( ) [ name ] ; } } /**
 * Creates a new visitor instance which maintains a provided TypeInfo instance
 * along with visiting visitor.
 */ export function visitWithTypeInfo ( typeInfo , visitor ) { return { enter ( ... args ) { const node = args [ 0 ] ; typeInfo . enter ( node ) ; const fn = getEnterLeaveForKind ( visitor , node . kind ) . enter ; if ( fn ) { const result = fn . apply ( visitor , args ) ; if ( result !== undefined ) { typeInfo . leave ( node ) ; if ( isNode ( result ) ) { typeInfo . enter ( result ) ; } } return result ; } } , leave ( ... args ) { const node = args [ 0 ] ; const fn = getEnterLeaveForKind ( visitor , node . kind ) . leave ; let result ; if ( fn ) { result = fn . apply ( visitor , args ) ; } typeInfo . leave ( node ) ; return result ; } , } ; }