Skip to content

Commit 9faa2bd

Browse files
authored
Add GetDirectiveLocation extension method (#396)
* Add GetDirectiveLocation extension method * Update
1 parent a440946 commit 9faa2bd

File tree

7 files changed

+278
-13
lines changed

7 files changed

+278
-13
lines changed

src/GraphQLParser.ApiTests/GraphQLParser.approved.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ namespace GraphQLParser.AST
130130
public GraphQLParser.AST.GraphQLName Name { get; set; }
131131
public GraphQLParser.AST.GraphQLValue Value { get; set; }
132132
}
133+
public class GraphQLArgumentDefinition : GraphQLParser.AST.GraphQLInputValueDefinition
134+
{
135+
public GraphQLArgumentDefinition(GraphQLParser.AST.GraphQLName name, GraphQLParser.AST.GraphQLType type) { }
136+
}
133137
public class GraphQLArguments : GraphQLParser.AST.ASTListNode<GraphQLParser.AST.GraphQLArgument>
134138
{
135139
public GraphQLArguments(System.Collections.Generic.List<GraphQLParser.AST.GraphQLArgument> items) { }
@@ -304,6 +308,10 @@ namespace GraphQLParser.AST
304308
public GraphQLParser.AST.GraphQLSelectionSet SelectionSet { get; set; }
305309
public GraphQLParser.AST.GraphQLTypeCondition? TypeCondition { get; set; }
306310
}
311+
public class GraphQLInputFieldDefinition : GraphQLParser.AST.GraphQLInputValueDefinition
312+
{
313+
public GraphQLInputFieldDefinition(GraphQLParser.AST.GraphQLName name, GraphQLParser.AST.GraphQLType type) { }
314+
}
307315
public class GraphQLInputFieldsDefinition : GraphQLParser.AST.ASTListNode<GraphQLParser.AST.GraphQLInputValueDefinition>
308316
{
309317
public GraphQLInputFieldsDefinition(System.Collections.Generic.List<GraphQLParser.AST.GraphQLInputValueDefinition> items) { }
@@ -325,6 +333,8 @@ namespace GraphQLParser.AST
325333
}
326334
public class GraphQLInputValueDefinition : GraphQLParser.AST.GraphQLTypeDefinition, GraphQLParser.AST.IHasDefaultValueNode, GraphQLParser.AST.IHasDirectivesNode
327335
{
336+
[System.Obsolete("Please use the GraphQLArgumentDefinition or GraphQLInputFieldDefinition construct" +
337+
"or.")]
328338
public GraphQLInputValueDefinition(GraphQLParser.AST.GraphQLName name, GraphQLParser.AST.GraphQLType type) { }
329339
public GraphQLParser.AST.GraphQLValue? DefaultValue { get; set; }
330340
public GraphQLParser.AST.GraphQLDirectives? Directives { get; set; }
@@ -633,6 +643,7 @@ namespace GraphQLParser
633643
where TNode : class, GraphQLParser.AST.INamedNode { }
634644
public static GraphQLParser.AST.GraphQLFragmentDefinition? FindFragmentDefinition(this GraphQLParser.AST.GraphQLDocument document, GraphQLParser.ROM name) { }
635645
public static int FragmentsCount(this GraphQLParser.AST.GraphQLDocument document) { }
646+
public static GraphQLParser.AST.DirectiveLocation GetDirectiveLocation(this GraphQLParser.AST.ASTNode node) { }
636647
public static int MaxNestedDepth(this GraphQLParser.AST.ASTNode node) { }
637648
public static GraphQLParser.AST.GraphQLOperationDefinition? OperationWithName(this GraphQLParser.AST.GraphQLDocument document, GraphQLParser.ROM operationName) { }
638649
public static int OperationsCount(this GraphQLParser.AST.GraphQLDocument document) { }
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
using GraphQLParser.Visitors;
2+
3+
namespace GraphQLParser.Tests;
4+
5+
public class GetDirectiveLocationTests
6+
{
7+
[Fact]
8+
public async Task ItWorks()
9+
{
10+
var sdl = $$"""
11+
schema @test(value: "{{DirectiveLocation.Schema}}") {
12+
query: Query
13+
}
14+
15+
type Query @test(value: "{{DirectiveLocation.Object}}") {
16+
field(arg:String @test(value:"{{DirectiveLocation.ArgumentDefinition}}")): String @test(value: "{{DirectiveLocation.FieldDefinition}}")
17+
}
18+
19+
scalar CustomScalar @test(value: "{{DirectiveLocation.Scalar}}")
20+
21+
interface CustomInterface @test(value: "{{DirectiveLocation.Interface}}") {
22+
field: String @test(value: "{{DirectiveLocation.FieldDefinition}}")
23+
}
24+
25+
union CustomUnion @test(value: "{{DirectiveLocation.Union}}") = A | B
26+
27+
enum CustomEnum @test(value: "{{DirectiveLocation.Enum}}") {
28+
A @test(value: "{{DirectiveLocation.EnumValue}}")
29+
}
30+
31+
input CustomInput @test(value: "{{DirectiveLocation.InputObject}}") {
32+
field: String @test(value: "{{DirectiveLocation.InputFieldDefinition}}")
33+
}
34+
35+
query Query @test(value: "{{DirectiveLocation.Query}}") {
36+
field @test(value: "{{DirectiveLocation.Field}}")
37+
...fragment1 @test(value: "{{DirectiveLocation.FragmentSpread}}")
38+
... on CustomType @test(value: "{{DirectiveLocation.InlineFragment}}") {
39+
field @test(value: "{{DirectiveLocation.Field}}")
40+
}
41+
}
42+
43+
fragment fragment1 on CustomType @test(value: "{{DirectiveLocation.FragmentDefinition}}") {
44+
field @test(value: "{{DirectiveLocation.Field}}")
45+
}
46+
47+
mutation($arg: String @test(value: "{{DirectiveLocation.VariableDefinition}}")) @test(value: "{{DirectiveLocation.Mutation}}") {
48+
field @test(value: "{{DirectiveLocation.Field}}")
49+
}
50+
51+
subscription @test(value: "{{DirectiveLocation.Subscription}}") {
52+
field @test(value: "{{DirectiveLocation.Field}}")
53+
}
54+
""";
55+
var ast = Parser.Parse(sdl);
56+
var context = new MyVisitor.MyContext();
57+
await new MyVisitor().VisitAsync(ast, context);
58+
context.Count.ShouldBe(24);
59+
}
60+
61+
private sealed class MyVisitor : ASTVisitor<MyVisitor.MyContext>
62+
{
63+
public override ValueTask VisitAsync(ASTNode node, MyContext context)
64+
{
65+
if (node is IHasDirectivesNode directivesNode)
66+
{
67+
var d = directivesNode.Directives?.FirstOrDefault(x => x.Name == "test");
68+
if (d != null)
69+
{
70+
var arg = d?.Arguments?.FirstOrDefault(x => x.Name == "value")?.Value;
71+
var argValue = (arg as GraphQLStringValue)?.Value;
72+
var location = node.GetDirectiveLocation();
73+
location.ToString().ShouldBe(argValue?.ToString());
74+
context.Count++;
75+
}
76+
}
77+
return base.VisitAsync(node, context);
78+
}
79+
80+
internal sealed class MyContext : IASTVisitorContext
81+
{
82+
public int Count { get; set; }
83+
public CancellationToken CancellationToken => default;
84+
}
85+
}
86+
}

src/GraphQLParser/AST/Definitions/GraphQLInputValueDefinition.cs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ internal GraphQLInputValueDefinition()
1616
/// <summary>
1717
/// Creates a new instance of <see cref="GraphQLInputValueDefinition"/>.
1818
/// </summary>
19+
[Obsolete($"Please use the {nameof(GraphQLArgumentDefinition)} or {nameof(GraphQLInputFieldDefinition)} constructor.")]
1920
public GraphQLInputValueDefinition(GraphQLName name, GraphQLType type)
2021
: base(name)
2122
{
@@ -79,3 +80,117 @@ public override List<GraphQLComment>? Comments
7980
set => _comments = value;
8081
}
8182
}
83+
84+
/// <summary>
85+
/// AST node for <see cref="ASTNodeKind.InputValueDefinition"/>, where it is used as an argument definition.
86+
/// </summary>
87+
[DebuggerDisplay("GraphQLArgumentDefinition: {Name}: {Type}")]
88+
public class GraphQLArgumentDefinition : GraphQLInputValueDefinition
89+
{
90+
internal GraphQLArgumentDefinition() : base() { }
91+
92+
/// <summary>
93+
/// Creates a new instance of <see cref="GraphQLArgumentDefinition"/>.
94+
/// </summary>
95+
public GraphQLArgumentDefinition(GraphQLName name, GraphQLType type)
96+
#pragma warning disable CS0618 // Type or member is obsolete
97+
: base(name, type) { }
98+
#pragma warning restore CS0618 // Type or member is obsolete
99+
}
100+
101+
internal sealed class GraphQLArgumentDefinitionWithLocation : GraphQLArgumentDefinition
102+
{
103+
private GraphQLLocation _location;
104+
105+
public override GraphQLLocation Location
106+
{
107+
get => _location;
108+
set => _location = value;
109+
}
110+
}
111+
112+
internal sealed class GraphQLArgumentDefinitionWithComment : GraphQLArgumentDefinition
113+
{
114+
private List<GraphQLComment>? _comments;
115+
116+
public override List<GraphQLComment>? Comments
117+
{
118+
get => _comments;
119+
set => _comments = value;
120+
}
121+
}
122+
123+
internal sealed class GraphQLArgumentDefinitionFull : GraphQLArgumentDefinition
124+
{
125+
private GraphQLLocation _location;
126+
private List<GraphQLComment>? _comments;
127+
128+
public override GraphQLLocation Location
129+
{
130+
get => _location;
131+
set => _location = value;
132+
}
133+
134+
public override List<GraphQLComment>? Comments
135+
{
136+
get => _comments;
137+
set => _comments = value;
138+
}
139+
}
140+
141+
/// <summary>
142+
/// AST node for <see cref="ASTNodeKind.InputValueDefinition"/>, where it is used as an input field definition.
143+
/// </summary>
144+
[DebuggerDisplay("GraphQLInputFieldDefinition: {Name}: {Type}")]
145+
public class GraphQLInputFieldDefinition : GraphQLInputValueDefinition
146+
{
147+
internal GraphQLInputFieldDefinition() : base() { }
148+
149+
/// <summary>
150+
/// Creates a new instance of <see cref="GraphQLInputFieldDefinition"/>.
151+
/// </summary>
152+
public GraphQLInputFieldDefinition(GraphQLName name, GraphQLType type)
153+
#pragma warning disable CS0618 // Type or member is obsolete
154+
: base(name, type) { }
155+
#pragma warning restore CS0618 // Type or member is obsolete
156+
}
157+
158+
internal sealed class GraphQLInputFieldDefinitionWithLocation : GraphQLInputFieldDefinition
159+
{
160+
private GraphQLLocation _location;
161+
162+
public override GraphQLLocation Location
163+
{
164+
get => _location;
165+
set => _location = value;
166+
}
167+
}
168+
169+
internal sealed class GraphQLInputFieldDefinitionWithComment : GraphQLInputFieldDefinition
170+
{
171+
private List<GraphQLComment>? _comments;
172+
173+
public override List<GraphQLComment>? Comments
174+
{
175+
get => _comments;
176+
set => _comments = value;
177+
}
178+
}
179+
180+
internal sealed class GraphQLInputFieldDefinitionFull : GraphQLInputFieldDefinition
181+
{
182+
private GraphQLLocation _location;
183+
private List<GraphQLComment>? _comments;
184+
185+
public override GraphQLLocation Location
186+
{
187+
get => _location;
188+
set => _location = value;
189+
}
190+
191+
public override List<GraphQLComment>? Comments
192+
{
193+
get => _comments;
194+
set => _comments = value;
195+
}
196+
}

