graphql.language.AstSorter Maven / Gradle / Ivy
package graphql.language;
import graphql.PublicApi;
import graphql.schema.idl.TypeInfo;
import graphql.util.TraversalControl;
import graphql.util.TraverserContext;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;
import static graphql.util.TreeTransformerUtil.changeNode;
import static java.util.Comparator.naturalOrder;
import static java.util.Comparator.nullsLast;
/**
* A class that helps you sort AST nodes
*/
@PublicApi
public class AstSorter {
/**
* This will sort nodes in specific orders and then alphabetically.
*
* The order is :
*
* - Query operation definitions
* - Mutation operation definitions
* - Subscriptions operation definitions
* - Fragment definitions
* - Directive definitions
* - Schema definitions
* - Object Type definitions
* - Interface Type definitions
* - Union Type definitions
* - Enum Type definitions
* - Scalar Type definitions
* - Input Object Type definitions
*
*
* After those groupings they will be sorted alphabetic. All arguments and directives on elements
* will be sorted alphabetically by name.
*
* @param nodeToBeSorted the node to be sorted
* @param of type {@link graphql.language.Node}
*
* @return a new sorted node (because {@link graphql.language.Node}s are immutable)
*/
public T sort(T nodeToBeSorted) {
NodeVisitorStub visitor = new NodeVisitorStub() {
@Override
public TraversalControl visitDocument(Document node, TraverserContext context) {
Document changedNode = node.transform(builder -> {
List definitions = sort(node.getDefinitions(), comparingDefinitions());
builder.definitions(definitions);
});
return changeNode(context, changedNode);
}
@Override
public TraversalControl visitOperationDefinition(OperationDefinition node, TraverserContext context) {
OperationDefinition changedNode = node.transform(builder -> {
builder.variableDefinitions(sort(node.getVariableDefinitions(), comparing(VariableDefinition::getName)));
builder.directives(sort(node.getDirectives(), comparing(Directive::getName)));
builder.selectionSet(sortSelectionSet(node.getSelectionSet()));
});
return changeNode(context, changedNode);
}
@Override
public TraversalControl visitField(Field node, TraverserContext context) {
Field changedNode = node.transform(builder -> {
builder.arguments(sort(node.getArguments(), comparing(Argument::getName)));
builder.directives(sort(node.getDirectives(), comparing(Directive::getName)));
builder.selectionSet(sortSelectionSet(node.getSelectionSet()));
});
return changeNode(context, changedNode);
}
@Override
public TraversalControl visitFragmentDefinition(FragmentDefinition node, TraverserContext context) {
FragmentDefinition changedNode = node.transform(builder -> {
builder.directives(sort(node.getDirectives(), comparing(Directive::getName)));
builder.selectionSet(sortSelectionSet(node.getSelectionSet()));
});
return changeNode(context, changedNode);
}
@Override
public TraversalControl visitInlineFragment(InlineFragment node, TraverserContext context) {
InlineFragment changedNode = node.transform(builder -> {
builder.directives(sort(node.getDirectives(), comparing(Directive::getName)));
builder.selectionSet(sortSelectionSet(node.getSelectionSet()));
});
return changeNode(context, changedNode);
}
@Override
public TraversalControl visitFragmentSpread(FragmentSpread node, TraverserContext context) {
FragmentSpread changedNode = node.transform(builder -> {
List directives = sort(node.getDirectives(), comparing(Directive::getName));
builder.directives(directives);
});
return changeNode(context, changedNode);
}
@Override
public TraversalControl visitDirective(Directive node, TraverserContext context) {
Directive changedNode = node.transform(builder -> {
List arguments = sort(node.getArguments(), comparing(Argument::getName));
builder.arguments(arguments);
});
return changeNode(context, changedNode);
}
@Override
public TraversalControl visitObjectValue(ObjectValue node, TraverserContext context) {
ObjectValue changedNode = node.transform(builder -> {
List objectFields = sort(node.getObjectFields(), comparing(ObjectField::getName));
builder.objectFields(objectFields);
});
return changeNode(context, changedNode);
}
// SDL classes here
@Override
public TraversalControl visitSchemaDefinition(SchemaDefinition node, TraverserContext context) {
SchemaDefinition changedNode = node.transform(builder -> {
builder.directives(sort(node.getDirectives(), comparing(Directive::getName)));
builder.operationTypeDefinitions(sort(node.getOperationTypeDefinitions(), comparing(OperationTypeDefinition::getName)));
});
return changeNode(context, changedNode);
}
@Override
public TraversalControl visitEnumTypeDefinition(EnumTypeDefinition node, TraverserContext context) {
EnumTypeDefinition changedNode = node.transform(builder -> {
builder.directives(sort(node.getDirectives(), comparing(Directive::getName)));
builder.enumValueDefinitions(sort(node.getEnumValueDefinitions(), comparing(EnumValueDefinition::getName)));
});
return changeNode(context, changedNode);
}
@Override
public TraversalControl visitScalarTypeDefinition(ScalarTypeDefinition node, TraverserContext context) {
ScalarTypeDefinition changedNode = node.transform(builder -> {
List directives = sort(node.getDirectives(), comparing(Directive::getName));
builder.directives(directives);
});
return changeNode(context, changedNode);
}
@Override
public TraversalControl visitInputObjectTypeDefinition(InputObjectTypeDefinition node, TraverserContext context) {
InputObjectTypeDefinition changedNode = node.transform(builder -> {
builder.directives(sort(node.getDirectives(), comparing(Directive::getName)));
builder.inputValueDefinitions(sort(node.getInputValueDefinitions(), comparing(InputValueDefinition::getName)));
});
return changeNode(context, changedNode);
}
@Override
public TraversalControl visitObjectTypeDefinition(ObjectTypeDefinition node, TraverserContext context) {
ObjectTypeDefinition changedNode = node.transform(builder -> {
builder.directives(sort(node.getDirectives(), comparing(Directive::getName)));
builder.implementz(sort(node.getImplements(), comparingTypes()));
builder.fieldDefinitions(sort(node.getFieldDefinitions(), comparing(FieldDefinition::getName)));
});
return changeNode(context, changedNode);
}
@Override
public TraversalControl visitInterfaceTypeDefinition(InterfaceTypeDefinition node, TraverserContext context) {
InterfaceTypeDefinition changedNode = node.transform(builder -> {
builder.directives(sort(node.getDirectives(), comparing(Directive::getName)));
builder.implementz(sort(node.getImplements(), comparingTypes()));
builder.definitions(sort(node.getFieldDefinitions(), comparing(FieldDefinition::getName)));
});
return changeNode(context, changedNode);
}
@Override
public TraversalControl visitUnionTypeDefinition(UnionTypeDefinition node, TraverserContext context) {
UnionTypeDefinition changedNode = node.transform(builder -> {
builder.directives(sort(node.getDirectives(), comparing(Directive::getName)));
builder.memberTypes(sort(node.getMemberTypes(), comparingTypes()));
});
return changeNode(context, changedNode);
}
@Override
public TraversalControl visitFieldDefinition(FieldDefinition node, TraverserContext context) {
FieldDefinition changedNode = node.transform(builder -> {
builder.directives(sort(node.getDirectives(), comparing(Directive::getName)));
builder.inputValueDefinitions(sort(node.getInputValueDefinitions(), comparing(InputValueDefinition::getName)));
});
return changeNode(context, changedNode);
}
@Override
public TraversalControl visitInputValueDefinition(InputValueDefinition node, TraverserContext context) {
InputValueDefinition changedNode = node.transform(builder -> {
List directives = sort(node.getDirectives(), comparing(Directive::getName));
builder.directives(directives);
});
return changeNode(context, changedNode);
}
@Override
public TraversalControl visitDirectiveDefinition(DirectiveDefinition node, TraverserContext context) {
DirectiveDefinition changedNode = node.transform(builder -> {
builder.inputValueDefinitions(sort(node.getInputValueDefinitions(), comparing(InputValueDefinition::getName)));
builder.directiveLocations(sort(node.getDirectiveLocations(), comparing(DirectiveLocation::getName)));
});
return changeNode(context, changedNode);
}
};
AstTransformer astTransformer = new AstTransformer();
Node newDoc = astTransformer.transform(nodeToBeSorted, visitor);
//noinspection unchecked
return (T) newDoc;
}
private Comparator comparingTypes() {
return comparing(type -> TypeInfo.typeInfo(type).getName());
}
private Comparator comparingSelections() {
Function byName = s -> {
if (s instanceof FragmentSpread) {
return ((FragmentSpread) s).getName();
}
if (s instanceof Field) {
return ((Field) s).getName();
}
if (s instanceof InlineFragment) {
TypeName typeCondition = ((InlineFragment) s).getTypeCondition();
return typeCondition == null ? "" : typeCondition.getName();
}
return "";
};
Function byType = s -> {
if (s instanceof Field) {
return 1;
}
if (s instanceof FragmentSpread) {
return 2;
}
if (s instanceof InlineFragment) {
return 3;
}
return 4;
};
return comparing(byType).thenComparing(comparing(byName));
}
private Comparator comparingDefinitions() {
Function byName = d -> {
if (d instanceof OperationDefinition) {
String name = ((OperationDefinition) d).getName();
return name == null ? "" : name;
}
if (d instanceof FragmentDefinition) {
return ((FragmentDefinition) d).getName();
}
if (d instanceof DirectiveDefinition) {
return ((DirectiveDefinition) d).getName();
}
if (d instanceof TypeDefinition) {
return ((TypeDefinition) d).getName();
}
return "";
};
Function byType = d -> {
if (d instanceof OperationDefinition) {
OperationDefinition.Operation operation = ((OperationDefinition) d).getOperation();
if (OperationDefinition.Operation.QUERY == operation || operation == null) {
return 101;
}
if (OperationDefinition.Operation.MUTATION == operation) {
return 102;
}
if (OperationDefinition.Operation.SUBSCRIPTION == operation) {
return 104;
}
return 100;
}
if (d instanceof FragmentDefinition) {
return 200;
}
// SDL
if (d instanceof DirectiveDefinition) {
return 300;
}
if (d instanceof SchemaDefinition) {
return 400;
}
if (d instanceof TypeDefinition) {
if (d instanceof ObjectTypeDefinition) {
return 501;
}
if (d instanceof InterfaceTypeDefinition) {
return 502;
}
if (d instanceof UnionTypeDefinition) {
return 503;
}
if (d instanceof EnumTypeDefinition) {
return 504;
}
if (d instanceof ScalarTypeDefinition) {
return 505;
}
if (d instanceof InputObjectTypeDefinition) {
return 506;
}
return 500;
}
return -1;
};
return comparing(byType).thenComparing(byName);
}
private SelectionSet sortSelectionSet(SelectionSet selectionSet) {
if (selectionSet == null) {
return null;
}
List selections = sort(selectionSet.getSelections(), comparingSelections());
return selectionSet.transform(builder -> builder.selections(selections));
}
private List sort(List items, Comparator comparing) {
items = new ArrayList<>(items);
items.sort(comparing);
return items;
}
private > Comparator comparing(
Function super T, ? extends U> keyExtractor) {
return Comparator.comparing(keyExtractor, nullsLast(naturalOrder()));
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy