@@ -15,6 +15,7 @@ import {
15
15
getNamedType ,
16
16
GraphQLField ,
17
17
GraphQLNamedType ,
18
+ GraphQLObjectType ,
18
19
GraphQLSchema ,
19
20
GraphQLType ,
20
21
isAbstractType ,
@@ -28,6 +29,7 @@ import {
28
29
OperationDefinitionNode ,
29
30
SelectionNode ,
30
31
SelectionSetNode ,
32
+ TypeInfo ,
31
33
VariableDefinitionNode ,
32
34
visit ,
33
35
visitWithTypeInfo ,
@@ -483,7 +485,48 @@ function finalizeSelectionSet(
483
485
const seenNonNullableMap = new WeakMap < readonly ASTNode [ ] , Set < string > > ( ) ;
484
486
const seenNullableMap = new WeakMap < readonly ASTNode [ ] , Set < string > > ( ) ;
485
487
486
- const filteredSelectionSet = visit (
488
+ const filteredSelectionSet = filterSelectionSet (
489
+ schema ,
490
+ typeInfo ,
491
+ validFragments ,
492
+ selectionSet ,
493
+ onOverlappingAliases ,
494
+ usedFragments ,
495
+ seenNonNullableMap ,
496
+ seenNullableMap ,
497
+ ) ;
498
+
499
+ visit (
500
+ filteredSelectionSet ,
501
+ {
502
+ [ Kind . VARIABLE ] : ( variableNode ) => {
503
+ usedVariables . push ( variableNode . name . value ) ;
504
+ } ,
505
+ } ,
506
+ // visitorKeys argument usage a la https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-graphql/src/batching/merge-queries.js
507
+ // empty keys cannot be removed only because of typescript errors
508
+ // will hopefully be fixed in future version of graphql-js to be optional
509
+ variablesVisitorKeys as any ,
510
+ ) ;
511
+
512
+ return {
513
+ selectionSet : filteredSelectionSet ,
514
+ usedFragments,
515
+ usedVariables,
516
+ } ;
517
+ }
518
+
519
+ function filterSelectionSet (
520
+ schema : GraphQLSchema ,
521
+ typeInfo : TypeInfo ,
522
+ validFragments : { [ name : string ] : GraphQLType } ,
523
+ selectionSet : SelectionSetNode ,
524
+ onOverlappingAliases : ( ) => void ,
525
+ usedFragments : Array < string > ,
526
+ seenNonNullableMap : WeakMap < readonly ASTNode [ ] , Set < string > > ,
527
+ seenNullableMap : WeakMap < readonly ASTNode [ ] , Set < string > > ,
528
+ ) {
529
+ return visit (
487
530
selectionSet ,
488
531
visitWithTypeInfo ( typeInfo , {
489
532
[ Kind . FIELD ] : {
@@ -541,20 +584,81 @@ function finalizeSelectionSet(
541
584
}
542
585
}
543
586
if ( possibleTypeNames . length > 0 ) {
544
- return possibleTypeNames . map ( ( possibleTypeName ) => ( {
545
- kind : Kind . INLINE_FRAGMENT ,
546
- typeCondition : {
547
- kind : Kind . NAMED_TYPE ,
548
- name : {
549
- kind : Kind . NAME ,
550
- value : possibleTypeName ,
587
+ const spreads = possibleTypeNames . map ( ( possibleTypeName ) => {
588
+ if ( ! node . selectionSet ?. selections ) {
589
+ // leaf field, no selection set. return as is we're sure it exists
590
+ return {
591
+ kind : Kind . INLINE_FRAGMENT ,
592
+ typeCondition : {
593
+ kind : Kind . NAMED_TYPE ,
594
+ name : {
595
+ kind : Kind . NAME ,
596
+ value : possibleTypeName ,
597
+ } ,
598
+ } ,
599
+ selectionSet : {
600
+ kind : Kind . SELECTION_SET ,
601
+ selections : [ node ] ,
602
+ } ,
603
+ } ;
604
+ }
605
+
606
+ // object field with selection set. filter it recursively
607
+ const possibleType = schema . getType (
608
+ possibleTypeName ,
609
+ ) as GraphQLObjectType ; // it's an object type because union members must be objects
610
+
611
+ const possibleField = possibleType . getFields ( ) [ node . name . value ] ;
612
+ if ( ! possibleField ) {
613
+ // the field does not exist on the possible type, skip the spread altogether
614
+ return undefined ;
615
+ }
616
+
617
+ // recursively filter the selection set because abstract types can be nested
618
+ const fieldFilteredSelectionSet = filterSelectionSet (
619
+ schema ,
620
+ getTypeInfoWithType ( schema , possibleField . type ) ,
621
+ validFragments ,
622
+ node . selectionSet ,
623
+ onOverlappingAliases ,
624
+ usedFragments ,
625
+ seenNonNullableMap ,
626
+ seenNullableMap ,
627
+ ) ;
628
+
629
+ if ( ! fieldFilteredSelectionSet . selections . length ) {
630
+ // no selections remain after filtering the field, skip the spread altogether
631
+ return undefined ;
632
+ }
633
+
634
+ return {
635
+ kind : Kind . INLINE_FRAGMENT ,
636
+ typeCondition : {
637
+ kind : Kind . NAMED_TYPE ,
638
+ name : {
639
+ kind : Kind . NAME ,
640
+ value : possibleTypeName ,
641
+ } ,
551
642
} ,
552
- } ,
553
- selectionSet : {
554
- kind : Kind . SELECTION_SET ,
555
- selections : [ node ] ,
556
- } ,
557
- } ) ) ;
643
+ selectionSet : {
644
+ kind : Kind . SELECTION_SET ,
645
+ selections : [
646
+ {
647
+ ...node ,
648
+ selectionSet : fieldFilteredSelectionSet ,
649
+ } ,
650
+ ] ,
651
+ } ,
652
+ } ;
653
+ } ) ;
654
+ const nonEmptySpreads = spreads . filter ( Boolean ) ;
655
+ if ( ! nonEmptySpreads . length ) {
656
+ // no spreads remain after filtering, skip the field altogether.
657
+ // this is important to avoid invalid ast nodes causing empty lines
658
+ // in the resulting query
659
+ return undefined ;
660
+ }
661
+ return nonEmptySpreads ;
558
662
}
559
663
}
560
664
return undefined ;
@@ -729,25 +833,6 @@ function finalizeSelectionSet(
729
833
// will hopefully be fixed in future version of graphql-js to be optional
730
834
filteredSelectionSetVisitorKeys as any ,
731
835
) ;
732
-
733
- visit (
734
- filteredSelectionSet ,
735
- {
736
- [ Kind . VARIABLE ] : ( variableNode ) => {
737
- usedVariables . push ( variableNode . name . value ) ;
738
- } ,
739
- } ,
740
- // visitorKeys argument usage a la https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-graphql/src/batching/merge-queries.js
741
- // empty keys cannot be removed only because of typescript errors
742
- // will hopefully be fixed in future version of graphql-js to be optional
743
- variablesVisitorKeys as any ,
744
- ) ;
745
-
746
- return {
747
- selectionSet : filteredSelectionSet ,
748
- usedFragments,
749
- usedVariables,
750
- } ;
751
836
}
752
837
753
838
function union ( ...arrays : Array < Array < string > > ) : Array < string > {
0 commit comments