src/GraphQLParser/Extensions/ASTNodeExtensions.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,4 +132,36 @@ public static int FragmentsCount(this GraphQLDocument document)
132132

133133
return null;
134134
}
135+
136+
/// <summary>
137+
/// Returns the directive location for the specified AST node.
138+
/// </summary>
139+
/// <exception cref="ArgumentOutOfRangeException"/>
140+
public static DirectiveLocation GetDirectiveLocation(this ASTNode node) => node switch
141+
{
142+
// type definitions
143+
GraphQLSchemaDefinition => DirectiveLocation.Schema,
144+
GraphQLScalarTypeDefinition => DirectiveLocation.Scalar,
145+
GraphQLObjectTypeDefinition => DirectiveLocation.Object,
146+
GraphQLFieldDefinition => DirectiveLocation.FieldDefinition,
147+
GraphQLArgumentDefinition => DirectiveLocation.ArgumentDefinition,
148+
GraphQLInterfaceTypeDefinition => DirectiveLocation.Interface,
149+
GraphQLUnionTypeDefinition => DirectiveLocation.Union,
150+
GraphQLEnumTypeDefinition => DirectiveLocation.Enum,
151+
GraphQLEnumValueDefinition => DirectiveLocation.EnumValue,
152+
GraphQLInputObjectTypeDefinition => DirectiveLocation.InputObject,
153+
GraphQLInputFieldDefinition => DirectiveLocation.InputFieldDefinition,
154+
155+
// executable definitions
156+
GraphQLOperationDefinition opDef when opDef.Operation == OperationType.Query => DirectiveLocation.Query,
157+
GraphQLOperationDefinition opDef when opDef.Operation == OperationType.Mutation => DirectiveLocation.Mutation,
158+
GraphQLOperationDefinition opDef when opDef.Operation == OperationType.Subscription => DirectiveLocation.Subscription,
159+
GraphQLField => DirectiveLocation.Field,
160+
GraphQLFragmentDefinition => DirectiveLocation.FragmentDefinition,
161+
GraphQLFragmentSpread => DirectiveLocation.FragmentSpread,
162+
GraphQLInlineFragment => DirectiveLocation.InlineFragment,
163+
GraphQLVariableDefinition => DirectiveLocation.VariableDefinition,
164+
165+
_ => throw new ArgumentOutOfRangeException(nameof(node), "The supplied node cannot")
166+
};
135167
}

