Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 35 additions & 3 deletions src/main/java/graphql/schema/usage/SchemaUsageSupport.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package graphql.schema.usage;

import graphql.PublicApi;
import graphql.schema.GraphQLAppliedDirective;
import graphql.schema.GraphQLAppliedDirectiveArgument;
import graphql.schema.GraphQLArgument;
import graphql.schema.GraphQLDirective;
import graphql.schema.GraphQLFieldDefinition;
Expand All @@ -20,6 +22,7 @@
import graphql.schema.SchemaTraverser;
import graphql.util.TraversalControl;
import graphql.util.TraverserContext;
import org.jetbrains.annotations.Nullable;

import java.util.HashSet;
import java.util.List;
Expand Down Expand Up @@ -59,6 +62,10 @@ private void recordBackReference(GraphQLNamedSchemaElement referencedElement, Gr
String typeName = ((GraphQLDirective) referencingElement).getName();
builder.elementBackReferences.computeIfAbsent(referencedElementName, k -> new HashSet<>()).add(typeName);
}
if (referencingElement instanceof GraphQLAppliedDirective) {
String typeName = ((GraphQLAppliedDirective) referencingElement).getName();
builder.elementBackReferences.computeIfAbsent(referencedElementName, k -> new HashSet<>()).add(typeName);
}
}

