Skip to content

Commit

Permalink
Add GetDirectiveLocation extension method (#396)
Browse files Browse the repository at this point in the history
* Add GetDirectiveLocation extension method

* Update
  • Loading branch information
Shane32 authored Aug 20, 2024
1 parent a440946 commit 9faa2bd
Show file tree
Hide file tree
Showing 7 changed files with 278 additions and 13 deletions.
11 changes: 11 additions & 0 deletions src/GraphQLParser.ApiTests/GraphQLParser.approved.txt
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ namespace GraphQLParser.AST
public GraphQLParser.AST.GraphQLName Name { get; set; }
public GraphQLParser.AST.GraphQLValue Value { get; set; }
}
public class GraphQLArgumentDefinition : GraphQLParser.AST.GraphQLInputValueDefinition
{
public GraphQLArgumentDefinition(GraphQLParser.AST.GraphQLName name, GraphQLParser.AST.GraphQLType type) { }
}
public class GraphQLArguments : GraphQLParser.AST.ASTListNode<GraphQLParser.AST.GraphQLArgument>
{
public GraphQLArguments(System.Collections.Generic.List<GraphQLParser.AST.GraphQLArgument> items) { }
Expand Down Expand Up @@ -304,6 +308,10 @@ namespace GraphQLParser.AST
public GraphQLParser.AST.GraphQLSelectionSet SelectionSet { get; set; }
public GraphQLParser.AST.GraphQLTypeCondition? TypeCondition { get; set; }
}
public class GraphQLInputFieldDefinition : GraphQLParser.AST.GraphQLInputValueDefinition
{
public GraphQLInputFieldDefinition(GraphQLParser.AST.GraphQLName name, GraphQLParser.AST.GraphQLType type) { }
}
public class GraphQLInputFieldsDefinition : GraphQLParser.AST.ASTListNode<GraphQLParser.AST.GraphQLInputValueDefinition>
{
public GraphQLInputFieldsDefinition(System.Collections.Generic.List<GraphQLParser.AST.GraphQLInputValueDefinition> items) { }
Expand All @@ -325,6 +333,8 @@ namespace GraphQLParser.AST
}
public class GraphQLInputValueDefinition : GraphQLParser.AST.GraphQLTypeDefinition, GraphQLParser.AST.IHasDefaultValueNode, GraphQLParser.AST.IHasDirectivesNode
{
[System.Obsolete("Please use the GraphQLArgumentDefinition or GraphQLInputFieldDefinition construct" +
"or.")]
public GraphQLInputValueDefinition(GraphQLParser.AST.GraphQLName name, GraphQLParser.AST.GraphQLType type) { }
public GraphQLParser.AST.GraphQLValue? DefaultValue { get; set; }
public GraphQLParser.AST.GraphQLDirectives? Directives { get; set; }
Expand Down Expand Up @@ -633,6 +643,7 @@ namespace GraphQLParser
where TNode : class, GraphQLParser.AST.INamedNode { }
public static GraphQLParser.AST.GraphQLFragmentDefinition? FindFragmentDefinition(this GraphQLParser.AST.GraphQLDocument document, GraphQLParser.ROM name) { }
public static int FragmentsCount(this GraphQLParser.AST.GraphQLDocument document) { }
public static GraphQLParser.AST.DirectiveLocation GetDirectiveLocation(this GraphQLParser.AST.ASTNode node) { }
public static int MaxNestedDepth(this GraphQLParser.AST.ASTNode node) { }
public static GraphQLParser.AST.GraphQLOperationDefinition? OperationWithName(this GraphQLParser.AST.GraphQLDocument document, GraphQLParser.ROM operationName) { }
public static int OperationsCount(this GraphQLParser.AST.GraphQLDocument document) { }
Expand Down
86 changes: 86 additions & 0 deletions src/GraphQLParser.Tests/GetDirectiveLocationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using GraphQLParser.Visitors;

namespace GraphQLParser.Tests;

public class GetDirectiveLocationTests
{
[Fact]
public async Task ItWorks()
{
var sdl = $$"""
schema @test(value: "{{DirectiveLocation.Schema}}") {
query: Query
}

type Query @test(value: "{{DirectiveLocation.Object}}") {
field(arg:String @test(value:"{{DirectiveLocation.ArgumentDefinition}}")): String @test(value: "{{DirectiveLocation.FieldDefinition}}")
}

scalar CustomScalar @test(value: "{{DirectiveLocation.Scalar}}")

interface CustomInterface @test(value: "{{DirectiveLocation.Interface}}") {
field: String @test(value: "{{DirectiveLocation.FieldDefinition}}")
}

union CustomUnion @test(value: "{{DirectiveLocation.Union}}") = A | B

enum CustomEnum @test(value: "{{DirectiveLocation.Enum}}") {
A @test(value: "{{DirectiveLocation.EnumValue}}")
}

input CustomInput @test(value: "{{DirectiveLocation.InputObject}}") {
field: String @test(value: "{{DirectiveLocation.InputFieldDefinition}}")
}

query Query @test(value: "{{DirectiveLocation.Query}}") {
field @test(value: "{{DirectiveLocation.Field}}")
...fragment1 @test(value: "{{DirectiveLocation.FragmentSpread}}")
... on CustomType @test(value: "{{DirectiveLocation.InlineFragment}}") {
field @test(value: "{{DirectiveLocation.Field}}")
}
}

fragment fragment1 on CustomType @test(value: "{{DirectiveLocation.FragmentDefinition}}") {
field @test(value: "{{DirectiveLocation.Field}}")
}

mutation($arg: String @test(value: "{{DirectiveLocation.VariableDefinition}}")) @test(value: "{{DirectiveLocation.Mutation}}") {
field @test(value: "{{DirectiveLocation.Field}}")
}

subscription @test(value: "{{DirectiveLocation.Subscription}}") {
field @test(value: "{{DirectiveLocation.Field}}")
}
""";
var ast = Parser.Parse(sdl);
var context = new MyVisitor.MyContext();
await new MyVisitor().VisitAsync(ast, context);
context.Count.ShouldBe(24);
}

private sealed class MyVisitor : ASTVisitor<MyVisitor.MyContext>
{
public override ValueTask VisitAsync(ASTNode node, MyContext context)
{
if (node is IHasDirectivesNode directivesNode)
{
var d = directivesNode.Directives?.FirstOrDefault(x => x.Name == "test");
if (d != null)
{
var arg = d?.Arguments?.FirstOrDefault(x => x.Name == "value")?.Value;
var argValue = (arg as GraphQLStringValue)?.Value;
var location = node.GetDirectiveLocation();
location.ToString().ShouldBe(argValue?.ToString());
context.Count++;
}
}
return base.VisitAsync(node, context);
}

internal sealed class MyContext : IASTVisitorContext
{
public int Count { get; set; }
public CancellationToken CancellationToken => default;
}
}
}
115 changes: 115 additions & 0 deletions src/GraphQLParser/AST/Definitions/GraphQLInputValueDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ internal GraphQLInputValueDefinition()
/// <summary>
/// Creates a new instance of <see cref="GraphQLInputValueDefinition"/>.
/// </summary>
[Obsolete($"Please use the {nameof(GraphQLArgumentDefinition)} or {nameof(GraphQLInputFieldDefinition)} constructor.")]
public GraphQLInputValueDefinition(GraphQLName name, GraphQLType type)
: base(name)
{
Expand Down Expand Up @@ -79,3 +80,117 @@ public override List<GraphQLComment>? Comments
set => _comments = value;
}
}

