diff --git a/v2/pkg/ast/ast_selection.go b/v2/pkg/ast/ast_selection.go index 0aee62c1b6..d67ef9f904 100644 --- a/v2/pkg/ast/ast_selection.go +++ b/v2/pkg/ast/ast_selection.go @@ -241,11 +241,3 @@ func (d *Document) SelectionSetFieldNames(set int) (fieldNames []string) { } return } - -func (d *Document) SelectionIsFieldSelection(ref int) bool { - return d.Selections[ref].Kind == SelectionKindField -} - -func (d *Document) SelectionIsInlineFragmentSelection(ref int) bool { - return d.Selections[ref].Kind == SelectionKindInlineFragment -} diff --git a/v2/pkg/astnormalization/abstract_field_normalizer.go b/v2/pkg/astnormalization/abstract_field_normalizer.go index 168456672c..16c365adcc 100644 --- a/v2/pkg/astnormalization/abstract_field_normalizer.go +++ b/v2/pkg/astnormalization/abstract_field_normalizer.go @@ -31,7 +31,6 @@ func NewAbstractFieldNormalizer(operation *ast.Document, definition *ast.Documen mergeInlineFragmentSelections(&walker) inlineSelectionsFromInlineFragments(&walker) - mergeFieldSelections(&walker) deduplicateFields(&walker) return normalizer diff --git a/v2/pkg/astnormalization/astnormalization.go b/v2/pkg/astnormalization/astnormalization.go index 60ff1f2d03..45717e05e0 100644 --- a/v2/pkg/astnormalization/astnormalization.go +++ b/v2/pkg/astnormalization/astnormalization.go @@ -3,7 +3,7 @@ Package astnormalization helps to transform parsed GraphQL AST's into a easier t # Example -This examples shows how the normalization package helps "simplifying" a GraphQL AST. +This example shows how the normalization package helps "simplify" a GraphQL AST. Input: @@ -76,7 +76,7 @@ import ( ) // NormalizeOperation creates a default Normalizer and applies all rules to a given AST -// In case you're using OperationNormalizer in a hot path you shouldn't be using this function. +// In case you're using OperationNormalizer in a hot path, you shouldn't be using this function. // Create a new OperationNormalizer using NewNormalizer() instead and re-use it. func NormalizeOperation(operation, definition *ast.Document, report *operationreport.Report) { normalizer := NewNormalizer(false, false) @@ -193,11 +193,11 @@ func WithNormalizeDefinition() Option { func (o *OperationNormalizer) setupOperationWalkers() { o.operationWalkers = make([]walkerStage, 0, 9) - // NOTE: normalization rules for variables relies on the fact that - // we will visit only single operation, so it is important to remove non-matching operations + // NOTE: normalization rules for variables rely on the fact that + // we will visit only a single operation, so it is important to remove non-matching operations if o.options.removeNotMatchingOperationDefinitions { removeNotMatchingOperationDefinitionsWalker := astvisitor.NewWalker(2) - // this rule do not walk deep into ast, so separate walk not expensive, + // This rule does not walk deep into ast, so a separate walk is not expensive, // but we could not mix this walk with other rules, because they need to go deep o.removeOperationDefinitionsVisitor = removeOperationDefinitions(&removeNotMatchingOperationDefinitionsWalker) @@ -212,7 +212,6 @@ func (o *OperationNormalizer) setupOperationWalkers() { directiveIncludeSkip(&directivesIncludeSkip) cleanup := astvisitor.NewWalker(8) - mergeFieldSelections(&cleanup) deduplicateFields(&cleanup) if o.options.removeUnusedVariables { del := deleteUnusedVariables(&cleanup) @@ -272,7 +271,7 @@ func (o *OperationNormalizer) setupOperationWalkers() { } o.operationWalkers = append(o.operationWalkers, walkerStage{ - name: "mergeFieldSelections, deduplicateFields, deleteUnusedVariables", + name: "deduplicateFields, deleteUnusedVariables", walker: &cleanup, }) @@ -332,7 +331,7 @@ func (o *OperationNormalizer) NormalizeNamedOperation(operation, definition *ast } // NOTE: debug code - do not remove - // printed, _ := astprinter.PrintStringIndent(operation, definition, " ") + // printed, _ := astprinter.PrintStringIndent(operation, " ") // fmt.Println("\n\nNormalizeOperation stage:", o.operationWalkers[i].name) // fmt.Println(printed) // fmt.Println("variables:", string(operation.Input.Variables)) diff --git a/v2/pkg/astnormalization/astnormalization_test.go b/v2/pkg/astnormalization/astnormalization_test.go index c5bfa3053f..7b1f902662 100644 --- a/v2/pkg/astnormalization/astnormalization_test.go +++ b/v2/pkg/astnormalization/astnormalization_test.go @@ -197,6 +197,176 @@ func TestNormalizeOperation(t *testing.T) { }`, ``, ``) }) + // The following 3 tests are to test the combined logic of inline fragment selection merging + // and a field selection merging. When applied separately, they could not merge the field and + // fragments. + t.Run("combined inline fragment fields merging 1", func(t *testing.T) { + run(t, testDefinition, ` + query q { + pet { + ... on Dog { + ... on Pet { + ...DogName + ...DogExtraString + } + } + } + pet { + ... on Dog { + ...DogBarkVolume + ...DogExtraString + } + } + } + fragment DogName on Pet { ... on Dog { name } } + fragment DogBarkVolume on Pet { ... on Dog { barkVolume } } + fragment DogExtraString on Dog { + ...DogName + extras { + ... on DogExtra { + string + } + } + } `, ` + query q { + pet { + ... on Dog { + name + extras { + string + } + barkVolume + } + } + }`, ``, ``) + }) + + t.Run("combined inline fragment fields merging 2", func(t *testing.T) { + run(t, testDefinition, ` + query q { + pet { + ...DogExtraString + } + pet { + ... on Dog { + extras { + ... on DogExtra { + string1 + } + } + } + } + } + fragment DogExtraString on Dog { + extras { + ... on DogExtra { + string + } + ... on Pet { + __typename + } + } + } `, ` + query q { + pet { + ... on Dog { + extras { + string + ... on Pet { + __typename + } + string1 + } + } + } + }`, ``, ``) + }) + + t.Run("combined inline fragment fields merging 3", func(t *testing.T) { + run(t, ` schema { query: Query } + scalar ID + scalar String + + type Query { + requestForQuote(id: ID!): Request + } + type Request { draftQuotePackage: QuotePackage } + type QuotePackage { requestedParts: Parts } + type Parts { edges: [PartEdge] } + type PartEdge { quote: Quote } + type Quote { quoteRevision: QuoteRevisionUnion } + union QuoteRevisionUnion = QuoteRevision | Node + type QuoteRevision { partBreakdown: PartBreakdown } + type Node { name: String! } + type PartBreakdown { + breakdownType: String! + cost: String! + } `, ` + query SimplePartQuoteEditorQuery($requestForQuoteId: ID!) { + requestForQuote(id: $requestForQuoteId) { + draftQuotePackage { + ...useBreakdownTypeFilter_quotePackage + requestedParts { + edges { + quote { + quoteRevision { + ... on QuoteRevision { + partBreakdown { + cost + } + } + } + } + } + } + } + } + } + + fragment useBreakdownTypeFilter_quotePackage on QuotePackage { + requestedParts { + edges { + quote { + quoteRevision { + ... on QuoteRevision { + partBreakdown { + breakdownType + } + } + ... on Node { + __isNode: __typename + } + } + } + } + } + } +`, ` + query SimplePartQuoteEditorQuery($requestForQuoteId: ID!) { + requestForQuote(id: $requestForQuoteId) { + draftQuotePackage { + requestedParts { + edges { + quote { + quoteRevision { + ... on QuoteRevision { + partBreakdown { + breakdownType + cost + } + } + ... on Node { + __isNode: __typename + } + } + } + } + } + } + } + }`, `123`, `123`) + }) + t.Run("fragments", func(t *testing.T) { run(t, variablesExtractionDefinition, ` mutation HttpBinPost{ diff --git a/v2/pkg/astnormalization/field_deduplication.go b/v2/pkg/astnormalization/field_deduplication.go index 54aa16a3c5..23f832a1ce 100644 --- a/v2/pkg/astnormalization/field_deduplication.go +++ b/v2/pkg/astnormalization/field_deduplication.go @@ -5,6 +5,7 @@ import ( "github.com/wundergraph/graphql-go-tools/v2/pkg/astvisitor" ) +// deduplicateFields registers a visitor to remove duplicate fields in a GraphQL operation. func deduplicateFields(walker *astvisitor.Walker) { visitor := deduplicateFieldsVisitor{ Walker: walker, diff --git a/v2/pkg/astnormalization/field_selection_merging.go b/v2/pkg/astnormalization/field_selection_merging.go deleted file mode 100644 index b0592db116..0000000000 --- a/v2/pkg/astnormalization/field_selection_merging.go +++ /dev/null @@ -1,95 +0,0 @@ -package astnormalization - -import ( - "bytes" - - "github.com/wundergraph/graphql-go-tools/v2/pkg/ast" - "github.com/wundergraph/graphql-go-tools/v2/pkg/astvisitor" -) - -func mergeFieldSelections(walker *astvisitor.Walker) { - visitor := fieldSelectionMergeVisitor{ - Walker: walker, - } - walker.RegisterEnterDocumentVisitor(&visitor) - walker.RegisterEnterSelectionSetVisitor(&visitor) -} - -type fieldSelectionMergeVisitor struct { - *astvisitor.Walker - operation *ast.Document -} - -func (f *fieldSelectionMergeVisitor) EnterDocument(operation, definition *ast.Document) { - f.operation = operation -} - -func (f *fieldSelectionMergeVisitor) fieldsCanMerge(left, right int) bool { - leftName := f.operation.FieldNameBytes(left) - rightName := f.operation.FieldNameBytes(right) - leftAlias := f.operation.FieldAliasBytes(left) - rightAlias := f.operation.FieldAliasBytes(right) - - if !bytes.Equal(leftName, rightName) || !bytes.Equal(leftAlias, rightAlias) { - return false - } - - leftDirectives := f.operation.FieldDirectives(left) - rightDirectives := f.operation.FieldDirectives(right) - - return f.operation.DirectiveSetsAreEqual(leftDirectives, rightDirectives) -} - -func (f *fieldSelectionMergeVisitor) fieldsHaveSelections(left, right int) bool { - return f.operation.Fields[left].HasSelections && f.operation.Fields[right].HasSelections -} - -func (f *fieldSelectionMergeVisitor) mergeFields(left, right int) (ok bool) { - var leftSet, rightSet int - leftSet, ok = f.operation.FieldSelectionSet(left) - if !ok { - return false - } - rightSet, ok = f.operation.FieldSelectionSet(right) - if !ok { - return false - } - - f.operation.AppendSelectionSet(leftSet, rightSet) - return true -} - -func (f *fieldSelectionMergeVisitor) EnterSelectionSet(ref int) { - - if len(f.operation.SelectionSets[ref].SelectionRefs) < 2 { - return - } - - for _, leftSelection := range f.operation.SelectionSets[ref].SelectionRefs { - if !f.operation.SelectionIsFieldSelection(leftSelection) { - continue - } - leftField := f.operation.Selections[leftSelection].Ref - for i, rightSelection := range f.operation.SelectionSets[ref].SelectionRefs { - if !f.operation.SelectionIsFieldSelection(rightSelection) { - continue - } - if leftSelection == rightSelection { - continue - } - rightField := f.operation.Selections[rightSelection].Ref - if !f.fieldsHaveSelections(leftField, rightField) { - continue - } - if !f.fieldsCanMerge(leftField, rightField) { - continue - } - - if f.mergeFields(leftField, rightField) { - f.operation.RemoveFromSelectionSet(ref, i) - f.RevisitNode() - } - return - } - } -} diff --git a/v2/pkg/astnormalization/field_selection_merging_test.go b/v2/pkg/astnormalization/field_selection_merging_test.go deleted file mode 100644 index 91a132656a..0000000000 --- a/v2/pkg/astnormalization/field_selection_merging_test.go +++ /dev/null @@ -1,100 +0,0 @@ -package astnormalization - -import "testing" - -func TestMergeFieldSelections(t *testing.T) { - t.Run("depth 1", func(t *testing.T) { - run(t, mergeFieldSelections, testDefinition, ` - query conflictingBecauseAlias { - dog { - extra { string } - extra { noString: string } - } - }`, ` - query conflictingBecauseAlias { - dog { - extra { - string - noString: string - } - } - }`) - }) - t.Run("depth 2", func(t *testing.T) { - run(t, mergeFieldSelections, testDefinition, ` - query conflictingBecauseAlias { - dog { - extra { string } - extra { string: noString } - } - dog { - extra { string } - extra { string: noString } - } - }`, ` - query conflictingBecauseAlias { - dog { - extra { - string - string: noString - string - string: noString - } - } - }`) - }) - t.Run("aliased", func(t *testing.T) { - t.Run("aliased", func(t *testing.T) { - run(t, mergeFieldSelections, testDefinition, ` - query conflictingBecauseAlias { - dog { - x: extras { string } - x: mustExtras { string } - } - }`, ` - query conflictingBecauseAlias { - dog { - x: extras { string } - x: mustExtras { string } - } - }`) - }) - }) - t.Run("fields with directives", func(t *testing.T) { - run(t, mergeFieldSelections, testDefinition, ` - { - field @skip(if: $foo) { - subfieldA - } - field @skip(if: $bar) { - subfieldB - } - }`, ` - { - field @skip(if: $foo) { - subfieldA - } - field @skip(if: $bar) { - subfieldB - } - }`) - }) - - t.Run("fields with the same directives", func(t *testing.T) { - run(t, mergeFieldSelections, testDefinition, ` - { - field @skip(if: $foo) { - subfieldA - } - field @skip(if: $foo) { - subfieldB - } - }`, ` - { - field @skip(if: $foo) { - subfieldA - subfieldB - } - }`) - }) -} diff --git a/v2/pkg/astnormalization/fragment_definition_removal.go b/v2/pkg/astnormalization/fragment_definition_removal.go index cf10f1acae..df7185483c 100644 --- a/v2/pkg/astnormalization/fragment_definition_removal.go +++ b/v2/pkg/astnormalization/fragment_definition_removal.go @@ -8,6 +8,8 @@ import ( type FragmentDefinitionRemoval struct { } +// removeFragmentDefinitions registers a visitor to mark all the unused fragment definitions +// as NodeKindUnknown in the operation. func removeFragmentDefinitions(walker *astvisitor.Walker) { visitor := &removeFragmentDefinitionsVisitor{ walker: walker, diff --git a/v2/pkg/astnormalization/inline_fragment_selection_merging.go b/v2/pkg/astnormalization/inline_fragment_selection_merging.go index 39c94ff020..541193e63b 100644 --- a/v2/pkg/astnormalization/inline_fragment_selection_merging.go +++ b/v2/pkg/astnormalization/inline_fragment_selection_merging.go @@ -7,6 +7,8 @@ import ( "github.com/wundergraph/graphql-go-tools/v2/pkg/astvisitor" ) +// mergeInlineFragmentSelections registers a visitor that +// merges inline fragment and field selections. func mergeInlineFragmentSelections(walker *astvisitor.Walker) { visitor := inlineFragmentSelectionMergeVisitor{ Walker: walker, @@ -54,33 +56,88 @@ func (f *inlineFragmentSelectionMergeVisitor) mergeInlineFragments(left, right i return true } +func (f *inlineFragmentSelectionMergeVisitor) fieldsCanMerge(left, right int) bool { + leftName := f.operation.FieldNameBytes(left) + rightName := f.operation.FieldNameBytes(right) + leftAlias := f.operation.FieldAliasBytes(left) + rightAlias := f.operation.FieldAliasBytes(right) + + if !bytes.Equal(leftName, rightName) || !bytes.Equal(leftAlias, rightAlias) { + return false + } + + leftDirectives := f.operation.FieldDirectives(left) + rightDirectives := f.operation.FieldDirectives(right) + + return f.operation.DirectiveSetsAreEqual(leftDirectives, rightDirectives) +} + +func (f *inlineFragmentSelectionMergeVisitor) fieldsHaveSelections(left, right int) bool { + return f.operation.Fields[left].HasSelections && f.operation.Fields[right].HasSelections +} + +func (f *inlineFragmentSelectionMergeVisitor) mergeFields(left, right int) (ok bool) { + var leftSet, rightSet int + leftSet, ok = f.operation.FieldSelectionSet(left) + if !ok { + return false + } + rightSet, ok = f.operation.FieldSelectionSet(right) + if !ok { + return false + } + + f.operation.AppendSelectionSet(leftSet, rightSet) + return true +} + func (f *inlineFragmentSelectionMergeVisitor) EnterSelectionSet(ref int) { - if len(f.operation.SelectionSets[ref].SelectionRefs) < 2 { + selectionRefs := f.operation.SelectionSets[ref].SelectionRefs + if len(selectionRefs) < 2 { return } - for _, leftSelection := range f.operation.SelectionSets[ref].SelectionRefs { - if !f.operation.SelectionIsInlineFragmentSelection(leftSelection) { + for i, leftSelection := range selectionRefs { + leftKind := f.operation.SelectionKind(leftSelection) + if leftKind != ast.SelectionKindInlineFragment && leftKind != ast.SelectionKindField { continue } - leftInlineFragment := f.operation.Selections[leftSelection].Ref - for i, rightSelection := range f.operation.SelectionSets[ref].SelectionRefs { - if !f.operation.SelectionIsInlineFragmentSelection(rightSelection) { + leftRef := f.operation.Selections[leftSelection].Ref + + for j := i + 1; j < len(selectionRefs); j++ { + rightSelection := selectionRefs[j] + rightKind := f.operation.SelectionKind(rightSelection) + if leftKind != rightKind { continue } - if leftSelection == rightSelection { + + rightRef := f.operation.Selections[rightSelection].Ref + + // Handle Inline Fragments. + if leftKind == ast.SelectionKindInlineFragment { + if !f.fragmentsCanBeMerged(leftRef, rightRef) { + continue + } + if f.mergeInlineFragments(leftRef, rightRef) { + f.operation.RemoveFromSelectionSet(ref, j) + f.RevisitNode() + return + } continue } - rightInlineFragment := f.operation.Selections[rightSelection].Ref - if !f.fragmentsCanBeMerged(leftInlineFragment, rightInlineFragment) { + + // Handle Fields. + if !f.fieldsHaveSelections(leftRef, rightRef) { continue } - if f.mergeInlineFragments(leftInlineFragment, rightInlineFragment) { - f.operation.RemoveFromSelectionSet(ref, i) + if !f.fieldsCanMerge(leftRef, rightRef) { + continue + } + if f.mergeFields(leftRef, rightRef) { + f.operation.RemoveFromSelectionSet(ref, j) f.RevisitNode() + return } - - return } } } diff --git a/v2/pkg/astnormalization/inline_fragment_selection_merging_test.go b/v2/pkg/astnormalization/inline_fragment_selection_merging_test.go index 8433719b42..ffb9f7359f 100644 --- a/v2/pkg/astnormalization/inline_fragment_selection_merging_test.go +++ b/v2/pkg/astnormalization/inline_fragment_selection_merging_test.go @@ -2,9 +2,10 @@ package astnormalization import "testing" -func TestMergeInlineFragmentSelections(t *testing.T) { - t.Run("fragments with the same field", func(t *testing.T) { - run(t, mergeInlineFragmentSelections, testDefinition, ` +func TestMergeInlineFragmentFieldSelections(t *testing.T) { + t.Run("just fragments", func(t *testing.T) { + t.Run("fragments with the same field", func(t *testing.T) { + run(t, mergeInlineFragmentSelections, testDefinition, ` query a { pet { ... on Dog { @@ -27,9 +28,9 @@ func TestMergeInlineFragmentSelections(t *testing.T) { } } }`) - }) - t.Run("fragments with different fields", func(t *testing.T) { - run(t, mergeInlineFragmentSelections, testDefinition, ` + }) + t.Run("fragments with different fields", func(t *testing.T) { + run(t, mergeInlineFragmentSelections, testDefinition, ` query a { pet { ... on Dog { @@ -52,9 +53,9 @@ func TestMergeInlineFragmentSelections(t *testing.T) { } } }`) - }) - t.Run("fragments with different types", func(t *testing.T) { - run(t, mergeInlineFragmentSelections, testDefinition, ` + }) + t.Run("fragments with different types", func(t *testing.T) { + run(t, mergeInlineFragmentSelections, testDefinition, ` query a { pet { ... on Dog { @@ -75,9 +76,9 @@ func TestMergeInlineFragmentSelections(t *testing.T) { } } }`) - }) - t.Run("fragments with different directives", func(t *testing.T) { - run(t, mergeInlineFragmentSelections, testDefinition, ` + }) + t.Run("fragments with different directives", func(t *testing.T) { + run(t, mergeInlineFragmentSelections, testDefinition, ` query a { pet { ... on Dog @skip(if: $foo) { @@ -98,10 +99,10 @@ func TestMergeInlineFragmentSelections(t *testing.T) { } } }`) - }) + }) - t.Run("fragments with the same directives", func(t *testing.T) { - run(t, mergeInlineFragmentSelections, testDefinition, ` + t.Run("fragments with the same directives", func(t *testing.T) { + run(t, mergeInlineFragmentSelections, testDefinition, ` query a { pet { ... on Dog @skip(if: $foo) { @@ -120,5 +121,215 @@ func TestMergeInlineFragmentSelections(t *testing.T) { } } }`) + }) + }) + t.Run("just fields", func(t *testing.T) { + t.Run("depth 1", func(t *testing.T) { + run(t, mergeInlineFragmentSelections, testDefinition, ` + query conflictingBecauseAlias { + dog { + extra { string } + extra { noString: string } + } + }`, ` + query conflictingBecauseAlias { + dog { + extra { + string + noString: string + } + } + }`) + }) + t.Run("depth 2", func(t *testing.T) { + run(t, mergeInlineFragmentSelections, testDefinition, ` + query conflictingBecauseAlias { + dog { + extra { string } + extra { string: noString } + } + dog { + extra { string } + extra { string: noString } + } + }`, ` + query conflictingBecauseAlias { + dog { + extra { + string + string: noString + string + string: noString + } + } + }`) + }) + t.Run("aliased", func(t *testing.T) { + run(t, mergeInlineFragmentSelections, testDefinition, ` + query conflictingBecauseAlias { + dog { + x: extras { string } + x: mustExtras { string } + } + }`, ` + query conflictingBecauseAlias { + dog { + x: extras { string } + x: mustExtras { string } + } + }`) + }) + t.Run("fields with directives", func(t *testing.T) { + run(t, mergeInlineFragmentSelections, testDefinition, ` + { + field @skip(if: $foo) { + subfieldA + } + field @skip(if: $bar) { + subfieldB + } + }`, ` + { + field @skip(if: $foo) { + subfieldA + } + field @skip(if: $bar) { + subfieldB + } + }`) + }) + + t.Run("fields with the same directives", func(t *testing.T) { + run(t, mergeInlineFragmentSelections, testDefinition, ` + { + field @skip(if: $foo) { + subfieldA + } + field @skip(if: $foo) { + subfieldB + } + }`, ` + { + field @skip(if: $foo) { + subfieldA + subfieldB + } + }`) + }) + }) + t.Run("fragments and fields", func(t *testing.T) { + t.Run("field field fragment", func(t *testing.T) { + run(t, mergeInlineFragmentSelections, testDefinition, ` + query fragmentsWithFields { + dog { + extras { + ... on DogExtra { + string + } + } + extras { + ... on DogExtra { + string + string1 + } + } + } + }`, ` + query fragmentsWithFields { + dog { + extras { + ... on DogExtra { + string + string + string1 + } + } + } + }`) + }) + t.Run("mixed heavily fields with deep inline fragments", func(t *testing.T) { + run(t, mergeInlineFragmentSelections, testDefinition, ` + query mixedFragmentTypes { + pet { + ... on Dog { + name + } + } + pet { + ... on Dog { + extras { + ... on DogExtra { + string + } + } + } + } + pet { + ... on Dog { + name + barkVolume + } + } + pet { + ... on Dog { + extras { + ... on DogExtra { + string1 + } + } + } + } + pet { + ... on Cat { + name + } + } + pet { + ... on Dog { + name + barkVolume + owner { + name + } + } + } + pet { + ... on Cat { + meowVolume + } + } + pet { + ... on Cat { + meowVolume + } + } + }`, ` + query mixedFragmentTypes { + pet { + ... on Dog { + name + extras { + ... on DogExtra { + string + string1 + } + } + name + barkVolume + name + barkVolume + owner { + name + } + } + ... on Cat { + name + meowVolume + meowVolume + } + } + }`) + }) + }) } diff --git a/v2/pkg/astnormalization/remove_self_aliasing.go b/v2/pkg/astnormalization/remove_self_aliasing.go index 09f55d8aab..e9af4c80f2 100644 --- a/v2/pkg/astnormalization/remove_self_aliasing.go +++ b/v2/pkg/astnormalization/remove_self_aliasing.go @@ -7,6 +7,7 @@ import ( "github.com/wundergraph/graphql-go-tools/v2/pkg/astvisitor" ) +// removeSelfAliasing registers a visitor to remove redundant aliasing within GraphQL documents. func removeSelfAliasing(walker *astvisitor.Walker) { visitor := removeSelfAliasingVisitor{} walker.RegisterEnterDocumentVisitor(&visitor)