From cae396058f6679b8bdc5a2b5298b059eb3dde993 Mon Sep 17 00:00:00 2001 From: Shane32 Date: Mon, 28 Jun 2021 13:23:47 -0400 Subject: [PATCH 1/3] Add support for description parsing --- .../GraphQL-Parser.approved.txt | 25 +-- src/GraphQLParser.Tests/ParserTests.cs | 125 +++++++++++++++ .../AST/GraphQLDirectiveDefinition.cs | 2 +- .../AST/GraphQLEnumTypeDefinition.cs | 2 +- .../AST/GraphQLEnumValueDefinition.cs | 2 +- .../AST/GraphQLFieldDefinition.cs | 2 +- .../AST/GraphQLInputObjectTypeDefinition.cs | 2 +- .../AST/GraphQLInputValueDefinition.cs | 2 +- .../AST/GraphQLInterfaceTypeDefinition.cs | 2 +- .../AST/GraphQLObjectTypeDefinition.cs | 2 +- .../AST/GraphQLScalarTypeDefinition.cs | 2 +- .../AST/GraphQLTypeDefinition.cs | 8 + .../AST/GraphQLUnionTypeDefinition.cs | 2 +- src/GraphQLParser/ParserContext.Parse.cs | 150 ++++++++++++++++-- 14 files changed, 297 insertions(+), 31 deletions(-) diff --git a/src/GraphQLParser.ApiTests/GraphQL-Parser.approved.txt b/src/GraphQLParser.ApiTests/GraphQL-Parser.approved.txt index 153cf2ec..4787fa60 100644 --- a/src/GraphQLParser.ApiTests/GraphQL-Parser.approved.txt +++ b/src/GraphQLParser.ApiTests/GraphQL-Parser.approved.txt @@ -69,7 +69,7 @@ namespace GraphQLParser.AST public override GraphQLParser.AST.ASTNodeKind Kind { get; } public GraphQLParser.AST.GraphQLName? Name { get; set; } } - public class GraphQLDirectiveDefinition : GraphQLParser.AST.GraphQLTypeDefinition + public class GraphQLDirectiveDefinition : GraphQLParser.AST.GraphQLTypeDefinitionWithDescription { public GraphQLDirectiveDefinition() { } public System.Collections.Generic.List? Arguments { get; set; } @@ -86,20 +86,20 @@ namespace GraphQLParser.AST public void Dispose() { } protected virtual void Dispose(bool disposing) { } } - public class GraphQLEnumTypeDefinition : GraphQLParser.AST.GraphQLTypeDefinition, GraphQLParser.AST.IHasDirectivesNode + public class GraphQLEnumTypeDefinition : GraphQLParser.AST.GraphQLTypeDefinitionWithDescription, GraphQLParser.AST.IHasDirectivesNode { public GraphQLEnumTypeDefinition() { } public System.Collections.Generic.List? Directives { get; set; } public override GraphQLParser.AST.ASTNodeKind Kind { get; } public System.Collections.Generic.List? Values { get; set; } } - public class GraphQLEnumValueDefinition : GraphQLParser.AST.GraphQLTypeDefinition, GraphQLParser.AST.IHasDirectivesNode + public class GraphQLEnumValueDefinition : GraphQLParser.AST.GraphQLTypeDefinitionWithDescription, GraphQLParser.AST.IHasDirectivesNode { public GraphQLEnumValueDefinition() { } public System.Collections.Generic.List? Directives { get; set; } public override GraphQLParser.AST.ASTNodeKind Kind { get; } } - public class GraphQLFieldDefinition : GraphQLParser.AST.GraphQLTypeDefinition, GraphQLParser.AST.IHasDirectivesNode + public class GraphQLFieldDefinition : GraphQLParser.AST.GraphQLTypeDefinitionWithDescription, GraphQLParser.AST.IHasDirectivesNode { public GraphQLFieldDefinition() { } public System.Collections.Generic.List? Arguments { get; set; } @@ -138,14 +138,14 @@ namespace GraphQLParser.AST public GraphQLParser.AST.GraphQLSelectionSet? SelectionSet { get; set; } public GraphQLParser.AST.GraphQLNamedType? TypeCondition { get; set; } } - public class GraphQLInputObjectTypeDefinition : GraphQLParser.AST.GraphQLTypeDefinition, GraphQLParser.AST.IHasDirectivesNode + public class GraphQLInputObjectTypeDefinition : GraphQLParser.AST.GraphQLTypeDefinitionWithDescription, GraphQLParser.AST.IHasDirectivesNode { public GraphQLInputObjectTypeDefinition() { } public System.Collections.Generic.List? Directives { get; set; } public System.Collections.Generic.List? Fields { get; set; } public override GraphQLParser.AST.ASTNodeKind Kind { get; } } - public class GraphQLInputValueDefinition : GraphQLParser.AST.GraphQLTypeDefinition, GraphQLParser.AST.IHasDirectivesNode + public class GraphQLInputValueDefinition : GraphQLParser.AST.GraphQLTypeDefinitionWithDescription, GraphQLParser.AST.IHasDirectivesNode { public GraphQLInputValueDefinition() { } public GraphQLParser.AST.GraphQLValue? DefaultValue { get; set; } @@ -153,7 +153,7 @@ namespace GraphQLParser.AST public override GraphQLParser.AST.ASTNodeKind Kind { get; } public GraphQLParser.AST.GraphQLType? Type { get; set; } } - public class GraphQLInterfaceTypeDefinition : GraphQLParser.AST.GraphQLTypeDefinition, GraphQLParser.AST.IHasDirectivesNode + public class GraphQLInterfaceTypeDefinition : GraphQLParser.AST.GraphQLTypeDefinitionWithDescription, GraphQLParser.AST.IHasDirectivesNode { public GraphQLInterfaceTypeDefinition() { } public System.Collections.Generic.List? Directives { get; set; } @@ -214,7 +214,7 @@ namespace GraphQLParser.AST public GraphQLParser.AST.GraphQLName? Name { get; set; } public GraphQLParser.AST.GraphQLValue? Value { get; set; } } - public class GraphQLObjectTypeDefinition : GraphQLParser.AST.GraphQLTypeDefinition, GraphQLParser.AST.IHasDirectivesNode + public class GraphQLObjectTypeDefinition : GraphQLParser.AST.GraphQLTypeDefinitionWithDescription, GraphQLParser.AST.IHasDirectivesNode { public GraphQLObjectTypeDefinition() { } public System.Collections.Generic.List? Directives { get; set; } @@ -245,7 +245,7 @@ namespace GraphQLParser.AST public GraphQLParser.AST.OperationType Operation { get; set; } public GraphQLParser.AST.GraphQLNamedType? Type { get; set; } } - public class GraphQLScalarTypeDefinition : GraphQLParser.AST.GraphQLTypeDefinition, GraphQLParser.AST.IHasDirectivesNode + public class GraphQLScalarTypeDefinition : GraphQLParser.AST.GraphQLTypeDefinitionWithDescription, GraphQLParser.AST.IHasDirectivesNode { public GraphQLScalarTypeDefinition() { } public System.Collections.Generic.List? Directives { get; set; } @@ -280,13 +280,18 @@ namespace GraphQLParser.AST protected GraphQLTypeDefinition() { } public GraphQLParser.AST.GraphQLName? Name { get; set; } } + public abstract class GraphQLTypeDefinitionWithDescription : GraphQLParser.AST.GraphQLTypeDefinition + { + protected GraphQLTypeDefinitionWithDescription() { } + public GraphQLParser.AST.GraphQLScalarValue? Description { get; set; } + } public class GraphQLTypeExtensionDefinition : GraphQLParser.AST.GraphQLTypeDefinition { public GraphQLTypeExtensionDefinition() { } public GraphQLParser.AST.GraphQLObjectTypeDefinition? Definition { get; set; } public override GraphQLParser.AST.ASTNodeKind Kind { get; } } - public class GraphQLUnionTypeDefinition : GraphQLParser.AST.GraphQLTypeDefinition, GraphQLParser.AST.IHasDirectivesNode + public class GraphQLUnionTypeDefinition : GraphQLParser.AST.GraphQLTypeDefinitionWithDescription, GraphQLParser.AST.IHasDirectivesNode { public GraphQLUnionTypeDefinition() { } public System.Collections.Generic.List? Directives { get; set; } diff --git a/src/GraphQLParser.Tests/ParserTests.cs b/src/GraphQLParser.Tests/ParserTests.cs index c315e4db..ad3f698e 100644 --- a/src/GraphQLParser.Tests/ParserTests.cs +++ b/src/GraphQLParser.Tests/ParserTests.cs @@ -519,5 +519,130 @@ public void Should_Throw_On_Empty_Types_With_Braces(string text, int line, int c ex.Column.ShouldBe(column); ex.Message.ShouldContain("Expected Name, found }"); } + + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Descriptions_Should_Read_Correctly(IgnoreOptions options) + { + using var document = @" +""A JSON scalar"" +scalar JSON + +"""""" +Human type +"""""" +type Human { + """""" + Name of human + """""" + name: String + + ""Test"" + test( + ""desc"" + arg: Int + ): Int +} + +""Test interface"" +interface TestInterface { + ""Object name"" + name: String +} + +"""""" +Test union +"""""" +union TestUnion = Test1 | Test2 + +""Example enum"" +enum Colors { + ""Red"" RED + ""Blue"" BLUE +} + +"""""" +This is an example input object +Line two of the description +"""""" +input TestInputObject { + """""" + The value of the input object + (any JSON value is accepted) + """""" + Value: JSON +} + +""Test directive"" +directive @TestDirective ( + ""Example"" + Value: Int +) on QUERY +".Parse(new ParserOptions { Ignore = options }); + var defs = document.Definitions; + defs.Count.ShouldBe(7); + + var scalarDef = defs.Single(x => x is GraphQLScalarTypeDefinition) as GraphQLScalarTypeDefinition; + scalarDef.Name.Value.ShouldBe("JSON"); + scalarDef.Description.Value.ShouldBe("A JSON scalar"); + + var objectDef = defs.Single(x => x is GraphQLObjectTypeDefinition) as GraphQLObjectTypeDefinition; + objectDef.Name.Value.ShouldBe("Human"); + objectDef.Description.Value.ShouldBe("Human type"); + objectDef.Fields.Count.ShouldBe(2); + objectDef.Fields[0].Name.Value.ShouldBe("name"); + objectDef.Fields[0].Type.ShouldBeAssignableTo().Name.Value.ShouldBe("String"); + objectDef.Fields[0].Description.Value.ShouldBe("Name of human"); + objectDef.Fields[1].Name.Value.ShouldBe("test"); + objectDef.Fields[1].Type.ShouldBeAssignableTo().Name.Value.ShouldBe("Int"); + objectDef.Fields[1].Description.Value.ShouldBe("Test"); + objectDef.Fields[1].Arguments.Count.ShouldBe(1); + objectDef.Fields[1].Arguments[0].Name.Value.ShouldBe("arg"); + objectDef.Fields[1].Arguments[0].Description.Value.ShouldBe("desc"); + objectDef.Fields[1].Arguments[0].Type.ShouldBeAssignableTo().Name.Value.ShouldBe("Int"); + + var interfaceDef = defs.Single(x => x is GraphQLInterfaceTypeDefinition) as GraphQLInterfaceTypeDefinition; + interfaceDef.Name.Value.ShouldBe("TestInterface"); + interfaceDef.Description.Value.ShouldBe("Test interface"); + interfaceDef.Fields.Count.ShouldBe(1); + interfaceDef.Fields[0].Name.Value.ShouldBe("name"); + interfaceDef.Fields[0].Type.ShouldBeAssignableTo().Name.Value.ShouldBe("String"); + interfaceDef.Fields[0].Description.Value.ShouldBe("Object name"); + + var unionDef = defs.Single(x => x is GraphQLUnionTypeDefinition) as GraphQLUnionTypeDefinition; + unionDef.Name.Value.ShouldBe("TestUnion"); + unionDef.Description.Value.ShouldBe("Test union"); + unionDef.Types.Count.ShouldBe(2); + unionDef.Types[0].Name.Value.ShouldBe("Test1"); + unionDef.Types[1].Name.Value.ShouldBe("Test2"); + + var enumDef = defs.Single(x => x is GraphQLEnumTypeDefinition) as GraphQLEnumTypeDefinition; + enumDef.Name.Value.ShouldBe("Colors"); + enumDef.Description.Value.ShouldBe("Example enum"); + enumDef.Values.Count.ShouldBe(2); + enumDef.Values[0].Name.Value.ShouldBe("RED"); + enumDef.Values[0].Description.Value.ShouldBe("Red"); + enumDef.Values[1].Name.Value.ShouldBe("BLUE"); + enumDef.Values[1].Description.Value.ShouldBe("Blue"); + + var inputDef = defs.Single(x => x is GraphQLInputObjectTypeDefinition) as GraphQLInputObjectTypeDefinition; + inputDef.Name.Value.ShouldBe("TestInputObject"); + inputDef.Description.Value.ShouldBe("This is an example input object\nLine two of the description"); + inputDef.Fields.Count.ShouldBe(1); + inputDef.Fields[0].Name.Value.ShouldBe("Value"); + inputDef.Fields[0].Type.ShouldBeAssignableTo().Name.Value.ShouldBe("JSON"); + inputDef.Fields[0].Description.Value.ShouldBe("The value of the input object\n (any JSON value is accepted)"); + + var directiveDef = defs.Single(x => x is GraphQLDirectiveDefinition) as GraphQLDirectiveDefinition; + directiveDef.Name.Value.ShouldBe("TestDirective"); + directiveDef.Description.Value.ShouldBe("Test directive"); + directiveDef.Arguments.Count.ShouldBe(1); + directiveDef.Arguments[0].Name.Value.ShouldBe("Value"); + directiveDef.Arguments[0].Type.ShouldBeAssignableTo().Name.Value.ShouldBe("Int"); + directiveDef.Arguments[0].Description.Value.ShouldBe("Example"); + } + } } diff --git a/src/GraphQLParser/AST/GraphQLDirectiveDefinition.cs b/src/GraphQLParser/AST/GraphQLDirectiveDefinition.cs index 4db3e61a..6a5cb5dd 100644 --- a/src/GraphQLParser/AST/GraphQLDirectiveDefinition.cs +++ b/src/GraphQLParser/AST/GraphQLDirectiveDefinition.cs @@ -5,7 +5,7 @@ namespace GraphQLParser.AST /// /// Represents a directive definition. /// - public class GraphQLDirectiveDefinition : GraphQLTypeDefinition + public class GraphQLDirectiveDefinition : GraphQLTypeDefinitionWithDescription { public List? Arguments { get; set; } diff --git a/src/GraphQLParser/AST/GraphQLEnumTypeDefinition.cs b/src/GraphQLParser/AST/GraphQLEnumTypeDefinition.cs index 9fd18ad3..c5959c40 100644 --- a/src/GraphQLParser/AST/GraphQLEnumTypeDefinition.cs +++ b/src/GraphQLParser/AST/GraphQLEnumTypeDefinition.cs @@ -2,7 +2,7 @@ namespace GraphQLParser.AST { - public class GraphQLEnumTypeDefinition : GraphQLTypeDefinition, IHasDirectivesNode + public class GraphQLEnumTypeDefinition : GraphQLTypeDefinitionWithDescription, IHasDirectivesNode { /// public List? Directives { get; set; } diff --git a/src/GraphQLParser/AST/GraphQLEnumValueDefinition.cs b/src/GraphQLParser/AST/GraphQLEnumValueDefinition.cs index 3dc28fb2..47dd2b69 100644 --- a/src/GraphQLParser/AST/GraphQLEnumValueDefinition.cs +++ b/src/GraphQLParser/AST/GraphQLEnumValueDefinition.cs @@ -2,7 +2,7 @@ namespace GraphQLParser.AST { - public class GraphQLEnumValueDefinition : GraphQLTypeDefinition, IHasDirectivesNode + public class GraphQLEnumValueDefinition : GraphQLTypeDefinitionWithDescription, IHasDirectivesNode { /// public List? Directives { get; set; } diff --git a/src/GraphQLParser/AST/GraphQLFieldDefinition.cs b/src/GraphQLParser/AST/GraphQLFieldDefinition.cs index 003041c9..c9efd69a 100644 --- a/src/GraphQLParser/AST/GraphQLFieldDefinition.cs +++ b/src/GraphQLParser/AST/GraphQLFieldDefinition.cs @@ -2,7 +2,7 @@ namespace GraphQLParser.AST { - public class GraphQLFieldDefinition : GraphQLTypeDefinition, IHasDirectivesNode + public class GraphQLFieldDefinition : GraphQLTypeDefinitionWithDescription, IHasDirectivesNode { public List? Arguments { get; set; } diff --git a/src/GraphQLParser/AST/GraphQLInputObjectTypeDefinition.cs b/src/GraphQLParser/AST/GraphQLInputObjectTypeDefinition.cs index 02f7af8f..7645e7ab 100644 --- a/src/GraphQLParser/AST/GraphQLInputObjectTypeDefinition.cs +++ b/src/GraphQLParser/AST/GraphQLInputObjectTypeDefinition.cs @@ -2,7 +2,7 @@ namespace GraphQLParser.AST { - public class GraphQLInputObjectTypeDefinition : GraphQLTypeDefinition, IHasDirectivesNode + public class GraphQLInputObjectTypeDefinition : GraphQLTypeDefinitionWithDescription, IHasDirectivesNode { /// public List? Directives { get; set; } diff --git a/src/GraphQLParser/AST/GraphQLInputValueDefinition.cs b/src/GraphQLParser/AST/GraphQLInputValueDefinition.cs index f5388c71..18d82f1e 100644 --- a/src/GraphQLParser/AST/GraphQLInputValueDefinition.cs +++ b/src/GraphQLParser/AST/GraphQLInputValueDefinition.cs @@ -2,7 +2,7 @@ namespace GraphQLParser.AST { - public class GraphQLInputValueDefinition : GraphQLTypeDefinition, IHasDirectivesNode + public class GraphQLInputValueDefinition : GraphQLTypeDefinitionWithDescription, IHasDirectivesNode { public GraphQLValue? DefaultValue { get; set; } diff --git a/src/GraphQLParser/AST/GraphQLInterfaceTypeDefinition.cs b/src/GraphQLParser/AST/GraphQLInterfaceTypeDefinition.cs index 7d78bfdd..d75e11ef 100644 --- a/src/GraphQLParser/AST/GraphQLInterfaceTypeDefinition.cs +++ b/src/GraphQLParser/AST/GraphQLInterfaceTypeDefinition.cs @@ -2,7 +2,7 @@ namespace GraphQLParser.AST { - public class GraphQLInterfaceTypeDefinition : GraphQLTypeDefinition, IHasDirectivesNode + public class GraphQLInterfaceTypeDefinition : GraphQLTypeDefinitionWithDescription, IHasDirectivesNode { /// public List? Directives { get; set; } diff --git a/src/GraphQLParser/AST/GraphQLObjectTypeDefinition.cs b/src/GraphQLParser/AST/GraphQLObjectTypeDefinition.cs index 708cd948..83142093 100644 --- a/src/GraphQLParser/AST/GraphQLObjectTypeDefinition.cs +++ b/src/GraphQLParser/AST/GraphQLObjectTypeDefinition.cs @@ -2,7 +2,7 @@ namespace GraphQLParser.AST { - public class GraphQLObjectTypeDefinition : GraphQLTypeDefinition, IHasDirectivesNode + public class GraphQLObjectTypeDefinition : GraphQLTypeDefinitionWithDescription, IHasDirectivesNode { /// public List? Directives { get; set; } diff --git a/src/GraphQLParser/AST/GraphQLScalarTypeDefinition.cs b/src/GraphQLParser/AST/GraphQLScalarTypeDefinition.cs index 00d91e98..c4e71ab4 100644 --- a/src/GraphQLParser/AST/GraphQLScalarTypeDefinition.cs +++ b/src/GraphQLParser/AST/GraphQLScalarTypeDefinition.cs @@ -2,7 +2,7 @@ namespace GraphQLParser.AST { - public class GraphQLScalarTypeDefinition : GraphQLTypeDefinition, IHasDirectivesNode + public class GraphQLScalarTypeDefinition : GraphQLTypeDefinitionWithDescription, IHasDirectivesNode { /// public List? Directives { get; set; } diff --git a/src/GraphQLParser/AST/GraphQLTypeDefinition.cs b/src/GraphQLParser/AST/GraphQLTypeDefinition.cs index 3f0c7d12..a939266f 100644 --- a/src/GraphQLParser/AST/GraphQLTypeDefinition.cs +++ b/src/GraphQLParser/AST/GraphQLTypeDefinition.cs @@ -5,4 +5,12 @@ public abstract class GraphQLTypeDefinition : ASTNode, INamedNode /// public GraphQLName? Name { get; set; } } + + public abstract class GraphQLTypeDefinitionWithDescription : GraphQLTypeDefinition + { + /// + /// Description of the node as represented by a nested node. + /// + public GraphQLScalarValue? Description { get; set; } + } } diff --git a/src/GraphQLParser/AST/GraphQLUnionTypeDefinition.cs b/src/GraphQLParser/AST/GraphQLUnionTypeDefinition.cs index 3ee86ab5..6409eb8a 100644 --- a/src/GraphQLParser/AST/GraphQLUnionTypeDefinition.cs +++ b/src/GraphQLParser/AST/GraphQLUnionTypeDefinition.cs @@ -2,7 +2,7 @@ namespace GraphQLParser.AST { - public class GraphQLUnionTypeDefinition : GraphQLTypeDefinition, IHasDirectivesNode + public class GraphQLUnionTypeDefinition : GraphQLTypeDefinitionWithDescription, IHasDirectivesNode { /// public List? Directives { get; set; } diff --git a/src/GraphQLParser/ParserContext.Parse.cs b/src/GraphQLParser/ParserContext.Parse.cs index 3af001ef..27a3b708 100644 --- a/src/GraphQLParser/ParserContext.Parse.cs +++ b/src/GraphQLParser/ParserContext.Parse.cs @@ -114,6 +114,13 @@ private ASTNode ParseDefinition() return definition; } + if (Peek(TokenKind.STRING)) + { + ASTNode? definition; + if ((definition = ParseNamedDefinitionWithDescription()) != null) + return definition; + } + return Throw_From_ParseDefinition(); } @@ -225,8 +232,14 @@ private GraphQLDirective ParseDirective() /// private GraphQLDirectiveDefinition ParseDirectiveDefinition() { - var comment = GetComment(); int start = _currentToken.Start; + GraphQLScalarValue? description = null; + if (Peek(TokenKind.STRING)) + { + description = ParseString(); + ParseComment(); + } + var comment = GetComment(); ExpectKeyword("directive"); Expect(TokenKind.AT); @@ -244,6 +257,7 @@ private GraphQLDirectiveDefinition ParseDirectiveDefinition() Repeatable = repeatable, Arguments = args, Locations = locations, + Description = description, } : new GraphQLDirectiveDefinitionFull { @@ -252,6 +266,7 @@ private GraphQLDirectiveDefinition ParseDirectiveDefinition() Repeatable = repeatable, Arguments = args, Locations = locations, + Description = description, Location = GetLocation(start) }; } @@ -333,8 +348,14 @@ private GraphQLDocument ParseDocument() private GraphQLEnumTypeDefinition ParseEnumTypeDefinition() { - var comment = GetComment(); int start = _currentToken.Start; + GraphQLScalarValue? description = null; + if (Peek(TokenKind.STRING)) + { + description = ParseString(); + ParseComment(); + } + var comment = GetComment(); ExpectKeyword("enum"); return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations @@ -343,6 +364,7 @@ private GraphQLEnumTypeDefinition ParseEnumTypeDefinition() Name = ParseName(), Directives = ParseDirectives(), Values = ParseEnumValueDefinitions(), + Description = description, } : new GraphQLEnumTypeDefinitionFull { @@ -350,6 +372,7 @@ private GraphQLEnumTypeDefinition ParseEnumTypeDefinition() Name = ParseName(), Directives = ParseDirectives(), Values = ParseEnumValueDefinitions(), + Description = description, Location = GetLocation(start) }; } @@ -371,28 +394,43 @@ private GraphQLValue ParseEnumValue(Token token) private GraphQLEnumValueDefinition ParseEnumValueDefinition() { - var comment = GetComment(); int start = _currentToken.Start; + GraphQLScalarValue? description = null; + if (Peek(TokenKind.STRING)) + { + description = ParseString(); + ParseComment(); + } + var comment = GetComment(); return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations ? new GraphQLEnumValueDefinition { Name = ParseName(), Directives = ParseDirectives(), + Description = description, } : new GraphQLEnumValueDefinitionFull { Comment = comment, Name = ParseName(), Directives = ParseDirectives(), + Description = description, Location = GetLocation(start) }; } private GraphQLFieldDefinition ParseFieldDefinition() { - var comment = GetComment(); int start = _currentToken.Start; + GraphQLScalarValue? description = null; + if (Peek(TokenKind.STRING)) + { + description = ParseString(); + ParseComment(); + } + + var comment = GetComment(); var name = ParseName(); var args = ParseArgumentDefs(); Expect(TokenKind.COLON); @@ -404,6 +442,7 @@ private GraphQLFieldDefinition ParseFieldDefinition() Arguments = args, Type = ParseType(), Directives = ParseDirectives(), + Description = description, } : new GraphQLFieldDefinitionFull { @@ -412,6 +451,7 @@ private GraphQLFieldDefinition ParseFieldDefinition() Arguments = args, Type = ParseType(), Directives = ParseDirectives(), + Description = description, Location = GetLocation(start) }; } @@ -583,8 +623,14 @@ private void Throw_From_ParseFragmentName() private GraphQLInputObjectTypeDefinition ParseInputObjectTypeDefinition() { - var comment = GetComment(); int start = _currentToken.Start; + GraphQLScalarValue? description = null; + if (Peek(TokenKind.STRING)) + { + description = ParseString(); + ParseComment(); + } + var comment = GetComment(); ExpectKeyword("input"); return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations @@ -593,6 +639,7 @@ private GraphQLInputObjectTypeDefinition ParseInputObjectTypeDefinition() Name = ParseName(), Directives = ParseDirectives(), Fields = ParseInputValueDefs(), + Description = description, } : new GraphQLInputObjectTypeDefinitionFull { @@ -600,14 +647,21 @@ private GraphQLInputObjectTypeDefinition ParseInputObjectTypeDefinition() Name = ParseName(), Directives = ParseDirectives(), Fields = ParseInputValueDefs(), + Description = description, Location = GetLocation(start) }; } private GraphQLInputValueDefinition ParseInputValueDef() { - var comment = GetComment(); int start = _currentToken.Start; + GraphQLScalarValue? description = null; + if (Peek(TokenKind.STRING)) + { + description = ParseString(); + ParseComment(); + } + var comment = GetComment(); var name = ParseName(); Expect(TokenKind.COLON); @@ -618,6 +672,7 @@ private GraphQLInputValueDefinition ParseInputValueDef() Type = ParseType(), DefaultValue = Skip(TokenKind.EQUALS) ? ParseValueLiteral(true) : null, Directives = ParseDirectives(), + Description = description, } : new GraphQLInputValueDefinitionFull { @@ -626,6 +681,7 @@ private GraphQLInputValueDefinition ParseInputValueDef() Type = ParseType(), DefaultValue = Skip(TokenKind.EQUALS) ? ParseValueLiteral(true) : null, Directives = ParseDirectives(), + Description = description, Location = GetLocation(start) }; } @@ -649,8 +705,14 @@ private GraphQLValue ParseInt(/*bool isConstant*/) private GraphQLInterfaceTypeDefinition ParseInterfaceTypeDefinition() { - var comment = GetComment(); int start = _currentToken.Start; + GraphQLScalarValue? description = null; + if (Peek(TokenKind.STRING)) + { + description = ParseString(); + ParseComment(); + } + var comment = GetComment(); ExpectKeyword("interface"); return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations @@ -659,6 +721,7 @@ private GraphQLInterfaceTypeDefinition ParseInterfaceTypeDefinition() Name = ParseName(), Directives = ParseDirectives(), Fields = ParseFieldDefinitions(), + Description = description, } : new GraphQLInterfaceTypeDefinitionFull { @@ -666,6 +729,7 @@ private GraphQLInterfaceTypeDefinition ParseInterfaceTypeDefinition() Name = ParseName(), Directives = ParseDirectives(), Fields = ParseFieldDefinitions(), + Description = description, Location = GetLocation(start) }; } @@ -756,6 +820,46 @@ private GraphQLName ParseName() return null; } + private ASTNode? ParseNamedDefinitionWithDescription() + { + //look-ahead to next token + var token = Lexer.Lex(_source, _currentToken.End); + //skip comments + while (_currentToken.Kind != TokenKind.EOF && _currentToken.Kind == TokenKind.COMMENT) + { + token = Lexer.Lex(_source, token.End); + } + //verify this is a NAME + if (token.Kind != TokenKind.NAME) + return null; + + //retrieve the value + var value = token.Value; + + if (value == "scalar") + return ParseScalarTypeDefinition(); + + if (value == "type") + return ParseObjectTypeDefinition(); + + if (value == "interface") + return ParseInterfaceTypeDefinition(); + + if (value == "union") + return ParseUnionTypeDefinition(); + + if (value == "enum") + return ParseEnumTypeDefinition(); + + if (value == "input") + return ParseInputObjectTypeDefinition(); + + if (value == "directive") + return ParseDirectiveDefinition(); + + return null; + } + private GraphQLNamedType ParseNamedType() { int start = _currentToken.Start; @@ -859,9 +963,15 @@ private List ParseObjectFields(bool isConstant) private GraphQLObjectTypeDefinition ParseObjectTypeDefinition() { + int start = _currentToken.Start; + GraphQLScalarValue? description = null; + if (Peek(TokenKind.STRING)) + { + description = ParseString(); + ParseComment(); + } var comment = GetComment(); - int start = _currentToken.Start; ExpectKeyword("type"); return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations @@ -871,6 +981,7 @@ private GraphQLObjectTypeDefinition ParseObjectTypeDefinition() Interfaces = ParseImplementsInterfaces(), Directives = ParseDirectives(), Fields = ParseFieldDefinitions(), + Description = description, } : new GraphQLObjectTypeDefinitionFull { @@ -879,6 +990,7 @@ private GraphQLObjectTypeDefinition ParseObjectTypeDefinition() Interfaces = ParseImplementsInterfaces(), Directives = ParseDirectives(), Fields = ParseFieldDefinitions(), + Description = description, Location = GetLocation(start) }; } @@ -971,8 +1083,14 @@ private GraphQLOperationTypeDefinition ParseOperationTypeDefinition() private GraphQLScalarTypeDefinition ParseScalarTypeDefinition() { - var comment = GetComment(); int start = _currentToken.Start; + GraphQLScalarValue? description = null; + if (Peek(TokenKind.STRING)) + { + description = ParseString(); + ParseComment(); + } + var comment = GetComment(); ExpectKeyword("scalar"); var name = ParseName(); var directives = ParseDirectives(); @@ -982,12 +1100,14 @@ private GraphQLScalarTypeDefinition ParseScalarTypeDefinition() { Name = name, Directives = directives, + Description = description, } : new GraphQLScalarTypeDefinitionFull { Comment = comment, Name = name, Directives = directives, + Description = description, Location = GetLocation(start) }; } @@ -1037,7 +1157,7 @@ private GraphQLSelectionSet ParseSelectionSet() }; } - private GraphQLValue ParseString(/*bool isConstant*/) + private GraphQLScalarValue ParseString(/*bool isConstant*/) { var token = _currentToken; Advance(); @@ -1132,8 +1252,14 @@ private List ParseUnionMembers() private GraphQLUnionTypeDefinition ParseUnionTypeDefinition() { - var comment = GetComment(); int start = _currentToken.Start; + GraphQLScalarValue? description = null; + if (Peek(TokenKind.STRING)) + { + description = ParseString(); + ParseComment(); + } + var comment = GetComment(); ExpectKeyword("union"); var name = ParseName(); var directives = ParseDirectives(); @@ -1146,6 +1272,7 @@ private GraphQLUnionTypeDefinition ParseUnionTypeDefinition() Name = name, Directives = directives, Types = types, + Description = description, } : new GraphQLUnionTypeDefinitionFull { @@ -1153,6 +1280,7 @@ private GraphQLUnionTypeDefinition ParseUnionTypeDefinition() Name = name, Directives = directives, Types = types, + Description = description, Location = GetLocation(start) }; } From 397d4edc1d39bd92b2158a0d947ddf41fe9902fd Mon Sep 17 00:00:00 2001 From: Shane32 Date: Mon, 28 Jun 2021 14:19:06 -0400 Subject: [PATCH 2/3] Update --- src/GraphQLParser.Tests/ParserTests.cs | 530 +++++++++++++++++++++++ src/GraphQLParser/ParserContext.Parse.cs | 2 +- 2 files changed, 531 insertions(+), 1 deletion(-) diff --git a/src/GraphQLParser.Tests/ParserTests.cs b/src/GraphQLParser.Tests/ParserTests.cs index ad3f698e..5ca01510 100644 --- a/src/GraphQLParser.Tests/ParserTests.cs +++ b/src/GraphQLParser.Tests/ParserTests.cs @@ -644,5 +644,535 @@ directive @TestDirective ( directiveDef.Arguments[0].Description.Value.ShouldBe("Example"); } + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Descriptions_WithComments_Should_Read_Correctly_1(IgnoreOptions options) + { + using var document = @" +# comment 1 +""A JSON scalar"" +# comment 2 +scalar JSON + +# comment 3 +"""""" +Human type +"""""" +# comment 4 +type Human { + # comment 5 + """""" + Name of human + """""" + # comment 6 + name: String + + # comment 7 + ""Test"" + # comment 8 + test( + # comment 9 + ""desc"" + # comment 10 + arg: Int + ): Int +} + +# comment 11 +""Test interface"" +# comment 12 +interface TestInterface { + # comment 13 + ""Object name"" + # comment 14 + name: String +} + +# comment 15 +"""""" +Test union +"""""" +# comment 16 +union TestUnion = Test1 | Test2 + +# comment 17 +""Example enum"" +# comment 18 +enum Colors { + # comment 19 + ""Red"" + # comment 20 + RED + # comment 21 + ""Blue"" + # comment 22 + BLUE +} + +# comment 23 +"""""" +This is an example input object +Line two of the description +"""""" +# comment 24 +input TestInputObject { + # comment 25 + """""" + The value of the input object + (any JSON value is accepted) + """""" + # comment 26 + Value: JSON +} + +# comment 27 +""Test directive"" +# comment 28 +directive @TestDirective ( + # comment 29 + ""Example"" + # comment 30 + Value: Int +) on QUERY +".Parse(new ParserOptions { Ignore = options }); + var defs = document.Definitions; + defs.Count.ShouldBe(7); + var parseComments = options == IgnoreOptions.None; + + var scalarDef = defs.Single(x => x is GraphQLScalarTypeDefinition) as GraphQLScalarTypeDefinition; + scalarDef.Name.Value.ShouldBe("JSON"); + scalarDef.Description.Value.ShouldBe("A JSON scalar"); + if (parseComments) + scalarDef.Comment.Text.ShouldBe(" comment 2"); + + var objectDef = defs.Single(x => x is GraphQLObjectTypeDefinition) as GraphQLObjectTypeDefinition; + objectDef.Name.Value.ShouldBe("Human"); + objectDef.Description.Value.ShouldBe("Human type"); + if (parseComments) + objectDef.Comment.Text.ShouldBe(" comment 4"); + objectDef.Fields.Count.ShouldBe(2); + objectDef.Fields[0].Name.Value.ShouldBe("name"); + objectDef.Fields[0].Type.ShouldBeAssignableTo().Name.Value.ShouldBe("String"); + objectDef.Fields[0].Description.Value.ShouldBe("Name of human"); + if (parseComments) + objectDef.Fields[0].Comment.Text.ShouldBe(" comment 6"); + objectDef.Fields[1].Name.Value.ShouldBe("test"); + objectDef.Fields[1].Type.ShouldBeAssignableTo().Name.Value.ShouldBe("Int"); + objectDef.Fields[1].Description.Value.ShouldBe("Test"); + if (parseComments) + objectDef.Fields[1].Comment.Text.ShouldBe(" comment 8"); + objectDef.Fields[1].Arguments.Count.ShouldBe(1); + objectDef.Fields[1].Arguments[0].Name.Value.ShouldBe("arg"); + objectDef.Fields[1].Arguments[0].Description.Value.ShouldBe("desc"); + objectDef.Fields[1].Arguments[0].Type.ShouldBeAssignableTo().Name.Value.ShouldBe("Int"); + if (parseComments) + objectDef.Fields[1].Arguments[0].Comment.Text.ShouldBe(" comment 10"); + + var interfaceDef = defs.Single(x => x is GraphQLInterfaceTypeDefinition) as GraphQLInterfaceTypeDefinition; + interfaceDef.Name.Value.ShouldBe("TestInterface"); + interfaceDef.Description.Value.ShouldBe("Test interface"); + if (parseComments) + interfaceDef.Comment.Text.ShouldBe(" comment 12"); + interfaceDef.Fields.Count.ShouldBe(1); + interfaceDef.Fields[0].Name.Value.ShouldBe("name"); + interfaceDef.Fields[0].Type.ShouldBeAssignableTo().Name.Value.ShouldBe("String"); + interfaceDef.Fields[0].Description.Value.ShouldBe("Object name"); + if (parseComments) + interfaceDef.Fields[0].Comment.Text.ShouldBe(" comment 14"); + + var unionDef = defs.Single(x => x is GraphQLUnionTypeDefinition) as GraphQLUnionTypeDefinition; + unionDef.Name.Value.ShouldBe("TestUnion"); + unionDef.Description.Value.ShouldBe("Test union"); + if (parseComments) + unionDef.Comment.Text.ShouldBe(" comment 16"); + unionDef.Types.Count.ShouldBe(2); + unionDef.Types[0].Name.Value.ShouldBe("Test1"); + unionDef.Types[1].Name.Value.ShouldBe("Test2"); + + var enumDef = defs.Single(x => x is GraphQLEnumTypeDefinition) as GraphQLEnumTypeDefinition; + enumDef.Name.Value.ShouldBe("Colors"); + enumDef.Description.Value.ShouldBe("Example enum"); + if (parseComments) + enumDef.Comment.Text.ShouldBe(" comment 18"); + enumDef.Values.Count.ShouldBe(2); + enumDef.Values[0].Name.Value.ShouldBe("RED"); + enumDef.Values[0].Description.Value.ShouldBe("Red"); + if (parseComments) + enumDef.Values[0].Comment.Text.ShouldBe(" comment 20"); + enumDef.Values[1].Name.Value.ShouldBe("BLUE"); + enumDef.Values[1].Description.Value.ShouldBe("Blue"); + if (parseComments) + enumDef.Values[1].Comment.Text.ShouldBe(" comment 22"); + + var inputDef = defs.Single(x => x is GraphQLInputObjectTypeDefinition) as GraphQLInputObjectTypeDefinition; + inputDef.Name.Value.ShouldBe("TestInputObject"); + inputDef.Description.Value.ShouldBe("This is an example input object\nLine two of the description"); + if (parseComments) + inputDef.Comment.Text.ShouldBe(" comment 24"); + inputDef.Fields.Count.ShouldBe(1); + inputDef.Fields[0].Name.Value.ShouldBe("Value"); + inputDef.Fields[0].Type.ShouldBeAssignableTo().Name.Value.ShouldBe("JSON"); + inputDef.Fields[0].Description.Value.ShouldBe("The value of the input object\n (any JSON value is accepted)"); + if (parseComments) + inputDef.Fields[0].Comment.Text.ShouldBe(" comment 26"); + + var directiveDef = defs.Single(x => x is GraphQLDirectiveDefinition) as GraphQLDirectiveDefinition; + directiveDef.Name.Value.ShouldBe("TestDirective"); + directiveDef.Description.Value.ShouldBe("Test directive"); + if (parseComments) + directiveDef.Comment.Text.ShouldBe(" comment 28"); + directiveDef.Arguments.Count.ShouldBe(1); + directiveDef.Arguments[0].Name.Value.ShouldBe("Value"); + directiveDef.Arguments[0].Type.ShouldBeAssignableTo().Name.Value.ShouldBe("Int"); + directiveDef.Arguments[0].Description.Value.ShouldBe("Example"); + if (parseComments) + directiveDef.Arguments[0].Comment.Text.ShouldBe(" comment 30"); + } + + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Descriptions_WithComments_Should_Read_Correctly_2(IgnoreOptions options) + { + using var document = @" +""A JSON scalar"" +# comment 2 +scalar JSON + +"""""" +Human type +"""""" +# comment 4 +type Human { + """""" + Name of human + """""" + # comment 6 + name: String + + ""Test"" + # comment 8 + test( + ""desc"" + # comment 10 + arg: Int + ): Int +} + +""Test interface"" +# comment 12 +interface TestInterface { + ""Object name"" + # comment 14 + name: String +} + +"""""" +Test union +"""""" +# comment 16 +union TestUnion = Test1 | Test2 + +""Example enum"" +# comment 18 +enum Colors { + ""Red"" + # comment 20 + RED + ""Blue"" + # comment 22 + BLUE +} + +"""""" +This is an example input object +Line two of the description +"""""" +# comment 24 +input TestInputObject { + """""" + The value of the input object + (any JSON value is accepted) + """""" + # comment 26 + Value: JSON +} + +""Test directive"" +# comment 28 +directive @TestDirective ( + ""Example"" + # comment 30 + Value: Int +) on QUERY +".Parse(new ParserOptions { Ignore = options }); + var defs = document.Definitions; + defs.Count.ShouldBe(7); + var parseComments = options == IgnoreOptions.None; + + var scalarDef = defs.Single(x => x is GraphQLScalarTypeDefinition) as GraphQLScalarTypeDefinition; + scalarDef.Name.Value.ShouldBe("JSON"); + scalarDef.Description.Value.ShouldBe("A JSON scalar"); + if (parseComments) + scalarDef.Comment.Text.ShouldBe(" comment 2"); + + var objectDef = defs.Single(x => x is GraphQLObjectTypeDefinition) as GraphQLObjectTypeDefinition; + objectDef.Name.Value.ShouldBe("Human"); + objectDef.Description.Value.ShouldBe("Human type"); + if (parseComments) + objectDef.Comment.Text.ShouldBe(" comment 4"); + objectDef.Fields.Count.ShouldBe(2); + objectDef.Fields[0].Name.Value.ShouldBe("name"); + objectDef.Fields[0].Type.ShouldBeAssignableTo().Name.Value.ShouldBe("String"); + objectDef.Fields[0].Description.Value.ShouldBe("Name of human"); + if (parseComments) + objectDef.Fields[0].Comment.Text.ShouldBe(" comment 6"); + objectDef.Fields[1].Name.Value.ShouldBe("test"); + objectDef.Fields[1].Type.ShouldBeAssignableTo().Name.Value.ShouldBe("Int"); + objectDef.Fields[1].Description.Value.ShouldBe("Test"); + if (parseComments) + objectDef.Fields[1].Comment.Text.ShouldBe(" comment 8"); + objectDef.Fields[1].Arguments.Count.ShouldBe(1); + objectDef.Fields[1].Arguments[0].Name.Value.ShouldBe("arg"); + objectDef.Fields[1].Arguments[0].Description.Value.ShouldBe("desc"); + objectDef.Fields[1].Arguments[0].Type.ShouldBeAssignableTo().Name.Value.ShouldBe("Int"); + if (parseComments) + objectDef.Fields[1].Arguments[0].Comment.Text.ShouldBe(" comment 10"); + + var interfaceDef = defs.Single(x => x is GraphQLInterfaceTypeDefinition) as GraphQLInterfaceTypeDefinition; + interfaceDef.Name.Value.ShouldBe("TestInterface"); + interfaceDef.Description.Value.ShouldBe("Test interface"); + if (parseComments) + interfaceDef.Comment.Text.ShouldBe(" comment 12"); + interfaceDef.Fields.Count.ShouldBe(1); + interfaceDef.Fields[0].Name.Value.ShouldBe("name"); + interfaceDef.Fields[0].Type.ShouldBeAssignableTo().Name.Value.ShouldBe("String"); + interfaceDef.Fields[0].Description.Value.ShouldBe("Object name"); + if (parseComments) + interfaceDef.Fields[0].Comment.Text.ShouldBe(" comment 14"); + + var unionDef = defs.Single(x => x is GraphQLUnionTypeDefinition) as GraphQLUnionTypeDefinition; + unionDef.Name.Value.ShouldBe("TestUnion"); + unionDef.Description.Value.ShouldBe("Test union"); + if (parseComments) + unionDef.Comment.Text.ShouldBe(" comment 16"); + unionDef.Types.Count.ShouldBe(2); + unionDef.Types[0].Name.Value.ShouldBe("Test1"); + unionDef.Types[1].Name.Value.ShouldBe("Test2"); + + var enumDef = defs.Single(x => x is GraphQLEnumTypeDefinition) as GraphQLEnumTypeDefinition; + enumDef.Name.Value.ShouldBe("Colors"); + enumDef.Description.Value.ShouldBe("Example enum"); + if (parseComments) + enumDef.Comment.Text.ShouldBe(" comment 18"); + enumDef.Values.Count.ShouldBe(2); + enumDef.Values[0].Name.Value.ShouldBe("RED"); + enumDef.Values[0].Description.Value.ShouldBe("Red"); + if (parseComments) + enumDef.Values[0].Comment.Text.ShouldBe(" comment 20"); + enumDef.Values[1].Name.Value.ShouldBe("BLUE"); + enumDef.Values[1].Description.Value.ShouldBe("Blue"); + if (parseComments) + enumDef.Values[1].Comment.Text.ShouldBe(" comment 22"); + + var inputDef = defs.Single(x => x is GraphQLInputObjectTypeDefinition) as GraphQLInputObjectTypeDefinition; + inputDef.Name.Value.ShouldBe("TestInputObject"); + inputDef.Description.Value.ShouldBe("This is an example input object\nLine two of the description"); + if (parseComments) + inputDef.Comment.Text.ShouldBe(" comment 24"); + inputDef.Fields.Count.ShouldBe(1); + inputDef.Fields[0].Name.Value.ShouldBe("Value"); + inputDef.Fields[0].Type.ShouldBeAssignableTo().Name.Value.ShouldBe("JSON"); + inputDef.Fields[0].Description.Value.ShouldBe("The value of the input object\n (any JSON value is accepted)"); + if (parseComments) + inputDef.Fields[0].Comment.Text.ShouldBe(" comment 26"); + + var directiveDef = defs.Single(x => x is GraphQLDirectiveDefinition) as GraphQLDirectiveDefinition; + directiveDef.Name.Value.ShouldBe("TestDirective"); + directiveDef.Description.Value.ShouldBe("Test directive"); + if (parseComments) + directiveDef.Comment.Text.ShouldBe(" comment 28"); + directiveDef.Arguments.Count.ShouldBe(1); + directiveDef.Arguments[0].Name.Value.ShouldBe("Value"); + directiveDef.Arguments[0].Type.ShouldBeAssignableTo().Name.Value.ShouldBe("Int"); + directiveDef.Arguments[0].Description.Value.ShouldBe("Example"); + if (parseComments) + directiveDef.Arguments[0].Comment.Text.ShouldBe(" comment 30"); + } + + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Descriptions_WithComments_Should_Read_Correctly_3(IgnoreOptions options) + { + using var document = @" +# comment 1 +""A JSON scalar"" +scalar JSON + +# comment 3 +"""""" +Human type +"""""" +type Human { + # comment 5 + """""" + Name of human + """""" + name: String + + # comment 7 + ""Test"" + test( + # comment 9 + ""desc"" + arg: Int + ): Int +} + +# comment 11 +""Test interface"" +interface TestInterface { + # comment 13 + ""Object name"" + name: String +} + +# comment 15 +"""""" +Test union +"""""" +union TestUnion = Test1 | Test2 + +# comment 17 +""Example enum"" +enum Colors { + # comment 19 + ""Red"" + RED + # comment 21 + ""Blue"" + BLUE +} + +# comment 23 +"""""" +This is an example input object +Line two of the description +"""""" +input TestInputObject { + # comment 25 + """""" + The value of the input object + (any JSON value is accepted) + """""" + Value: JSON +} + +# comment 27 +""Test directive"" +directive @TestDirective ( + # comment 29 + ""Example"" + Value: Int +) on QUERY +".Parse(new ParserOptions { Ignore = options }); + var defs = document.Definitions; + defs.Count.ShouldBe(7); + var parseComments = options == IgnoreOptions.None; + + var scalarDef = defs.Single(x => x is GraphQLScalarTypeDefinition) as GraphQLScalarTypeDefinition; + scalarDef.Name.Value.ShouldBe("JSON"); + scalarDef.Description.Value.ShouldBe("A JSON scalar"); + if (parseComments) + scalarDef.Comment.Text.ShouldBe(" comment 1"); + + var objectDef = defs.Single(x => x is GraphQLObjectTypeDefinition) as GraphQLObjectTypeDefinition; + objectDef.Name.Value.ShouldBe("Human"); + objectDef.Description.Value.ShouldBe("Human type"); + if (parseComments) + objectDef.Comment.Text.ShouldBe(" comment 3"); + objectDef.Fields.Count.ShouldBe(2); + objectDef.Fields[0].Name.Value.ShouldBe("name"); + objectDef.Fields[0].Type.ShouldBeAssignableTo().Name.Value.ShouldBe("String"); + objectDef.Fields[0].Description.Value.ShouldBe("Name of human"); + if (parseComments) + objectDef.Fields[0].Comment.Text.ShouldBe(" comment 5"); + objectDef.Fields[1].Name.Value.ShouldBe("test"); + objectDef.Fields[1].Type.ShouldBeAssignableTo().Name.Value.ShouldBe("Int"); + objectDef.Fields[1].Description.Value.ShouldBe("Test"); + if (parseComments) + objectDef.Fields[1].Comment.Text.ShouldBe(" comment 7"); + objectDef.Fields[1].Arguments.Count.ShouldBe(1); + objectDef.Fields[1].Arguments[0].Name.Value.ShouldBe("arg"); + objectDef.Fields[1].Arguments[0].Description.Value.ShouldBe("desc"); + objectDef.Fields[1].Arguments[0].Type.ShouldBeAssignableTo().Name.Value.ShouldBe("Int"); + if (parseComments) + objectDef.Fields[1].Arguments[0].Comment.Text.ShouldBe(" comment 9"); + + var interfaceDef = defs.Single(x => x is GraphQLInterfaceTypeDefinition) as GraphQLInterfaceTypeDefinition; + interfaceDef.Name.Value.ShouldBe("TestInterface"); + interfaceDef.Description.Value.ShouldBe("Test interface"); + if (parseComments) + interfaceDef.Comment.Text.ShouldBe(" comment 11"); + interfaceDef.Fields.Count.ShouldBe(1); + interfaceDef.Fields[0].Name.Value.ShouldBe("name"); + interfaceDef.Fields[0].Type.ShouldBeAssignableTo().Name.Value.ShouldBe("String"); + interfaceDef.Fields[0].Description.Value.ShouldBe("Object name"); + if (parseComments) + interfaceDef.Fields[0].Comment.Text.ShouldBe(" comment 13"); + + var unionDef = defs.Single(x => x is GraphQLUnionTypeDefinition) as GraphQLUnionTypeDefinition; + unionDef.Name.Value.ShouldBe("TestUnion"); + unionDef.Description.Value.ShouldBe("Test union"); + if (parseComments) + unionDef.Comment.Text.ShouldBe(" comment 15"); + unionDef.Types.Count.ShouldBe(2); + unionDef.Types[0].Name.Value.ShouldBe("Test1"); + unionDef.Types[1].Name.Value.ShouldBe("Test2"); + + var enumDef = defs.Single(x => x is GraphQLEnumTypeDefinition) as GraphQLEnumTypeDefinition; + enumDef.Name.Value.ShouldBe("Colors"); + enumDef.Description.Value.ShouldBe("Example enum"); + if (parseComments) + enumDef.Comment.Text.ShouldBe(" comment 17"); + enumDef.Values.Count.ShouldBe(2); + enumDef.Values[0].Name.Value.ShouldBe("RED"); + enumDef.Values[0].Description.Value.ShouldBe("Red"); + if (parseComments) + enumDef.Values[0].Comment.Text.ShouldBe(" comment 19"); + enumDef.Values[1].Name.Value.ShouldBe("BLUE"); + enumDef.Values[1].Description.Value.ShouldBe("Blue"); + if (parseComments) + enumDef.Values[1].Comment.Text.ShouldBe(" comment 21"); + + var inputDef = defs.Single(x => x is GraphQLInputObjectTypeDefinition) as GraphQLInputObjectTypeDefinition; + inputDef.Name.Value.ShouldBe("TestInputObject"); + inputDef.Description.Value.ShouldBe("This is an example input object\nLine two of the description"); + if (parseComments) + inputDef.Comment.Text.ShouldBe(" comment 23"); + inputDef.Fields.Count.ShouldBe(1); + inputDef.Fields[0].Name.Value.ShouldBe("Value"); + inputDef.Fields[0].Type.ShouldBeAssignableTo().Name.Value.ShouldBe("JSON"); + inputDef.Fields[0].Description.Value.ShouldBe("The value of the input object\n (any JSON value is accepted)"); + if (parseComments) + inputDef.Fields[0].Comment.Text.ShouldBe(" comment 25"); + + var directiveDef = defs.Single(x => x is GraphQLDirectiveDefinition) as GraphQLDirectiveDefinition; + directiveDef.Name.Value.ShouldBe("TestDirective"); + directiveDef.Description.Value.ShouldBe("Test directive"); + if (parseComments) + directiveDef.Comment.Text.ShouldBe(" comment 27"); + directiveDef.Arguments.Count.ShouldBe(1); + directiveDef.Arguments[0].Name.Value.ShouldBe("Value"); + directiveDef.Arguments[0].Type.ShouldBeAssignableTo().Name.Value.ShouldBe("Int"); + directiveDef.Arguments[0].Description.Value.ShouldBe("Example"); + if (parseComments) + directiveDef.Arguments[0].Comment.Text.ShouldBe(" comment 29"); + } } } diff --git a/src/GraphQLParser/ParserContext.Parse.cs b/src/GraphQLParser/ParserContext.Parse.cs index 27a3b708..d17d76cd 100644 --- a/src/GraphQLParser/ParserContext.Parse.cs +++ b/src/GraphQLParser/ParserContext.Parse.cs @@ -825,7 +825,7 @@ private GraphQLName ParseName() //look-ahead to next token var token = Lexer.Lex(_source, _currentToken.End); //skip comments - while (_currentToken.Kind != TokenKind.EOF && _currentToken.Kind == TokenKind.COMMENT) + while (token.Kind != TokenKind.EOF && token.Kind == TokenKind.COMMENT) { token = Lexer.Lex(_source, token.End); } From 68344cb66f1f7eb137517852b90e477aefcdd4b2 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Tue, 6 Jul 2021 11:24:41 -0400 Subject: [PATCH 3/3] Incorporated concepts from PR 70 --- .../GraphQL-Parser.approved.txt | 15 ++++- src/GraphQLParser/AST/ASTNodeKind.cs | 5 ++ src/GraphQLParser/AST/GraphQLDescription.cs | 9 +++ .../AST/GraphQLTypeDefinition.cs | 6 +- src/GraphQLParser/AST/IHasDescription.cs | 7 +++ src/GraphQLParser/ParserContext.Parse.cs | 56 ++++++++++++------- 6 files changed, 73 insertions(+), 25 deletions(-) create mode 100644 src/GraphQLParser/AST/GraphQLDescription.cs create mode 100644 src/GraphQLParser/AST/IHasDescription.cs diff --git a/src/GraphQLParser.ApiTests/GraphQL-Parser.approved.txt b/src/GraphQLParser.ApiTests/GraphQL-Parser.approved.txt index 4787fa60..6f308433 100644 --- a/src/GraphQLParser.ApiTests/GraphQL-Parser.approved.txt +++ b/src/GraphQLParser.ApiTests/GraphQL-Parser.approved.txt @@ -47,6 +47,7 @@ namespace GraphQLParser.AST TypeExtensionDefinition = 35, DirectiveDefinition = 36, Comment = 37, + Description = 38, } public class GraphQLArgument : GraphQLParser.AST.ASTNode, GraphQLParser.AST.INamedNode { @@ -62,6 +63,12 @@ namespace GraphQLParser.AST public GraphQLParser.ROM Text { get; set; } public override GraphQLParser.AST.GraphQLLocation Location { get; set; } } + public class GraphQLDescription : GraphQLParser.AST.ASTNode + { + public GraphQLDescription() { } + public override GraphQLParser.AST.ASTNodeKind Kind { get; } + public GraphQLParser.ROM Value { get; set; } + } public class GraphQLDirective : GraphQLParser.AST.ASTNode, GraphQLParser.AST.INamedNode { public GraphQLDirective() { } @@ -280,10 +287,10 @@ namespace GraphQLParser.AST protected GraphQLTypeDefinition() { } public GraphQLParser.AST.GraphQLName? Name { get; set; } } - public abstract class GraphQLTypeDefinitionWithDescription : GraphQLParser.AST.GraphQLTypeDefinition + public abstract class GraphQLTypeDefinitionWithDescription : GraphQLParser.AST.GraphQLTypeDefinition, GraphQLParser.AST.IHasDescription { protected GraphQLTypeDefinitionWithDescription() { } - public GraphQLParser.AST.GraphQLScalarValue? Description { get; set; } + public GraphQLParser.AST.GraphQLDescription? Description { get; set; } } public class GraphQLTypeExtensionDefinition : GraphQLParser.AST.GraphQLTypeDefinition { @@ -316,6 +323,10 @@ namespace GraphQLParser.AST public GraphQLParser.AST.GraphQLType? Type { get; set; } public GraphQLParser.AST.GraphQLVariable? Variable { get; set; } } + public interface IHasDescription + { + GraphQLParser.AST.GraphQLDescription? Description { get; set; } + } public interface IHasDirectivesNode { System.Collections.Generic.List? Directives { get; set; } diff --git a/src/GraphQLParser/AST/ASTNodeKind.cs b/src/GraphQLParser/AST/ASTNodeKind.cs index 56f96543..9d749cbc 100644 --- a/src/GraphQLParser/AST/ASTNodeKind.cs +++ b/src/GraphQLParser/AST/ASTNodeKind.cs @@ -89,5 +89,10 @@ public enum ASTNodeKind /// and have no significance to the semantic meaning of a GraphQL Document. /// Comment, + + /// + /// Description of a type definition. + /// + Description, } } diff --git a/src/GraphQLParser/AST/GraphQLDescription.cs b/src/GraphQLParser/AST/GraphQLDescription.cs new file mode 100644 index 00000000..f939c8b5 --- /dev/null +++ b/src/GraphQLParser/AST/GraphQLDescription.cs @@ -0,0 +1,9 @@ +namespace GraphQLParser.AST +{ + public class GraphQLDescription : ASTNode + { + public override ASTNodeKind Kind => ASTNodeKind.Description; + + public ROM Value { get; set; } + } +} diff --git a/src/GraphQLParser/AST/GraphQLTypeDefinition.cs b/src/GraphQLParser/AST/GraphQLTypeDefinition.cs index a939266f..391908b7 100644 --- a/src/GraphQLParser/AST/GraphQLTypeDefinition.cs +++ b/src/GraphQLParser/AST/GraphQLTypeDefinition.cs @@ -6,11 +6,11 @@ public abstract class GraphQLTypeDefinition : ASTNode, INamedNode public GraphQLName? Name { get; set; } } - public abstract class GraphQLTypeDefinitionWithDescription : GraphQLTypeDefinition + public abstract class GraphQLTypeDefinitionWithDescription : GraphQLTypeDefinition, IHasDescription { /// - /// Description of the node as represented by a nested node. + /// Description of the node /// - public GraphQLScalarValue? Description { get; set; } + public GraphQLDescription? Description { get; set; } } } diff --git a/src/GraphQLParser/AST/IHasDescription.cs b/src/GraphQLParser/AST/IHasDescription.cs new file mode 100644 index 00000000..9a2a1b2b --- /dev/null +++ b/src/GraphQLParser/AST/IHasDescription.cs @@ -0,0 +1,7 @@ +namespace GraphQLParser.AST +{ + public interface IHasDescription + { + GraphQLDescription? Description { get; set; } + } +} diff --git a/src/GraphQLParser/ParserContext.Parse.cs b/src/GraphQLParser/ParserContext.Parse.cs index d17d76cd..926fd391 100644 --- a/src/GraphQLParser/ParserContext.Parse.cs +++ b/src/GraphQLParser/ParserContext.Parse.cs @@ -233,10 +233,10 @@ private GraphQLDirective ParseDirective() private GraphQLDirectiveDefinition ParseDirectiveDefinition() { int start = _currentToken.Start; - GraphQLScalarValue? description = null; + GraphQLDescription? description = null; if (Peek(TokenKind.STRING)) { - description = ParseString(); + description = ParseDescription(); ParseComment(); } var comment = GetComment(); @@ -349,10 +349,10 @@ private GraphQLDocument ParseDocument() private GraphQLEnumTypeDefinition ParseEnumTypeDefinition() { int start = _currentToken.Start; - GraphQLScalarValue? description = null; + GraphQLDescription? description = null; if (Peek(TokenKind.STRING)) { - description = ParseString(); + description = ParseDescription(); ParseComment(); } var comment = GetComment(); @@ -395,10 +395,10 @@ private GraphQLValue ParseEnumValue(Token token) private GraphQLEnumValueDefinition ParseEnumValueDefinition() { int start = _currentToken.Start; - GraphQLScalarValue? description = null; + GraphQLDescription? description = null; if (Peek(TokenKind.STRING)) { - description = ParseString(); + description = ParseDescription(); ParseComment(); } var comment = GetComment(); @@ -423,10 +423,10 @@ private GraphQLEnumValueDefinition ParseEnumValueDefinition() private GraphQLFieldDefinition ParseFieldDefinition() { int start = _currentToken.Start; - GraphQLScalarValue? description = null; + GraphQLDescription? description = null; if (Peek(TokenKind.STRING)) { - description = ParseString(); + description = ParseDescription(); ParseComment(); } @@ -624,10 +624,10 @@ private void Throw_From_ParseFragmentName() private GraphQLInputObjectTypeDefinition ParseInputObjectTypeDefinition() { int start = _currentToken.Start; - GraphQLScalarValue? description = null; + GraphQLDescription? description = null; if (Peek(TokenKind.STRING)) { - description = ParseString(); + description = ParseDescription(); ParseComment(); } var comment = GetComment(); @@ -655,10 +655,10 @@ private GraphQLInputObjectTypeDefinition ParseInputObjectTypeDefinition() private GraphQLInputValueDefinition ParseInputValueDef() { int start = _currentToken.Start; - GraphQLScalarValue? description = null; + GraphQLDescription? description = null; if (Peek(TokenKind.STRING)) { - description = ParseString(); + description = ParseDescription(); ParseComment(); } var comment = GetComment(); @@ -706,10 +706,10 @@ private GraphQLValue ParseInt(/*bool isConstant*/) private GraphQLInterfaceTypeDefinition ParseInterfaceTypeDefinition() { int start = _currentToken.Start; - GraphQLScalarValue? description = null; + GraphQLDescription? description = null; if (Peek(TokenKind.STRING)) { - description = ParseString(); + description = ParseDescription(); ParseComment(); } var comment = GetComment(); @@ -964,10 +964,10 @@ private List ParseObjectFields(bool isConstant) private GraphQLObjectTypeDefinition ParseObjectTypeDefinition() { int start = _currentToken.Start; - GraphQLScalarValue? description = null; + GraphQLDescription? description = null; if (Peek(TokenKind.STRING)) { - description = ParseString(); + description = ParseDescription(); ParseComment(); } var comment = GetComment(); @@ -1084,10 +1084,10 @@ private GraphQLOperationTypeDefinition ParseOperationTypeDefinition() private GraphQLScalarTypeDefinition ParseScalarTypeDefinition() { int start = _currentToken.Start; - GraphQLScalarValue? description = null; + GraphQLDescription? description = null; if (Peek(TokenKind.STRING)) { - description = ParseString(); + description = ParseDescription(); ParseComment(); } var comment = GetComment(); @@ -1173,6 +1173,22 @@ private GraphQLScalarValue ParseString(/*bool isConstant*/) }; } + private GraphQLDescription ParseDescription() + { + var token = _currentToken; + Advance(); + return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLDescription() + { + Value = token.Value, + } + : new GraphQLDescription() + { + Value = token.Value, + Location = GetLocation(token.Start) + }; + } + private GraphQLType ParseType() { GraphQLType type; @@ -1253,10 +1269,10 @@ private List ParseUnionMembers() private GraphQLUnionTypeDefinition ParseUnionTypeDefinition() { int start = _currentToken.Start; - GraphQLScalarValue? description = null; + GraphQLDescription? description = null; if (Peek(TokenKind.STRING)) { - description = ParseString(); + description = ParseDescription(); ParseComment(); } var comment = GetComment();