/// <summary>
/// AST node for <see cref="ASTNodeKind.InputValueDefinition"/>, where it is used as an argument definition.
/// </summary>
[DebuggerDisplay("GraphQLArgumentDefinition: {Name}: {Type}")]
public class GraphQLArgumentDefinition : GraphQLInputValueDefinition
{
internal GraphQLArgumentDefinition() : base() { }

/// <summary>
/// Creates a new instance of <see cref="GraphQLArgumentDefinition"/>.
/// </summary>
public GraphQLArgumentDefinition(GraphQLName name, GraphQLType type)
#pragma warning disable CS0618 // Type or member is obsolete
: base(name, type) { }
#pragma warning restore CS0618 // Type or member is obsolete
}

internal sealed class GraphQLArgumentDefinitionWithLocation : GraphQLArgumentDefinition
{
private GraphQLLocation _location;

public override GraphQLLocation Location
{
get => _location;
set => _location = value;
}
}

internal sealed class GraphQLArgumentDefinitionWithComment : GraphQLArgumentDefinition
{
private List<GraphQLComment>? _comments;

public override List<GraphQLComment>? Comments
{
get => _comments;
set => _comments = value;
}
}

internal sealed class GraphQLArgumentDefinitionFull : GraphQLArgumentDefinition
{
private GraphQLLocation _location;
private List<GraphQLComment>? _comments;

public override GraphQLLocation Location
{
get => _location;
set => _location = value;
}

public override List<GraphQLComment>? Comments
{
get => _comments;
set => _comments = value;
}
}