private void memberInterfaces(GraphQLNamedType containingType, List<GraphQLNamedOutputType> members) {
Expand All @@ -84,6 +91,19 @@ public TraversalControl visitGraphQLArgument(GraphQLArgument node, TraverserCont
return CONTINUE;
}

@Override
public TraversalControl visitGraphQLAppliedDirectiveArgument(GraphQLAppliedDirectiveArgument node, TraverserContext<GraphQLSchemaElement> context) {
GraphQLNamedType inputType = GraphQLTypeUtil.unwrapAll(node.getType());
builder.argReferenceCount.compute(inputType.getName(), incCount());

GraphQLSchemaElement parentElement = context.getParentNode();
if (parentElement instanceof GraphQLAppliedDirective) {
parentElement = context.getParentContext().getParentNode();
}
recordBackReference(inputType, parentElement);
return CONTINUE;
}

@Override
public TraversalControl visitGraphQLFieldDefinition(GraphQLFieldDefinition node, TraverserContext<GraphQLSchemaElement> context) {
GraphQLNamedType fieldType = GraphQLTypeUtil.unwrapAll(node.getType());
Expand All @@ -108,11 +128,24 @@ public TraversalControl visitGraphQLInputObjectField(GraphQLInputObjectField nod

@Override
public TraversalControl visitGraphQLDirective(GraphQLDirective directive, TraverserContext<GraphQLSchemaElement> context) {
GraphQLSchemaElement parentElement = visitDirectiveLike(context, directive.getName());
recordBackReference(directive, parentElement);
return CONTINUE;
}

@Override
public TraversalControl visitGraphQLAppliedDirective(GraphQLAppliedDirective appliedDirective, TraverserContext<GraphQLSchemaElement> context) {
GraphQLSchemaElement parentElement = visitDirectiveLike(context, appliedDirective.getName());
recordBackReference(appliedDirective, parentElement);
return CONTINUE;
}

private GraphQLSchemaElement visitDirectiveLike(TraverserContext<GraphQLSchemaElement> context, String directiveName) {
GraphQLSchemaElement parentElement = context.getParentNode();
if (parentElement != null) {
// a null parent is a directive definition
// we record a count if the directive is applied to something - not just defined
builder.directiveReferenceCount.compute(directive.getName(), incCount());
builder.directiveReferenceCount.compute(directiveName, incCount());
}
if (parentElement instanceof GraphQLArgument) {
context = context.getParentContext();
Expand All @@ -126,8 +159,7 @@ public TraversalControl visitGraphQLDirective(GraphQLDirective directive, Traver
context = context.getParentContext();
parentElement = context.getParentNode();
}
recordBackReference(directive, parentElement);
return CONTINUE;
return parentElement;
}

@Override
Expand Down
55 changes: 43 additions & 12 deletions src/test/groovy/graphql/schema/usage/SchemaUsageSupportTest.groovy
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package graphql.schema.usage

import graphql.TestUtil
import graphql.schema.GraphQLAppliedDirective
import graphql.schema.GraphQLFieldDefinition
import graphql.schema.SchemaTransformer
import graphql.schema.visitor.GraphQLSchemaTraversalControl
import graphql.schema.visitor.GraphQLSchemaVisitor
import spock.lang.Specification

class SchemaUsageSupportTest extends Specification {
Expand Down Expand Up @@ -193,10 +198,10 @@ class SchemaUsageSupportTest extends Specification {
!schemaUsage.isStronglyReferenced(schema, "UnRefInputTypeDirective")
!schemaUsage.isStronglyReferenced(schema, "UnRefDirectiveInputType")

schemaUsage.getUnReferencedElements(schema).collect {it.name}.sort() ==
schemaUsage.getUnReferencedElements(schema).collect { it.name }.sort() ==
["UnIRef1", "UnRef1", "UnRefDirectiveInputType", "UnRefEnum1",
"UnRefHangingInputType", "UnRefHangingInputType2", "UnRefHangingInputType3",
"UnRefHangingType","UnRefHangingType2", "UnRefInput1",
"UnRefHangingType", "UnRefHangingType2", "UnRefInput1",
"UnRefFieldDirective", "UnRefInputTypeDirective", "UnRefHangingArgDirective"].sort()
}

Expand Down Expand Up @@ -233,17 +238,43 @@ class SchemaUsageSupportTest extends Specification {
def schemaUsage = SchemaUsageSupport.getSchemaUsage(schema)

then:
! schemaUsage.isStronglyReferenced(schema, "UnRefHangingType")
! schemaUsage.isStronglyReferenced(schema, "UnRefHangingType2")
! schemaUsage.isStronglyReferenced(schema, "UnRefHangingType3")
! schemaUsage.isStronglyReferenced(schema, "UnRefHangingInputType")
! schemaUsage.isStronglyReferenced(schema, "UnRefHangingInputType2")
! schemaUsage.isStronglyReferenced(schema, "UnRefHangingInputType3")
! schemaUsage.isStronglyReferenced(schema, "UnRefHangingArgDirective")

schemaUsage.getDirectiveReferenceCounts()["UnRefHangingArgDirective"] == 1
!schemaUsage.isStronglyReferenced(schema, "UnRefHangingType")
!schemaUsage.isStronglyReferenced(schema, "UnRefHangingType2")
!schemaUsage.isStronglyReferenced(schema, "UnRefHangingType3")
!schemaUsage.isStronglyReferenced(schema, "UnRefHangingInputType")
!schemaUsage.isStronglyReferenced(schema, "UnRefHangingInputType2")
!schemaUsage.isStronglyReferenced(schema, "UnRefHangingInputType3")
!schemaUsage.isStronglyReferenced(schema, "UnRefHangingArgDirective")

// 2 because of the dual nature of directives and applied directives
schemaUsage.getDirectiveReferenceCounts()["UnRefHangingArgDirective"] == 2
schemaUsage.getArgumentReferenceCounts()["UnRefHangingInputType"] == 1
schemaUsage.getFieldReferenceCounts()["UnRefHangingType2"] == 2
schemaUsage.getArgumentReferenceCounts()["UnRefHangingInputType3"] == 2
schemaUsage.getArgumentReferenceCounts()["UnRefHangingInputType3"] == 3
}

def "can handle cleared directives"() {
// https://github.com/graphql-java/graphql-java/issues/3267


def schema = TestUtil.schema(sdl)
schema = new SchemaTransformer().transform(schema, new GraphQLSchemaVisitor() {

@Override
GraphQLSchemaTraversalControl visitFieldDefinition(GraphQLFieldDefinition fieldDef, GraphQLSchemaVisitor.FieldDefinitionVisitorEnvironment env) {
if (fieldDef.getAppliedDirective("RefFieldDirective") != null) {
List<GraphQLAppliedDirective> directives = fieldDef.getAppliedDirectives();
fieldDef = fieldDef.transform(
f -> f.clearDirectives().replaceAppliedDirectives(directives)
)
}
return env.changeNode(fieldDef)
}
}.toTypeVisitor())

when:
def schemaUsage = SchemaUsageSupport.getSchemaUsage(schema)
then:
schemaUsage.isStronglyReferenced(schema, "RefFieldDirective")
}
}