src/GraphQLParser/NodeHelper.cs

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -483,15 +483,32 @@ public static GraphQLObjectValue CreateGraphQLObjectValue(IgnoreOptions options)
483483
}
484484

485485
[MethodImpl(MethodImplOptions.AggressiveInlining)]
486-
public static GraphQLInputValueDefinition CreateGraphQLInputValueDefinition(IgnoreOptions options)
486+
public static GraphQLInputValueDefinition CreateGraphQLInputValueDefinition(IgnoreOptions options, bool? argument)
487487
{
488-
return options switch
489-
{
490-
IgnoreOptions.All => new GraphQLInputValueDefinition(),
491-
IgnoreOptions.Comments => new GraphQLInputValueDefinitionWithLocation(),
492-
IgnoreOptions.Locations => new GraphQLInputValueDefinitionWithComment(),
493-
_ => new GraphQLInputValueDefinitionFull(),
494-
};
488+
if (argument == true)
489+
return options switch
490+
{
491+
IgnoreOptions.All => new GraphQLArgumentDefinition(),
492+
IgnoreOptions.Comments => new GraphQLArgumentDefinitionWithLocation(),
493+
IgnoreOptions.Locations => new GraphQLArgumentDefinitionWithComment(),
494+
_ => new GraphQLArgumentDefinitionFull(),
495+
};
496+
else if (argument == false)
497+
return options switch
498+
{
499+
IgnoreOptions.All => new GraphQLInputFieldDefinition(),
500+
IgnoreOptions.Comments => new GraphQLInputFieldDefinitionWithLocation(),
501+
IgnoreOptions.Locations => new GraphQLInputFieldDefinitionWithComment(),
502+
_ => new GraphQLInputFieldDefinitionFull(),
503+
};
504+
else
505+
return options switch
506+
{
507+
IgnoreOptions.All => new GraphQLInputValueDefinition(),
508+
IgnoreOptions.Comments => new GraphQLInputValueDefinitionWithLocation(),
509+
IgnoreOptions.Locations => new GraphQLInputValueDefinitionWithComment(),
510+
_ => new GraphQLInputValueDefinitionFull(),
511+
};
495512
}
496513

