@@ -713,93 +713,113 @@ func (set *nodeSet) Add(node ast.Node) bool {
713
713
return true
714
714
}
715
715
716
+ func CycleErrorMessage (fragName string , spreadNames []string ) string {
717
+ via := ""
718
+ if len (spreadNames ) > 0 {
719
+ via = " via " + strings .Join (spreadNames , ", " )
720
+ }
721
+ return fmt .Sprintf (`Cannot spread fragment "%v" within itself%v.` , fragName , via )
722
+ }
723
+
716
724
/**
717
725
* NoFragmentCyclesRule
718
726
*/
719
727
func NoFragmentCyclesRule (context * ValidationContext ) * ValidationRuleInstance {
720
- // Gather all the fragment spreads ASTs for each fragment definition.
721
- // Importantly this does not include inline fragments.
722
- definitions := context .Document ().Definitions
723
- spreadsInFragment := map [string ][]* ast.FragmentSpread {}
724
- for _ , node := range definitions {
725
- if node .GetKind () == kinds .FragmentDefinition {
726
- if node , ok := node .(* ast.FragmentDefinition ); ok && node != nil {
727
- nodeName := ""
728
- if node .Name != nil {
729
- nodeName = node .Name .Value
728
+
729
+ // Tracks already visited fragments to maintain O(N) and to ensure that cycles
730
+ // are not redundantly reported.
731
+ visitedFrags := map [string ]bool {}
732
+
733
+ // Array of AST nodes used to produce meaningful errors
734
+ spreadPath := []* ast.FragmentSpread {}
735
+
736
+ // Position in the spread path
737
+ spreadPathIndexByName := map [string ]int {}
738
+
739
+ // This does a straight-forward DFS to find cycles.
740
+ // It does not terminate when a cycle was found but continues to explore
741
+ // the graph to find all possible cycles.
742
+ var detectCycleRecursive func (fragment * ast.FragmentDefinition )
743
+ detectCycleRecursive = func (fragment * ast.FragmentDefinition ) {
744
+
745
+ fragmentName := ""
746
+ if fragment .Name != nil {
747
+ fragmentName = fragment .Name .Value
748
+ }
749
+ visitedFrags [fragmentName ] = true
750
+
751
+ spreadNodes := context .FragmentSpreads (fragment )
752
+ if len (spreadNodes ) == 0 {
753
+ return
754
+ }
755
+
756
+ spreadPathIndexByName [fragmentName ] = len (spreadPath )
757
+
758
+ for _ , spreadNode := range spreadNodes {
759
+
760
+ spreadName := ""
761
+ if spreadNode .Name != nil {
762
+ spreadName = spreadNode .Name .Value
763
+ }
764
+ cycleIndex , ok := spreadPathIndexByName [spreadName ]
765
+ if ! ok {
766
+ spreadPath = append (spreadPath , spreadNode )
767
+ if visited , ok := visitedFrags [spreadName ]; ! ok || ! visited {
768
+ spreadFragment := context .Fragment (spreadName )
769
+ if spreadFragment != nil {
770
+ detectCycleRecursive (spreadFragment )
771
+ }
772
+ }
773
+ spreadPath = spreadPath [:len (spreadPath )- 1 ]
774
+ } else {
775
+ cyclePath := spreadPath [cycleIndex :]
776
+
777
+ spreadNames := []string {}
778
+ for _ , s := range cyclePath {
779
+ name := ""
780
+ if s .Name != nil {
781
+ name = s .Name .Value
782
+ }
783
+ spreadNames = append (spreadNames , name )
784
+ }
785
+
786
+ nodes := []ast.Node {}
787
+ for _ , c := range cyclePath {
788
+ nodes = append (nodes , c )
730
789
}
731
- spreadsInFragment [nodeName ] = gatherSpreads (node )
790
+ nodes = append (nodes , spreadNode )
791
+
792
+ reportError (
793
+ context ,
794
+ CycleErrorMessage (spreadName , spreadNames ),
795
+ nodes ,
796
+ )
732
797
}
798
+
733
799
}
800
+ delete (spreadPathIndexByName , fragmentName )
801
+
734
802
}
735
- // Tracks spreads known to lead to cycles to ensure that cycles are not
736
- // redundantly reported.
737
- knownToLeadToCycle := newNodeSet ()
738
803
739
804
visitorOpts := & visitor.VisitorOptions {
740
805
KindFuncMap : map [string ]visitor.NamedVisitFuncs {
806
+ kinds .OperationDefinition : visitor.NamedVisitFuncs {
807
+ Kind : func (p visitor.VisitFuncParams ) (string , interface {}) {
808
+ return visitor .ActionSkip , nil
809
+ },
810
+ },
741
811
kinds .FragmentDefinition : visitor.NamedVisitFuncs {
742
812
Kind : func (p visitor.VisitFuncParams ) (string , interface {}) {
743
813
if node , ok := p .Node .(* ast.FragmentDefinition ); ok && node != nil {
744
- spreadPath := []* ast.FragmentSpread {}
745
- initialName := ""
814
+ nodeName := ""
746
815
if node .Name != nil {
747
- initialName = node .Name .Value
816
+ nodeName = node .Name .Value
748
817
}
749
- var detectCycleRecursive func (fragmentName string )
750
- detectCycleRecursive = func (fragmentName string ) {
751
- spreadNodes , _ := spreadsInFragment [fragmentName ]
752
- for _ , spreadNode := range spreadNodes {
753
- if knownToLeadToCycle .Has (spreadNode ) {
754
- continue
755
- }
756
- spreadNodeName := ""
757
- if spreadNode .Name != nil {
758
- spreadNodeName = spreadNode .Name .Value
759
- }
760
- if spreadNodeName == initialName {
761
- cyclePath := []ast.Node {}
762
- for _ , path := range spreadPath {
763
- cyclePath = append (cyclePath , path )
764
- }
765
- cyclePath = append (cyclePath , spreadNode )
766
- for _ , spread := range cyclePath {
767
- knownToLeadToCycle .Add (spread )
768
- }
769
- via := ""
770
- spreadNames := []string {}
771
- for _ , s := range spreadPath {
772
- if s .Name != nil {
773
- spreadNames = append (spreadNames , s .Name .Value )
774
- }
775
- }
776
- if len (spreadNames ) > 0 {
777
- via = " via " + strings .Join (spreadNames , ", " )
778
- }
779
- reportError (
780
- context ,
781
- fmt .Sprintf (`Cannot spread fragment "%v" within itself%v.` , initialName , via ),
782
- cyclePath ,
783
- )
784
- continue
785
- }
786
- spreadPathHasCurrentNode := false
787
- for _ , spread := range spreadPath {
788
- if spread == spreadNode {
789
- spreadPathHasCurrentNode = true
790
- }
791
- }
792
- if spreadPathHasCurrentNode {
793
- continue
794
- }
795
- spreadPath = append (spreadPath , spreadNode )
796
- detectCycleRecursive (spreadNodeName )
797
- _ , spreadPath = spreadPath [len (spreadPath )- 1 ], spreadPath [:len (spreadPath )- 1 ]
798
- }
818
+ if _ , ok := visitedFrags [nodeName ]; ! ok {
819
+ detectCycleRecursive (node )
799
820
}
800
- detectCycleRecursive (initialName )
801
821
}
802
- return visitor .ActionNoChange , nil
822
+ return visitor .ActionSkip , nil
803
823
},
804
824
},
805
825
},
@@ -2163,25 +2183,3 @@ func isValidLiteralValue(ttype Input, valueAST ast.Value) (bool, []string) {
2163
2183
2164
2184
return true , nil
2165
2185
}
2166
-
2167
- /**
2168
- * Given an operation or fragment AST node, gather all the
2169
- * named spreads defined within the scope of the fragment
2170
- * or operation
2171
- */
2172
- func gatherSpreads (node ast.Node ) (spreadNodes []* ast.FragmentSpread ) {
2173
- visitorOpts := & visitor.VisitorOptions {
2174
- KindFuncMap : map [string ]visitor.NamedVisitFuncs {
2175
- kinds .FragmentSpread : visitor.NamedVisitFuncs {
2176
- Kind : func (p visitor.VisitFuncParams ) (string , interface {}) {
2177
- if node , ok := p .Node .(* ast.FragmentSpread ); ok && node != nil {
2178
- spreadNodes = append (spreadNodes , node )
2179
- }
2180
- return visitor .ActionNoChange , nil
2181
- },
2182
- },
2183
- },
2184
- }
2185
- visitor .Visit (node , visitorOpts , nil )
2186
- return spreadNodes
2187
- }
0 commit comments