/// <summary>
/// AST node for <see cref="ASTNodeKind.InputValueDefinition"/>, where it is used as an input field definition.
/// </summary>
[DebuggerDisplay("GraphQLInputFieldDefinition: {Name}: {Type}")]
public class GraphQLInputFieldDefinition : GraphQLInputValueDefinition
{
internal GraphQLInputFieldDefinition() : base() { }

/// <summary>
/// Creates a new instance of <see cref="GraphQLInputFieldDefinition"/>.
/// </summary>
public GraphQLInputFieldDefinition(GraphQLName name, GraphQLType type)
#pragma warning disable CS0618 // Type or member is obsolete
: base(name, type) { }
#pragma warning restore CS0618 // Type or member is obsolete
}

internal sealed class GraphQLInputFieldDefinitionWithLocation : GraphQLInputFieldDefinition
{
private GraphQLLocation _location;

public override GraphQLLocation Location
{
get => _location;
set => _location = value;
}
}

internal sealed class GraphQLInputFieldDefinitionWithComment : GraphQLInputFieldDefinition
{
private List<GraphQLComment>? _comments;

public override List<GraphQLComment>? Comments
{
get => _comments;
set => _comments = value;
}
}

internal sealed class GraphQLInputFieldDefinitionFull : GraphQLInputFieldDefinition
{
private GraphQLLocation _location;
private List<GraphQLComment>? _comments;

public override GraphQLLocation Location
{
get => _location;
set => _location = value;
}

public override List<GraphQLComment>? Comments
{
get => _comments;
set => _comments = value;
}
}
32 changes: 32 additions & 0 deletions src/GraphQLParser/Extensions/ASTNodeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,36 @@ public static int FragmentsCount(this GraphQLDocument document)

return null;
}