497514
[MethodImpl(MethodImplOptions.AggressiveInlining)]

src/GraphQLParser/Parser.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,11 @@ public static T Parse<T>(ROM source, ParserOptions options = default)
8080
else if (typeof(T) == typeof(GraphQLInputObjectTypeDefinition))
8181
result = (T)(object)context.ParseInputObjectTypeDefinition();
8282
else if (typeof(T) == typeof(GraphQLInputValueDefinition))
83-
result = (T)(object)context.ParseInputValueDefinition();
83+
result = (T)(object)context.ParseInputValueDefinition(null);
84+
else if (typeof(T) == typeof(GraphQLInputFieldDefinition))
85+
result = (T)(object)context.ParseInputValueDefinition(false);
86+
else if (typeof(T) == typeof(GraphQLArgumentDefinition))
87+
result = (T)(object)context.ParseInputValueDefinition(true);
8488
else if (typeof(T) == typeof(GraphQLInterfaceTypeDefinition))
8589
result = (T)(object)context.ParseInterfaceTypeDefinition();
8690
else if (typeof(T) == typeof(GraphQLObjectTypeDefinition))

src/GraphQLParser/ParserContext.Parse.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ public GraphQLArgumentsDefinition ParseArgumentsDefinition()
9797
var argsDef = NodeHelper.CreateGraphQLArgumentsDefinition(_ignoreOptions);
9898

