import { didYouMean } from '../../jsutils/didYouMean' ; import { naturalCompare } from '../../jsutils/naturalCompare' ; import { suggestionList } from '../../jsutils/suggestionList' ; import { GraphQLError } from '../../error/GraphQLError' ; import type { FieldNode } from '../../language/ast' ; import type { ASTVisitor } from '../../language/visitor' ; import type { GraphQLInterfaceType , GraphQLObjectType , GraphQLOutputType , } from '../../type/definition' ; import { isAbstractType , isInterfaceType , isObjectType , } from '../../type/definition' ; import type { GraphQLSchema } from '../../type/schema' ; import type { ValidationContext } from '../ValidationContext' ; /**
 * Fields on correct type
 *
 * A GraphQL document is only valid if all fields selected are defined by the
 * parent type, or are an allowed meta field such as __typename.
 *
 * See https://spec.graphql.org/draft/#sec-Field-Selections
 */ export function FieldsOnCorrectTypeRule ( context ) { return { Field ( node ) { const type = context . getParentType ( ) ; if ( type ) { const fieldDef = context . getFieldDef ( ) ; if ( ! fieldDef ) { const schema = context . getSchema ( ) ; const fieldName = node . name . value ; let suggestion = didYouMean ( 'to use an inline fragment on' , getSuggestedTypeNames ( schema , type , fieldName ) ) ; if ( suggestion === '' ) { suggestion = didYouMean ( getSuggestedFieldNames ( type , fieldName ) ) ; } context . reportError ( new GraphQLError ( `Cannot query field "${ fieldName }" on type "${ type . name }".` + suggestion , node ) ) ; } } } , } ; } /**
 * Go through all of the implementations of type, as well as the interfaces that
 * they implement. If any of those types include the provided field, suggest them,
 * sorted by how often the type is referenced.
 */ function getSuggestedTypeNames ( schema , type , fieldName ) { if ( ! isAbstractType ( type ) ) { return [ ] ; } const suggestedTypes = new Set ( ) ; const usageCount = Object . create ( null ) ; for ( const possibleType of schema . getPossibleTypes ( type ) ) { if ( ! possibleType . getFields ( ) [ fieldName ] ) { continue ; } suggestedTypes . add ( possibleType ) ; usageCount [ possibleType . name ] = 1 ; for ( const possibleInterface of possibleType . getInterfaces ( ) ) { if ( ! possibleInterface . getFields ( ) [ fieldName ] ) { continue ; } suggestedTypes . add ( possibleInterface ) ; usageCount [ possibleInterface . name ] = ( usageCount [ possibleInterface . name ] ?? 0 ) + 1 ; } } return [ ... suggestedTypes ] . sort ( ( typeA , typeB ) => { const usageCountDiff = usageCount [ typeB . name ] - usageCount [ typeA . name ] ; if ( usageCountDiff !== 0 ) { return usageCountDiff ; } if ( isInterfaceType ( typeA ) && schema . isSubType ( typeA , typeB ) ) { return - 1 ; } if ( isInterfaceType ( typeB ) && schema . isSubType ( typeB , typeA ) ) { return 1 ; } return naturalCompare ( typeA . name , typeB . name ) ; } ) . map ( ( x ) => x . name ) ; } /**
 * For the field name provided, determine if there are any similar field names
 * that may be the result of a typo.
 */ function getSuggestedFieldNames ( type , fieldName ) { if ( isObjectType ( type ) || isInterfaceType ( type ) ) { const possibleFieldNames = Object . keys ( type . getFields ( ) ) ; return suggestionList ( fieldName , possibleFieldNames ) ; } return [ ] ; }