/// <summary>
/// Returns the directive location for the specified AST node.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException"/>
public static DirectiveLocation GetDirectiveLocation(this ASTNode node) => node switch
{
// type definitions
GraphQLSchemaDefinition => DirectiveLocation.Schema,
GraphQLScalarTypeDefinition => DirectiveLocation.Scalar,
GraphQLObjectTypeDefinition => DirectiveLocation.Object,
GraphQLFieldDefinition => DirectiveLocation.FieldDefinition,
GraphQLArgumentDefinition => DirectiveLocation.ArgumentDefinition,
GraphQLInterfaceTypeDefinition => DirectiveLocation.Interface,
GraphQLUnionTypeDefinition => DirectiveLocation.Union,
GraphQLEnumTypeDefinition => DirectiveLocation.Enum,
GraphQLEnumValueDefinition => DirectiveLocation.EnumValue,
GraphQLInputObjectTypeDefinition => DirectiveLocation.InputObject,
GraphQLInputFieldDefinition => DirectiveLocation.InputFieldDefinition,

// executable definitions
GraphQLOperationDefinition opDef when opDef.Operation == OperationType.Query => DirectiveLocation.Query,
GraphQLOperationDefinition opDef when opDef.Operation == OperationType.Mutation => DirectiveLocation.Mutation,
GraphQLOperationDefinition opDef when opDef.Operation == OperationType.Subscription => DirectiveLocation.Subscription,
GraphQLField => DirectiveLocation.Field,
GraphQLFragmentDefinition => DirectiveLocation.FragmentDefinition,
GraphQLFragmentSpread => DirectiveLocation.FragmentSpread,
GraphQLInlineFragment => DirectiveLocation.InlineFragment,
GraphQLVariableDefinition => DirectiveLocation.VariableDefinition,

_ => throw new ArgumentOutOfRangeException(nameof(node), "The supplied node cannot")
};
}
33 changes: 25 additions & 8 deletions src/GraphQLParser/NodeHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -483,15 +483,32 @@ public static GraphQLObjectValue CreateGraphQLObjectValue(IgnoreOptions options)
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static GraphQLInputValueDefinition CreateGraphQLInputValueDefinition(IgnoreOptions options)
public static GraphQLInputValueDefinition CreateGraphQLInputValueDefinition(IgnoreOptions options, bool? argument)
{
return options switch
{
IgnoreOptions.All => new GraphQLInputValueDefinition(),
IgnoreOptions.Comments => new GraphQLInputValueDefinitionWithLocation(),
IgnoreOptions.Locations => new GraphQLInputValueDefinitionWithComment(),
_ => new GraphQLInputValueDefinitionFull(),
};
if (argument == true)
return options switch
{
IgnoreOptions.All => new GraphQLArgumentDefinition(),
IgnoreOptions.Comments => new GraphQLArgumentDefinitionWithLocation(),
IgnoreOptions.Locations => new GraphQLArgumentDefinitionWithComment(),
_ => new GraphQLArgumentDefinitionFull(),
};
else if (argument == false)
return options switch
{
IgnoreOptions.All => new GraphQLInputFieldDefinition(),
IgnoreOptions.Comments => new GraphQLInputFieldDefinitionWithLocation(),
IgnoreOptions.Locations => new GraphQLInputFieldDefinitionWithComment(),
_ => new GraphQLInputFieldDefinitionFull(),
};
else
return options switch
{
IgnoreOptions.All => new GraphQLInputValueDefinition(),
IgnoreOptions.Comments => new GraphQLInputValueDefinitionWithLocation(),
IgnoreOptions.Locations => new GraphQLInputValueDefinitionWithComment(),
_ => new GraphQLInputValueDefinitionFull(),
};
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down
6 changes: 5 additions & 1 deletion src/GraphQLParser/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,11 @@ public static T Parse<T>(ROM source, ParserOptions options = default)
else if (typeof(T) == typeof(GraphQLInputObjectTypeDefinition))
result = (T)(object)context.ParseInputObjectTypeDefinition();
else if (typeof(T) == typeof(GraphQLInputValueDefinition))
result = (T)(object)context.ParseInputValueDefinition();
result = (T)(object)context.ParseInputValueDefinition(null);
else if (typeof(T) == typeof(GraphQLInputFieldDefinition))
result = (T)(object)context.ParseInputValueDefinition(false);
else if (typeof(T) == typeof(GraphQLArgumentDefinition))
result = (T)(object)context.ParseInputValueDefinition(true);
else if (typeof(T) == typeof(GraphQLInterfaceTypeDefinition))
result = (T)(object)context.ParseInterfaceTypeDefinition();
else if (typeof(T) == typeof(GraphQLObjectTypeDefinition))
Expand Down
8 changes: 4 additions & 4 deletions src/GraphQLParser/ParserContext.Parse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public GraphQLArgumentsDefinition ParseArgumentsDefinition()
var argsDef = NodeHelper.CreateGraphQLArgumentsDefinition(_ignoreOptions);

argsDef.Comments = GetComments();
argsDef.Items = OneOrMore(TokenKind.PAREN_L, (ref ParserContext context) => context.ParseInputValueDefinition(), TokenKind.PAREN_R);
argsDef.Items = OneOrMore(TokenKind.PAREN_L, (ref ParserContext context) => context.ParseInputValueDefinition(true), TokenKind.PAREN_R);
argsDef.Location = GetLocation(start);

DecreaseDepth();
Expand All @@ -114,7 +114,7 @@ public GraphQLInputFieldsDefinition ParseInputFieldsDefinition()
var inputFieldsDef = NodeHelper.CreateGraphQLInputFieldsDefinition(_ignoreOptions);

inputFieldsDef.Comments = GetComments();
inputFieldsDef.Items = OneOrMore(TokenKind.BRACE_L, (ref ParserContext context) => context.ParseInputValueDefinition(), TokenKind.BRACE_R);
inputFieldsDef.Items = OneOrMore(TokenKind.BRACE_L, (ref ParserContext context) => context.ParseInputValueDefinition(false), TokenKind.BRACE_R);
inputFieldsDef.Location = GetLocation(start);

DecreaseDepth();
Expand Down Expand Up @@ -735,13 +735,13 @@ private GraphQLInputObjectTypeExtension ParseInputObjectTypeExtension(int start,
}

// http://spec.graphql.org/October2021/#InputValueDefinition
public GraphQLInputValueDefinition ParseInputValueDefinition()
public GraphQLInputValueDefinition ParseInputValueDefinition(bool? argument)
{
IncreaseDepth();

int start = _currentToken.Start;

var def = NodeHelper.CreateGraphQLInputValueDefinition(_ignoreOptions);
var def = NodeHelper.CreateGraphQLInputValueDefinition(_ignoreOptions, argument);

def.Description = Peek(TokenKind.STRING) ? ParseDescription() : null;
def.Comments = GetComments();
Expand Down

0 comments on commit 9faa2bd

Please sign in to comment.