9999
argsDef.Comments = GetComments();
100-
argsDef.Items = OneOrMore(TokenKind.PAREN_L, (ref ParserContext context) => context.ParseInputValueDefinition(), TokenKind.PAREN_R);
100+
argsDef.Items = OneOrMore(TokenKind.PAREN_L, (ref ParserContext context) => context.ParseInputValueDefinition(true), TokenKind.PAREN_R);
101101
argsDef.Location = GetLocation(start);
102102

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

116116
inputFieldsDef.Comments = GetComments();
117-
inputFieldsDef.Items = OneOrMore(TokenKind.BRACE_L, (ref ParserContext context) => context.ParseInputValueDefinition(), TokenKind.BRACE_R);
117+
inputFieldsDef.Items = OneOrMore(TokenKind.BRACE_L, (ref ParserContext context) => context.ParseInputValueDefinition(false), TokenKind.BRACE_R);
118118
inputFieldsDef.Location = GetLocation(start);
119119

120120
DecreaseDepth();
@@ -735,13 +735,13 @@ private GraphQLInputObjectTypeExtension ParseInputObjectTypeExtension(int start,
735735
}
736736

737737
// http://spec.graphql.org/October2021/#InputValueDefinition
738-
public GraphQLInputValueDefinition ParseInputValueDefinition()
738+
public GraphQLInputValueDefinition ParseInputValueDefinition(bool? argument)
739739
{
740740
IncreaseDepth();
741741

742742
int start = _currentToken.Start;
743743

744-
var def = NodeHelper.CreateGraphQLInputValueDefinition(_ignoreOptions);
744+
var def = NodeHelper.CreateGraphQLInputValueDefinition(_ignoreOptions, argument);
745745

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

0 commit comments

Comments
 (0)