import { QueryPlanInlineFragmentNode , QueryPlanSelectionNode } from '../' ; import { ASTNode , Kind , print , SelectionNode as GraphQLJSSelectionNode , visit } from 'graphql' ; import { Config , NewPlugin , Refs } from 'pretty-format' ; export default { test ( value ) { return value && typeof value . kind === 'string' ; } , serialize ( value , config , indentation , _depth , _refs , _printer ) { const lines = print ( remapInlineFragmentNodes ( value ) ) . trim ( ) . split ( '\n' ) ; if ( lines . length === 0 ) { return '' ; } else if ( lines . length === 1 ) { return lines [ 0 ] ; } return lines . map ( line => { const indentationLength = getIndentationLength ( line ) ; const dedentedLine = line . slice ( indentationLength ) ; const indentationDepth = indentationLength / 2 ; return indentation + config . indent . repeat ( indentationDepth ) + dedentedLine ; } ) . join ( config . spacingOuter ) ; } , } as NewPlugin ; function getIndentationLength ( line ) { const result = /^( {2})+/ . exec ( line ) ; return result === null ? 0 : result [ 0 ] . length ; } /**
 * This function converts potential InlineFragmentNodes that WE created
 * (defined in ../QueryPlan, not graphql-js) to GraphQL-js compliant AST nodes
 * for the graphql-js printer to work with
 *
 * The arg type here SHOULD be (node: AstNode | SelectionNode (from ../QueryPlan)),
 * but that breaks the graphql-js visitor, as it won't allow our redefined
 * SelectionNode to be passed in.
 *
 * Since our SelectionNode still has a `kind`, this will still functionally work
 * at runtime to call the InlineFragment visitor defined below
 *
 * We have to cast the `fragmentNode as unknown` and then to an InlineFragmentNode
 * at the bottom though, since there's no way to cast it appropriately to an
 * `InlineFragmentNode` as defined in ../QueryPlan.ts. TypeScript will complain
 * about there not being overlapping fields
 */ export function remapInlineFragmentNodes ( node ) { return visit ( node , { InlineFragment : ( fragmentNode ) => { if ( fragmentNode . selectionSet ) return fragmentNode ; /**
             * Since the above check wasn't hit, we _know_ that fragmentNode is an
             * InlineFragmentNode from ../QueryPlan, but we can't actually type that
             * without causing ourselves a lot of headache, so we cast to unknown and
             * then to InlineFragmentNode (from ../QueryPlan) below
             */ return { kind : Kind . INLINE_FRAGMENT , typeCondition : fragmentNode . typeCondition ? { kind : Kind . NAMED_TYPE , name : { kind : Kind . NAME , value : fragmentNode . typeCondition , } , } : undefined , selectionSet : { kind : Kind . SELECTION_SET , selections : remapSelections ( ( ( fragmentNode as unknown ) as QueryPlanInlineFragmentNode ) . selections ) , } , } ; } , } ) ; } function remapSelections ( selections ) { return selections . map ( ( selection ) => { switch ( selection . kind ) { case 'Field' : return { kind : Kind . FIELD , name : { kind : Kind . NAME , value : selection . name , } , selectionSet : { kind : Kind . SELECTION_SET , selections : remapSelections ( selection . selections || [ ] ) , } , } ; case 'InlineFragment' : return { kind : Kind . INLINE_FRAGMENT , selectionSet : { kind : Kind . SELECTION_SET , selections : remapSelections ( selection . selections || [ ] ) , } , typeCondition : selection . typeCondition ? { kind : Kind . NAMED_TYPE , name : { kind : Kind . NAME , value : selection . typeCondition , } , } : undefined , } ; } } ) ; }