Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add GetDirectiveLocation extension method #396

Merged
merged 2 commits into from
Aug 20, 2024
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
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);
Dismissed Show dismissed Hide dismissed
else if (typeof(T) == typeof(GraphQLInputFieldDefinition))
result = (T)(object)context.ParseInputValueDefinition(false);
Dismissed Show dismissed Hide dismissed
else if (typeof(T) == typeof(GraphQLArgumentDefinition))
result = (T)(object)context.ParseInputValueDefinition(true);
Dismissed Show dismissed Hide dismissed
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
Loading