diff --git a/README.md b/README.md index 10bbbc8b..91c5d756 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # GraphQL Dotnet Parser + [![AppVeyor](https://img.shields.io/appveyor/ci/graphql-dotnet-ci/parser.svg)](https://ci.appveyor.com/project/graphql-dotnet-ci/parser) [![Coverage Status](https://coveralls.io/repos/github/graphql-dotnet/parser/badge.svg?branch=master)](https://coveralls.io/github/graphql-dotnet/parser?branch=master) [![NuGet](https://img.shields.io/nuget/v/GraphQL-Parser.svg)](https://www.nuget.org/packages/GraphQL-Parser) @@ -21,13 +22,19 @@ Generates token based on input text. Lexer takes advantage of `ReadOnlyMemory` but still allocates memory for AST. ### Usage + +Directly: + ```c# -var ast = Parser.Parse(@" +var ast1 = Parser.Parse(@" { field -}", ignoreComments: true); +}"); -or +var ast2 = Parser.Parse(@" +{ + field +}", new ParserOptions { Ignore = IgnoreOptions.IgnoreComments }); +``` + +Or via extension method: + +```c# +var ast = @" +{ + field +}".Parse(); var ast = @" { field -}".Parse(ignoreComments: true); +}".Parse(new ParserOptions { Ignore = IgnoreOptions.IgnoreCommentsAndLocations }); ``` -By default `ignoreComments` is `true` to improve performance. +By default `ParserOptions.Ignore` is `IgnoreOptions.IgnoreComments` to improve performance. +If you don't need information about tokens locations in the source document, then use `IgnoreOptions.IgnoreCommentsAndLocations`. +This will maximize the saving of memory allocated in the managed heap for AST. -Json representation of the resulting AST would be: +### Example of json representation of the resulting AST ```json { - "Definitions": [{ - "Directives": [], - "Kind": 2, - "Name": null, - "Operation": 0, - "SelectionSet": { - "Kind": 5, - "Selections": [{ - "Alias": null, - "Arguments": [], - "Directives": [], - "Kind": 6, - "Name": { - "Kind": 0, - "Value": "field", - "Location": { - "End": 50, - "Start": 31 - } - }, - "SelectionSet": null, - "Location": { - "End": 50, - "Start": 31 - } - }], - "Location": { - "End": 50, - "Start": 13 - } - }, - "VariableDefinitions": null, - "Location": { - "End": 50, - "Start": 13 - } - }], - "Kind": 1, - "Location": { - "End": 50, - "Start": 13 - } + "Definitions": [{ + "Directives": [], + "Kind": 2, + "Name": null, + "Operation": 0, + "SelectionSet": { + "Kind": 5, + "Selections": [{ + "Alias": null, + "Arguments": [], + "Directives": [], + "Kind": 6, + "Name": { + "Kind": 0, + "Value": "field", + "Location": { + "End": 50, + "Start": 31 + } + }, + "SelectionSet": null, + "Location": { + "End": 50, + "Start": 31 + } + }], + "Location": { + "End": 50, + "Start": 13 + } + }, + "VariableDefinitions": null, + "Location": { + "End": 50, + "Start": 13 + } + }], + "Kind": 1, + "Location": { + "End": 50, + "Start": 13 + } } ``` diff --git a/assets/lexer-example.png b/assets/lexer-example.png index 9ed0000a..1029e9c2 100644 Binary files a/assets/lexer-example.png and b/assets/lexer-example.png differ diff --git a/src/GraphQLParser.ApiTests/GraphQL-Parser.approved.txt b/src/GraphQLParser.ApiTests/GraphQL-Parser.approved.txt index 04495b17..9625873c 100644 --- a/src/GraphQLParser.ApiTests/GraphQL-Parser.approved.txt +++ b/src/GraphQLParser.ApiTests/GraphQL-Parser.approved.txt @@ -3,9 +3,9 @@ namespace GraphQLParser.AST public abstract class ASTNode { protected ASTNode() { } - public GraphQLParser.AST.GraphQLComment? Comment { get; set; } public abstract GraphQLParser.AST.ASTNodeKind Kind { get; } - public GraphQLParser.AST.GraphQLLocation Location { get; set; } + public virtual GraphQLParser.AST.GraphQLComment? Comment { get; set; } + public virtual GraphQLParser.AST.GraphQLLocation Location { get; set; } } public enum ASTNodeKind { @@ -60,6 +60,7 @@ namespace GraphQLParser.AST public GraphQLComment() { } public override GraphQLParser.AST.ASTNodeKind Kind { get; } public GraphQLParser.ROM Text { get; set; } + public override GraphQLParser.AST.GraphQLLocation Location { get; set; } } public class GraphQLDirective : GraphQLParser.AST.ASTNode, GraphQLParser.AST.INamedNode { @@ -374,6 +375,12 @@ namespace GraphQLParser public virtual GraphQLParser.AST.GraphQLVariable EndVisitVariable(GraphQLParser.AST.GraphQLVariable variable) { } public virtual void Visit(GraphQLParser.AST.GraphQLDocument ast) { } } + public enum IgnoreOptions + { + IgnoreComments = 0, + IgnoreCommentsAndLocations = 1, + None = 2, + } public static class Lexer { public static GraphQLParser.Token Lex(GraphQLParser.ROM source, int start = 0) { } @@ -386,12 +393,16 @@ namespace GraphQLParser } public static class Parser { - public static GraphQLParser.AST.GraphQLDocument Parse(GraphQLParser.ROM source, bool ignoreComments = true) { } + public static GraphQLParser.AST.GraphQLDocument Parse(GraphQLParser.ROM source, GraphQLParser.ParserOptions options = default) { } } public static class ParserExtensions { public static GraphQLParser.Token Lex(this string source, int start = 0) { } - public static GraphQLParser.AST.GraphQLDocument Parse(this string source, bool ignoreComments = true) { } + public static GraphQLParser.AST.GraphQLDocument Parse(this string source, GraphQLParser.ParserOptions options = default) { } + } + public struct ParserOptions + { + public GraphQLParser.IgnoreOptions Ignore { get; set; } } public readonly struct ROM : System.IEquatable { diff --git a/src/GraphQLParser.Benchmarks/Benchmarks/BenchmarkBase.cs b/src/GraphQLParser.Benchmarks/Benchmarks/BenchmarkBase.cs index fbf52424..ad264883 100644 --- a/src/GraphQLParser.Benchmarks/Benchmarks/BenchmarkBase.cs +++ b/src/GraphQLParser.Benchmarks/Benchmarks/BenchmarkBase.cs @@ -10,6 +10,7 @@ public abstract class BenchmarkBase : IBenchmark private string _kitchen = null!; private string _introspection = null!; private string _params = null!; + private string _variables = null!; private string _github = null!; [GlobalSetup] @@ -20,6 +21,7 @@ public virtual void GlobalSetup() _kitchen = "kitchenSink".ReadGraphQLFile(); _introspection = "introspectionQuery".ReadGraphQLFile(); _params = "params".ReadGraphQLFile(); + _variables = "variables".ReadGraphQLFile(); _github = "github".ReadGraphQLFile(); } @@ -32,6 +34,7 @@ public string GetQueryByName(string name) "kitchen" => _kitchen, "introspection" => _introspection, "params" => _params, + "variables" => _variables, "github" => _github, _ => throw new System.Exception(name) }; @@ -44,6 +47,7 @@ public IEnumerable Names() yield return "kitchen"; yield return "introspection"; yield return "params"; + yield return "variables"; yield return "github"; } diff --git a/src/GraphQLParser.Benchmarks/Benchmarks/ParserBenchmark.cs b/src/GraphQLParser.Benchmarks/Benchmarks/ParserBenchmark.cs index c0728609..2a4723a5 100644 --- a/src/GraphQLParser.Benchmarks/Benchmarks/ParserBenchmark.cs +++ b/src/GraphQLParser.Benchmarks/Benchmarks/ParserBenchmark.cs @@ -1,19 +1,95 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Globalization; +using System.Linq; using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Order; +using BenchmarkDotNet.Reports; +using BenchmarkDotNet.Running; namespace GraphQLParser.Benchmarks { + [Config(typeof(Config))] [MemoryDiagnoser] //[RPlotExporter, CsvMeasurementsExporter] public class ParserBenchmark : BenchmarkBase { + private class Config : ManualConfig + { + public Config() + { + SummaryStyle = new SummaryStyle(CultureInfo.InvariantCulture, printUnitsInHeader: true, sizeUnit: null, timeUnit: null, printUnitsInContent: false, printZeroValuesInContent: false, maxParameterColumnWidth: 40); + Orderer = new ParserOrderer(); + } + + private class ParserOrderer : IOrderer + { + private static int GetOrder(object o) => o switch + { + IgnoreOptions.None => 1, + IgnoreOptions.IgnoreComments => 2, + IgnoreOptions.IgnoreCommentsAndLocations => 3, + _ => 4 + }; + + public IEnumerable GetExecutionOrder(ImmutableArray benchmarksCase) => benchmarksCase; + + public IEnumerable GetSummaryOrder(ImmutableArray benchmarksCase, Summary summary) => + from benchmark in benchmarksCase + orderby GetOrder(benchmark.Parameters["options"]) + orderby benchmark.Parameters["name"] + select benchmark; + + public string GetHighlightGroupKey(BenchmarkCase benchmarkCase) => null!; + + public string GetLogicalGroupKey(ImmutableArray allBenchmarksCases, BenchmarkCase benchmarkCase) => (string)benchmarkCase.Parameters["name"]; + + public IEnumerable> GetLogicalGroupOrder(IEnumerable> logicalGroups) => logicalGroups.OrderBy(it => it.Key); + + public bool SeparateLogicalGroups => true; + } + } + [Benchmark] - [ArgumentsSource(nameof(Names))] - public void Parse(string name) + [ArgumentsSource(nameof(NamesAndOptions))] + public void Parse(string name, IgnoreOptions options) { var source = GetQueryByName(name); - source.Parse().Dispose(); + source.Parse(new ParserOptions { Ignore = options }).Dispose(); + } + + public IEnumerable NamesAndOptions() + { + yield return new object[] { "hero", IgnoreOptions.None }; + yield return new object[] { "hero", IgnoreOptions.IgnoreComments }; + yield return new object[] { "hero", IgnoreOptions.IgnoreCommentsAndLocations }; + + yield return new object[] { "escapes", IgnoreOptions.None }; + yield return new object[] { "escapes", IgnoreOptions.IgnoreComments }; + yield return new object[] { "escapes", IgnoreOptions.IgnoreCommentsAndLocations }; + + yield return new object[] { "kitchen", IgnoreOptions.None }; + yield return new object[] { "kitchen", IgnoreOptions.IgnoreComments }; + yield return new object[] { "kitchen", IgnoreOptions.IgnoreCommentsAndLocations }; + + yield return new object[] { "introspection", IgnoreOptions.None }; + yield return new object[] { "introspection", IgnoreOptions.IgnoreComments }; + yield return new object[] { "introspection", IgnoreOptions.IgnoreCommentsAndLocations }; + + yield return new object[] { "params", IgnoreOptions.None }; + yield return new object[] { "params", IgnoreOptions.IgnoreComments }; + yield return new object[] { "params", IgnoreOptions.IgnoreCommentsAndLocations }; + + yield return new object[] { "variables", IgnoreOptions.None }; + yield return new object[] { "variables", IgnoreOptions.IgnoreComments }; + yield return new object[] { "variables", IgnoreOptions.IgnoreCommentsAndLocations }; + + yield return new object[] { "github", IgnoreOptions.None }; + yield return new object[] { "github", IgnoreOptions.IgnoreComments }; + yield return new object[] { "github", IgnoreOptions.IgnoreCommentsAndLocations }; } - public override void Run() => Parse("params"); + public override void Run() => Parse("params", IgnoreOptions.None); } } diff --git a/src/GraphQLParser.Benchmarks/Files/variables.graphql b/src/GraphQLParser.Benchmarks/Files/variables.graphql new file mode 100644 index 00000000..ff4943a0 --- /dev/null +++ b/src/GraphQLParser.Benchmarks/Files/variables.graphql @@ -0,0 +1,32 @@ +query var($condition: Boolean! = true, $count: Int! = 1000, $name: String! = "Tom", $address: String! = null) { + alias1: field1(c: $condition, cnt: $count, name: $name, addr: $address, info: null) { + field2(c: $condition, cnt: $count, name: $name, addr: $address, info: null) { + field3(c: $condition, cnt: $count, name: $name, addr: $address, info: null) { + field4(c: $condition, cnt: $count, name: $name, addr: $address, info: null) { + field5(c: $condition, cnt: $count, name: $name, addr: $address, info: null) + inner: value + } + } + } + } + alias2: field1(c: $condition, cnt: $count, name: $name, addr: $address, info: null) { + field2(c: $condition, cnt: $count, name: $name, addr: $address, info: null) { + field3(c: $condition, cnt: $count, name: $name, addr: $address, info: null) { + field4(c: $condition, cnt: $count, name: $name, addr: $address, info: null) { + field5(c: $condition, cnt: $count, name: $name, addr: $address, info: null) + inner: value + } + } + } + } + alias3: field1(c: $condition, cnt: $count, name: $name, addr: $address, info: null) { + field2(c: $condition, cnt: $count, name: $name, addr: $address, info: null) { + field3(c: $condition, cnt: $count, name: $name, addr: $address, info: null) { + field4(c: $condition, cnt: $count, name: $name, addr: $address, info: null) { + field5(c: $condition, cnt: $count, name: $name, addr: $address, info: null) + inner: value + } + } + } + } +} diff --git a/src/GraphQLParser.Benchmarks/Properties/launchSettings.json b/src/GraphQLParser.Benchmarks/Properties/launchSettings.json index 43128398..b06e203f 100644 --- a/src/GraphQLParser.Benchmarks/Properties/launchSettings.json +++ b/src/GraphQLParser.Benchmarks/Properties/launchSettings.json @@ -1,8 +1,11 @@ { "profiles": { + "Benchmark": { + "commandName": "Project" + }, "Profiler": { "commandName": "Project", "commandLineArgs": "test" } } -} \ No newline at end of file +} diff --git a/src/GraphQLParser.Tests/GraphQLAstVisitorTests.cs b/src/GraphQLParser.Tests/GraphQLAstVisitorTests.cs index 9ab27186..d78125e7 100644 --- a/src/GraphQLParser.Tests/GraphQLAstVisitorTests.cs +++ b/src/GraphQLParser.Tests/GraphQLAstVisitorTests.cs @@ -52,253 +52,337 @@ public GraphQLAstVisitorTests() _visitedEnumValues = MockVisitMethod((visitor) => visitor.BeginVisitEnumValue(null)); } - [Fact] - public void Visit_BooleanValueArgument_VisitsOneBooleanValue() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Visit_BooleanValueArgument_VisitsOneBooleanValue(IgnoreOptions options) { - using var d = "{ stuff(id : true) }".Parse(); + using var d = "{ stuff(id : true) }".Parse(new ParserOptions { Ignore = options }); _visitor.Visit(d); _visitedBooleanValues.ShouldHaveSingleItem(); } - [Fact] - public void Visit_DefinitionWithSingleFragmentSpread_VisitsFragmentSpreadOneTime() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Visit_DefinitionWithSingleFragmentSpread_VisitsFragmentSpreadOneTime(IgnoreOptions options) { - using var d = "{ foo { ...fragment } }".Parse(); + using var d = "{ foo { ...fragment } }".Parse(new ParserOptions { Ignore = options }); _visitor.Visit(d); _visitedFragmentSpreads.ShouldHaveSingleItem(); } - [Fact] - public void Visit_DefinitionWithSingleFragmentSpread_VisitsNameOfPropertyAndFragmentSpread() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Visit_DefinitionWithSingleFragmentSpread_VisitsNameOfPropertyAndFragmentSpread(IgnoreOptions options) { - using var d = "{ foo { ...fragment } }".Parse(); + using var d = "{ foo { ...fragment } }".Parse(new ParserOptions { Ignore = options }); _visitor.Visit(d); _visitedNames.Count.ShouldBe(2); } - [Fact] - public void Visit_DirectiveWithVariable_VisitsVariableOnce() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Visit_DirectiveWithVariable_VisitsVariableOnce(IgnoreOptions options) { - using var d = "{ ... @include(if : $stuff) { field } }".Parse(); + using var d = "{ ... @include(if : $stuff) { field } }".Parse(new ParserOptions { Ignore = options }); _visitor.Visit(d); _visitedVariables.ShouldHaveSingleItem(); } - [Fact] - public void Visit_EnumValueArgument_VisitsOneEnumValue() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Visit_EnumValueArgument_VisitsOneEnumValue(IgnoreOptions options) { - using var d = "{ stuff(id : TEST_ENUM) }".Parse(); + using var d = "{ stuff(id : TEST_ENUM) }".Parse(new ParserOptions { Ignore = options }); _visitor.Visit(d); _visitedEnumValues.ShouldHaveSingleItem(); } - [Fact] - public void Visit_FloatValueArgument_VisitsOneFloatValue() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Visit_FloatValueArgument_VisitsOneFloatValue(IgnoreOptions options) { - using var d = "{ stuff(id : 1.2) }".Parse(); + using var d = "{ stuff(id : 1.2) }".Parse(new ParserOptions { Ignore = options }); _visitor.Visit(d); _visitedFloatValues.ShouldHaveSingleItem(); } - [Fact] - public void Visit_FragmentWithTypeCondition_VisitsFragmentDefinitionOnce() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Visit_FragmentWithTypeCondition_VisitsFragmentDefinitionOnce(IgnoreOptions options) { - using var d = "fragment testFragment on Stuff { field }".Parse(); + using var d = "fragment testFragment on Stuff { field }".Parse(new ParserOptions { Ignore = options }); _visitor.Visit(d); _visitedFragmentDefinitions.ShouldHaveSingleItem(); } - [Fact] - public void Visit_FragmentWithTypeCondition_VisitsTypeConditionOnce() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Visit_FragmentWithTypeCondition_VisitsTypeConditionOnce(IgnoreOptions options) { - using var d = "fragment testFragment on Stuff { field }".Parse(); + using var d = "fragment testFragment on Stuff { field }".Parse(new ParserOptions { Ignore = options }); _visitor.Visit(d); _visitedFragmentTypeConditions.ShouldHaveSingleItem(); } - [Fact] - public void Visit_InlineFragmentWithDirectiveAndArgument_VisitsArgumentsOnce() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Visit_InlineFragmentWithDirectiveAndArgument_VisitsArgumentsOnce(IgnoreOptions options) { - using var d = "{ ... @include(if : $stuff) { field } }".Parse(); + using var d = "{ ... @include(if : $stuff) { field } }".Parse(new ParserOptions { Ignore = options }); _visitor.Visit(d); _visitedArguments.ShouldHaveSingleItem(); } - [Fact] - public void Visit_InlineFragmentWithDirectiveAndArgument_VisitsDirectiveOnce() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Visit_InlineFragmentWithDirectiveAndArgument_VisitsDirectiveOnce(IgnoreOptions options) { - using var d = "{ ... @include(if : $stuff) { field } }".Parse(); + using var d = "{ ... @include(if : $stuff) { field } }".Parse(new ParserOptions { Ignore = options }); _visitor.Visit(d); _visitedDirectives.ShouldHaveSingleItem(); } - [Fact] - public void Visit_InlineFragmentWithDirectiveAndArgument_VisitsNameThreeTimes() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Visit_InlineFragmentWithDirectiveAndArgument_VisitsNameThreeTimes(IgnoreOptions options) { - using var d = "{ ... @include(if : $stuff) { field } }".Parse(); + using var d = "{ ... @include(if : $stuff) { field } }".Parse(new ParserOptions { Ignore = options }); _visitor.Visit(d); _visitedNames.Count.ShouldBe(4); } - [Fact] - public void Visit_InlineFragmentWithOneField_VisitsOneField() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Visit_InlineFragmentWithOneField_VisitsOneField(IgnoreOptions options) { - using var d = "{ ... @include(if : $stuff) { field } }".Parse(); + using var d = "{ ... @include(if : $stuff) { field } }".Parse(new ParserOptions { Ignore = options }); _visitor.Visit(d); _visitedFieldSelections.ShouldHaveSingleItem(); } - [Fact] - public void Visit_InlineFragmentWithTypeCondition_VisitsInlineFragmentOnce() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Visit_InlineFragmentWithTypeCondition_VisitsInlineFragmentOnce(IgnoreOptions options) { - using var d = "{ ... on Stuff { field } }".Parse(); + using var d = "{ ... on Stuff { field } }".Parse(new ParserOptions { Ignore = options }); _visitor.Visit(d); _visitedInlineFragments.ShouldHaveSingleItem(); } - [Fact] - public void Visit_InlineFragmentWithTypeCondition_VisitsTypeConditionOnce() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Visit_InlineFragmentWithTypeCondition_VisitsTypeConditionOnce(IgnoreOptions options) { - using var d = "{ ... on Stuff { field } }".Parse(); + using var d = "{ ... on Stuff { field } }".Parse(new ParserOptions { Ignore = options }); _visitor.Visit(d); _visitedFragmentTypeConditions.ShouldHaveSingleItem(); } - [Fact] - public void Visit_IntValueArgument_VisitsOneIntValue() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Visit_IntValueArgument_VisitsOneIntValue(IgnoreOptions options) { - using var d = "{ stuff(id : 1) }".Parse(); + using var d = "{ stuff(id : 1) }".Parse(new ParserOptions { Ignore = options }); _visitor.Visit(d); _visitedIntValues.ShouldHaveSingleItem(); } - [Fact] - public void Visit_OneDefinition_CallsVisitDefinitionOnce() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Visit_OneDefinition_CallsVisitDefinitionOnce(IgnoreOptions options) { - using var d = "{ a }".Parse(); + using var d = "{ a }".Parse(new ParserOptions { Ignore = options }); _visitor.Visit(d); _visitedDefinitions.ShouldHaveSingleItem(); } - [Fact] - public void Visit_OneDefinition_ProvidesCorrectDefinitionAsParameter() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Visit_OneDefinition_ProvidesCorrectDefinitionAsParameter(IgnoreOptions options) { - using var d = "{ a }".Parse(); + using var d = "{ a }".Parse(new ParserOptions { Ignore = options }); _visitor.Visit(d); _visitedDefinitions.Single().ShouldBe(d.Definitions.Single()); } - [Fact] - public void Visit_OneDefinition_VisitsOneSelectionSet() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Visit_OneDefinition_VisitsOneSelectionSet(IgnoreOptions options) { - using var d = "{ a, b }".Parse(); + using var d = "{ a, b }".Parse(new ParserOptions { Ignore = options }); _visitor.Visit(d); _visitedSelectionSets.ShouldHaveSingleItem(); } - [Fact] - public void Visit_OneDefinitionWithOneAliasedField_VisitsOneAlias() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Visit_OneDefinitionWithOneAliasedField_VisitsOneAlias(IgnoreOptions options) { - using var d = "{ foo, foo : bar }".Parse(); + using var d = "{ foo, foo : bar }".Parse(new ParserOptions { Ignore = options }); _visitor.Visit(d); _visitedAliases.ShouldHaveSingleItem(); } - [Fact] - public void Visit_OneDefinitionWithOneArgument_VisitsOneArgument() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Visit_OneDefinitionWithOneArgument_VisitsOneArgument(IgnoreOptions options) { - using var d = "{ foo(id : 1) { name } }".Parse(); + using var d = "{ foo(id : 1) { name } }".Parse(new ParserOptions { Ignore = options }); _visitor.Visit(d); _visitedArguments.ShouldHaveSingleItem(); } - [Fact] - public void Visit_OneDefinitionWithOneNestedArgument_VisitsOneArgument() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Visit_OneDefinitionWithOneNestedArgument_VisitsOneArgument(IgnoreOptions options) { - using var d = "{ foo{ names(size: 10) } }".Parse(); + using var d = "{ foo{ names(size: 10) } }".Parse(new ParserOptions { Ignore = options }); _visitor.Visit(d); _visitedArguments.ShouldHaveSingleItem(); } - [Fact] - public void Visit_StringValueArgument_VisitsOneStringValue() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Visit_StringValueArgument_VisitsOneStringValue(IgnoreOptions options) { - using var d = "{ stuff(id : \"abc\") }".Parse(); + using var d = "{ stuff(id : \"abc\") }".Parse(new ParserOptions { Ignore = options }); _visitor.Visit(d); _visitedStringValues.ShouldHaveSingleItem(); } - [Fact] - public void Visit_TwoDefinitions_CallsVisitDefinitionTwice() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Visit_TwoDefinitions_CallsVisitDefinitionTwice(IgnoreOptions options) { - using var d = "{ a }\n{ b }".Parse(); + using var d = "{ a }\n{ b }".Parse(new ParserOptions { Ignore = options }); _visitor.Visit(d); _visitedDefinitions.Count.ShouldBe(2); } - [Fact] - public void Visit_TwoFieldSelections_VisitsFieldSelectionTwice() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Visit_TwoFieldSelections_VisitsFieldSelectionTwice(IgnoreOptions options) { - using var d = "{ a, b }".Parse(); + using var d = "{ a, b }".Parse(new ParserOptions { Ignore = options }); _visitor.Visit(d); _visitedFieldSelections.Count.ShouldBe(2); } - [Fact] - public void Visit_TwoFieldSelections_VisitsTwoFieldNames() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Visit_TwoFieldSelections_VisitsTwoFieldNames(IgnoreOptions options) { - using var d = "{ a, b }".Parse(); + using var d = "{ a, b }".Parse(new ParserOptions { Ignore = options }); _visitor.Visit(d); _visitedNames.Count.ShouldBe(2); } - [Fact] - public void Visit_TwoFieldSelections_VisitsTwoFieldNamesAndDefinitionName() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Visit_TwoFieldSelections_VisitsTwoFieldNamesAndDefinitionName(IgnoreOptions options) { - using var d = "query foo { a, b }".Parse(); + using var d = "query foo { a, b }".Parse(new ParserOptions { Ignore = options }); _visitor.Visit(d); _visitedNames.Count.ShouldBe(3); } - [Fact] - public void Visit_TwoFieldSelectionsWithOneNested_VisitsFiveFieldSelections() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Visit_TwoFieldSelectionsWithOneNested_VisitsFiveFieldSelections(IgnoreOptions options) { - using var d = "{a, nested { x, y }, b}".Parse(); + using var d = "{a, nested { x, y }, b}".Parse(new ParserOptions { Ignore = options }); _visitor.Visit(d); _visitedFieldSelections.Count.ShouldBe(5); } - [Fact] - public void Visit_TwoFieldSelectionsWithOneNested_VisitsFiveNames() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Visit_TwoFieldSelectionsWithOneNested_VisitsFiveNames(IgnoreOptions options) { - using var d = "{a, nested { x, y }, b}".Parse(); + using var d = "{a, nested { x, y }, b}".Parse(new ParserOptions { Ignore = options }); _visitor.Visit(d); _visitedNames.Count.ShouldBe(5); diff --git a/src/GraphQLParser.Tests/Issue82.cs b/src/GraphQLParser.Tests/Issue82.cs index e3dad5e6..79f93380 100644 --- a/src/GraphQLParser.Tests/Issue82.cs +++ b/src/GraphQLParser.Tests/Issue82.cs @@ -13,20 +13,23 @@ public class Issue82 } "; - [Fact] - public void Parse_Named_And_Literal_Variables() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Parse_Named_And_Literal_Variables(IgnoreOptions options) { - using var document = _query.Parse(); + using var document = _query.Parse(new ParserOptions { Ignore = options }); var def = document.Definitions[0] as GraphQLOperationDefinition; def.VariableDefinitions.Count.ShouldBe(1); - def.VariableDefinitions[0].Type.ShouldBeOfType().Name.Value.ShouldBe("String"); + def.VariableDefinitions[0].Type.ShouldBeAssignableTo().Name.Value.ShouldBe("String"); def.VariableDefinitions[0].Variable.Name.Value.ShouldBe("username"); - var selection = def.SelectionSet.Selections[0].ShouldBeOfType(); + var selection = def.SelectionSet.Selections[0].ShouldBeAssignableTo(); selection.Arguments.Count.ShouldBe(2); - selection.Arguments[0].Value.ShouldBeOfType().Name.Value.ShouldBe("username"); - selection.Arguments[1].Value.ShouldBeOfType().Value.ShouldBe("Pete"); + selection.Arguments[0].Value.ShouldBeAssignableTo().Name.Value.ShouldBe("username"); + selection.Arguments[1].Value.ShouldBeAssignableTo().Value.ShouldBe("Pete"); } } } diff --git a/src/GraphQLParser.Tests/ParserTests.cs b/src/GraphQLParser.Tests/ParserTests.cs index 991e9a05..f53642f3 100644 --- a/src/GraphQLParser.Tests/ParserTests.cs +++ b/src/GraphQLParser.Tests/ParserTests.cs @@ -12,8 +12,11 @@ public class ParserTests { private static readonly string _nl = Environment.NewLine; - [Fact] - public void Extra_Comments_Should_Read_Correctly() + [Theory] + [InlineData(IgnoreOptions.None)] + //[InlineData(IgnoreOptions.IgnoreComments)] + //[InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Extra_Comments_Should_Read_Correctly(IgnoreOptions options) { const string query = @" query _ { @@ -30,7 +33,7 @@ query _ { #comment4 "; - using var document = query.Parse(false); + using var document = query.Parse(new ParserOptions { Ignore = options }); document.Definitions.Count().ShouldBe(1); // query var def = document.Definitions.First() as GraphQLOperationDefinition; @@ -55,8 +58,11 @@ query _ { document.UnattachedComments[2].Text.ShouldBe("comment4"); } - [Fact] - public void Comments_Can_Be_Ignored() + [Theory] + //[InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Comments_Can_Be_Ignored(IgnoreOptions options) { const string query = @" { @@ -65,7 +71,7 @@ public void Comments_Can_Be_Ignored() # comment2 }"; - var document = query.Parse(); + var document = query.Parse(new ParserOptions { Ignore = options }); document.UnattachedComments.ShouldBeNull(); document.Definitions.Count().ShouldBe(1); var def = document.Definitions.First() as GraphQLOperationDefinition; @@ -75,8 +81,11 @@ public void Comments_Can_Be_Ignored() field.Comment.ShouldBeNull(); } - [Fact] - public void Comments_on_FragmentSpread_Should_Read_Correclty() + [Theory] + [InlineData(IgnoreOptions.None)] + //[InlineData(IgnoreOptions.IgnoreComments)] + //[InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Comments_on_FragmentSpread_Should_Read_Correclty(IgnoreOptions options) { const string query = @" query _ { @@ -90,7 +99,7 @@ fragment human on person { name }"; - using var document = query.Parse(false); + using var document = query.Parse(new ParserOptions { Ignore = options }); document.Definitions.Count.ShouldBe(2); var def = document.Definitions.First() as GraphQLOperationDefinition; def.SelectionSet.Selections.Count.ShouldBe(1); @@ -100,8 +109,11 @@ fragment human on person { fragment.Comment.ShouldNotBeNull().Text.ShouldBe("comment"); } - [Fact] - public void Comments_on_FragmentInline_Should_Read_Correclty() + [Theory] + [InlineData(IgnoreOptions.None)] + //[InlineData(IgnoreOptions.IgnoreComments)] + //[InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Comments_on_FragmentInline_Should_Read_Correclty(IgnoreOptions options) { const string query = @" query _ { @@ -113,7 +125,7 @@ ... on human { } }"; - using var document = query.Parse(false); + using var document = query.Parse(new ParserOptions { Ignore = options }); document.Definitions.Count.ShouldBe(1); var def = document.Definitions.First() as GraphQLOperationDefinition; def.SelectionSet.Selections.Count.ShouldBe(1); @@ -123,8 +135,11 @@ ... on human { fragment.Comment.ShouldNotBeNull().Text.ShouldBe("comment"); } - [Fact] - public void Comments_on_Variable_Should_Read_Correclty() + [Theory] + [InlineData(IgnoreOptions.None)] + //[InlineData(IgnoreOptions.IgnoreComments)] + //[InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Comments_on_Variable_Should_Read_Correclty(IgnoreOptions options) { const string query = @" query _( @@ -138,7 +153,7 @@ query _( } }"; - using var document = query.Parse(false); + using var document = query.Parse(new ParserOptions { Ignore = options }); document.Definitions.Count.ShouldBe(1); var def = document.Definitions.First() as GraphQLOperationDefinition; def.VariableDefinitions.Count.ShouldBe(3); @@ -147,8 +162,11 @@ query _( def.VariableDefinitions.Skip(2).First().Comment.ShouldNotBeNull().Text.ShouldBe("comment3"); } - [Fact] - public void Comments_On_SelectionSet_Should_Read_Correctly() + [Theory] + [InlineData(IgnoreOptions.None)] + //[InlineData(IgnoreOptions.IgnoreComments)] + //[InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Comments_On_SelectionSet_Should_Read_Correctly(IgnoreOptions options) { using var document = @" query { @@ -158,7 +176,7 @@ public void Comments_On_SelectionSet_Should_Read_Correctly() #second comment field3 } -".Parse(false); +".Parse(new ParserOptions { Ignore = options }); document.Definitions.Count.ShouldBe(1); var def = document.Definitions.First() as GraphQLOperationDefinition; def.SelectionSet.Selections.Count.ShouldBe(3); @@ -167,8 +185,11 @@ public void Comments_On_SelectionSet_Should_Read_Correctly() def.SelectionSet.Selections.Skip(2).First().Comment.ShouldNotBeNull().Text.ShouldBe("second comment"); } - [Fact] - public void Comments_On_Enums_Should_Read_Correctly() + [Theory] + [InlineData(IgnoreOptions.None)] + //[InlineData(IgnoreOptions.IgnoreComments)] + //[InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Comments_On_Enums_Should_Read_Correctly(IgnoreOptions options) { using var document = @" # different animals @@ -188,7 +209,7 @@ input Parameter { } scalar JSON -".Parse(false); +".Parse(new ParserOptions { Ignore = options }); document.Definitions.Count.ShouldBe(3); var d1 = document.Definitions.First() as GraphQLEnumTypeDefinition; d1.Name.Value.ShouldBe("Animal"); @@ -206,17 +227,23 @@ scalar JSON d2.Fields.First().Comment.Text.ShouldBe("any value"); } - [Fact] - public void Parse_Unicode_Char_At_EOF_Should_Throw() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Parse_Unicode_Char_At_EOF_Should_Throw(IgnoreOptions options) { - Should.Throw(() => "{\"\\ue }".Parse()); + Should.Throw(() => "{\"\\ue }".Parse(new ParserOptions { Ignore = options })); } - [Fact] - public void Parse_FieldInput_HasCorrectLocations() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + //[InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Parse_FieldInput_HasCorrectLocations(IgnoreOptions options) { // { field } - using var document = ParseGraphQLFieldSource(); + using var document = ParseGraphQLFieldSource(options); document.Location.ShouldBe(new GraphQLLocation(0, 9)); // { field } document.Definitions.First().Location.ShouldBe(new GraphQLLocation(0, 9)); // { field } @@ -224,51 +251,69 @@ public void Parse_FieldInput_HasCorrectLocations() (document.Definitions.First() as GraphQLOperationDefinition).SelectionSet.Selections.First().Location.ShouldBe(new GraphQLLocation(2, 7)); // field } - [Fact] - public void Parse_FieldInput_HasOneOperationDefinition() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Parse_FieldInput_HasOneOperationDefinition(IgnoreOptions options) { - using var document = ParseGraphQLFieldSource(); + using var document = ParseGraphQLFieldSource(options); document.Definitions.First().Kind.ShouldBe(ASTNodeKind.OperationDefinition); } - [Fact] - public void Parse_FieldInput_NameIsNull() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Parse_FieldInput_NameIsNull(IgnoreOptions options) { - using var document = ParseGraphQLFieldSource(); + using var document = ParseGraphQLFieldSource(options); GetSingleOperationDefinition(document).Name.ShouldBeNull(); } - [Fact] - public void Parse_FieldInput_OperationIsQuery() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Parse_FieldInput_OperationIsQuery(IgnoreOptions options) { - using var document = ParseGraphQLFieldSource(); + using var document = ParseGraphQLFieldSource(options); GetSingleOperationDefinition(document).Operation.ShouldBe(OperationType.Query); } - [Fact] - public void Parse_FieldInput_ReturnsDocumentNode() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Parse_FieldInput_ReturnsDocumentNode(IgnoreOptions options) { - using var document = ParseGraphQLFieldSource(); + using var document = ParseGraphQLFieldSource(options); document.Kind.ShouldBe(ASTNodeKind.Document); } - [Fact] - public void Parse_FieldInput_SelectionSetContainsSingleFieldSelection() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Parse_FieldInput_SelectionSetContainsSingleFieldSelection(IgnoreOptions options) { - using var document = ParseGraphQLFieldSource(); + using var document = ParseGraphQLFieldSource(options); GetSingleSelection(document).Kind.ShouldBe(ASTNodeKind.Field); } - [Fact] - public void Parse_FieldWithOperationTypeAndNameInput_HasCorrectLocations() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + //[InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Parse_FieldWithOperationTypeAndNameInput_HasCorrectLocations(IgnoreOptions options) { // mutation Foo { field } - using var document = ParseGraphQLFieldWithOperationTypeAndNameSource(); + using var document = ParseGraphQLFieldWithOperationTypeAndNameSource(options); document.Location.ShouldBe(new GraphQLLocation(0, 22)); document.Definitions.First().Location.ShouldBe(new GraphQLLocation(0, 22)); @@ -277,50 +322,68 @@ public void Parse_FieldWithOperationTypeAndNameInput_HasCorrectLocations() (document.Definitions.First() as GraphQLOperationDefinition).SelectionSet.Selections.First().Location.ShouldBe(new GraphQLLocation(15, 20)); // field } - [Fact] - public void Parse_FieldWithOperationTypeAndNameInput_HasOneOperationDefinition() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Parse_FieldWithOperationTypeAndNameInput_HasOneOperationDefinition(IgnoreOptions options) { - using var document = ParseGraphQLFieldWithOperationTypeAndNameSource(); + using var document = ParseGraphQLFieldWithOperationTypeAndNameSource(options); document.Definitions.First().Kind.ShouldBe(ASTNodeKind.OperationDefinition); } - [Fact] - public void Parse_FieldWithOperationTypeAndNameInput_NameIsNull() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Parse_FieldWithOperationTypeAndNameInput_NameIsNull(IgnoreOptions options) { - using var document = ParseGraphQLFieldWithOperationTypeAndNameSource(); + using var document = ParseGraphQLFieldWithOperationTypeAndNameSource(options); GetSingleOperationDefinition(document).Name.Value.ShouldBe("Foo"); } - [Fact] - public void Parse_FieldWithOperationTypeAndNameInput_OperationIsQuery() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Parse_FieldWithOperationTypeAndNameInput_OperationIsQuery(IgnoreOptions options) { - using var document = ParseGraphQLFieldWithOperationTypeAndNameSource(); + using var document = ParseGraphQLFieldWithOperationTypeAndNameSource(options); GetSingleOperationDefinition(document).Operation.ShouldBe(OperationType.Mutation); } - [Fact] - public void Parse_FieldWithOperationTypeAndNameInput_ReturnsDocumentNode() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Parse_FieldWithOperationTypeAndNameInput_ReturnsDocumentNode(IgnoreOptions options) { - using var document = ParseGraphQLFieldWithOperationTypeAndNameSource(); + using var document = ParseGraphQLFieldWithOperationTypeAndNameSource(options); document.Kind.ShouldBe(ASTNodeKind.Document); } - [Fact] - public void Parse_FieldWithOperationTypeAndNameInput_SelectionSetContainsSingleFieldWithOperationTypeAndNameSelection() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Parse_FieldWithOperationTypeAndNameInput_SelectionSetContainsSingleFieldWithOperationTypeAndNameSelection(IgnoreOptions options) { - using var document = ParseGraphQLFieldWithOperationTypeAndNameSource(); + using var document = ParseGraphQLFieldWithOperationTypeAndNameSource(options); GetSingleSelection(document).Kind.ShouldBe(ASTNodeKind.Field); } - [Fact] - public void Parse_KitchenSink_DoesNotThrowError() + [Theory] + [InlineData(IgnoreOptions.None)] + //[InlineData(IgnoreOptions.IgnoreComments)] + //[InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Parse_KitchenSink_DoesNotThrowError(IgnoreOptions options) { - using var document = LoadKitchenSink().Parse(false); + using var document = LoadKitchenSink().Parse(new ParserOptions { Ignore = options }); var typeDef = document.Definitions.OfType().First(d => d.Name.Value == "Foo"); var fieldDef = typeDef.Fields.First(d => d.Name.Value == "three"); fieldDef.Comment.ShouldNotBeNull().Text.ShouldBe($" multiline comments{_nl} with very importand description #{_nl} # and symbol # and ##"); @@ -332,22 +395,73 @@ public void Parse_KitchenSink_DoesNotThrowError() ((string)comment.Text).StartsWith(" Copyright (c) 2015, Facebook, Inc.").ShouldBeTrue(); } - [Fact] - public void Parse_NullInput_EmptyDocument() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Parse_NullInput_EmptyDocument(IgnoreOptions options) { - using var document = ((string)null).Parse(); + using var document = ((string)null).Parse(new ParserOptions { Ignore = options }); document.Definitions.ShouldBeEmpty(); } - [Fact] - public void Parse_VariableInlineValues_DoesNotThrowError() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Parse_VariableInlineValues_DoesNotThrowError(IgnoreOptions options) { - using ("{ field(complex: { a: { b: [ $var ] } }) }".Parse()) + using ("{ field(complex: { a: { b: [ $var ] } }) }".Parse(new ParserOptions { Ignore = options })) { } } + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Parse_Empty_Field_Arguments_Should_Throw(IgnoreOptions options) + { + Should.Throw(() => "{ a() }".Parse(new ParserOptions { Ignore = options })); + } + + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Parse_Empty_Directive_Arguments_Should_Throw(IgnoreOptions options) + { + Should.Throw(() => "directive @dir() on FIELD_DEFINITION".Parse(new ParserOptions { Ignore = options })); + } + + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Parse_Empty_Enum_Values_Should_Throw(IgnoreOptions options) + { + Should.Throw(() => "enum Empty { }".Parse(new ParserOptions { Ignore = options })); + } + + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Parse_Empty_SelectionSet_Should_Throw(IgnoreOptions options) + { + Should.Throw(() => "{ a { } }".Parse(new ParserOptions { Ignore = options })); + } + + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Parse_Empty_VariableDefinitions_Should_Throw(IgnoreOptions options) + { + Should.Throw(() => "query test() { a }".Parse(new ParserOptions { Ignore = options })); + } + private static GraphQLOperationDefinition GetSingleOperationDefinition(GraphQLDocument document) { return (GraphQLOperationDefinition)document.Definitions.Single(); @@ -514,9 +628,9 @@ on FIELD | INLINE_FRAGMENT"; } - private static GraphQLDocument ParseGraphQLFieldSource() => "{ field }".Parse(); + private static GraphQLDocument ParseGraphQLFieldSource(IgnoreOptions options) => "{ field }".Parse(new ParserOptions { Ignore = options }); - private static GraphQLDocument ParseGraphQLFieldWithOperationTypeAndNameSource() => "mutation Foo { field }".Parse(); + private static GraphQLDocument ParseGraphQLFieldWithOperationTypeAndNameSource(IgnoreOptions options) => "mutation Foo { field }".Parse(new ParserOptions { Ignore = options }); [Theory] [InlineData("directive @dir repeatable on FIELD_DEFINITION", true)] @@ -531,7 +645,7 @@ on FIELD [InlineData(@"directive @dir on | FIELD_DEFINITION | ENUM_VALUE", false)] - [InlineData(@"directive @dir on + [InlineData(@"directive @dir on | FIELD_DEFINITION | ENUM_VALUE", false)] public void Should_Parse_Directives(string text, bool repeatable) @@ -539,7 +653,7 @@ public void Should_Parse_Directives(string text, bool repeatable) using var document = text.Parse(); document.ShouldNotBeNull(); document.Definitions.Count.ShouldBe(1); - document.Definitions[0].ShouldBeOfType().Repeatable.ShouldBe(repeatable); + document.Definitions[0].ShouldBeAssignableTo().Repeatable.ShouldBe(repeatable); } [Theory] diff --git a/src/GraphQLParser.Tests/Validation/ParserValidationTests.cs b/src/GraphQLParser.Tests/Validation/ParserValidationTests.cs index 40ee5f7c..ec1f689e 100644 --- a/src/GraphQLParser.Tests/Validation/ParserValidationTests.cs +++ b/src/GraphQLParser.Tests/Validation/ParserValidationTests.cs @@ -6,10 +6,13 @@ namespace GraphQLParser.Tests.Validation { public class ParserValidationTests { - [Fact] - public void Parse_FragmentInvalidOnName_ThrowsExceptionWithCorrectMessage() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Parse_FragmentInvalidOnName_ThrowsExceptionWithCorrectMessage(IgnoreOptions options) { - var exception = Should.Throw(() => "fragment on on on { on }".Parse()); + var exception = Should.Throw(() => "fragment on on on { on }".Parse(new ParserOptions { Ignore = options })); exception.Message.ShouldBe( "Syntax Error GraphQL (1:10) Unexpected Name \"on\"\n" + @@ -20,10 +23,13 @@ public void Parse_FragmentInvalidOnName_ThrowsExceptionWithCorrectMessage() exception.Column.ShouldBe(10); } - [Fact] - public void Parse_InvalidDefaultValue_ThrowsExceptionWithCorrectMessage() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Parse_InvalidDefaultValue_ThrowsExceptionWithCorrectMessage(IgnoreOptions options) { - var exception = Should.Throw(() => "query Foo($x: Complex = { a: { b: [ $var ] } }) { field }".Parse()); + var exception = Should.Throw(() => "query Foo($x: Complex = { a: { b: [ $var ] } }) { field }".Parse(new ParserOptions { Ignore = options })); exception.Message.ShouldBe( "Syntax Error GraphQL (1:37) Unexpected $\n" + @@ -34,10 +40,13 @@ public void Parse_InvalidDefaultValue_ThrowsExceptionWithCorrectMessage() exception.Column.ShouldBe(37); } - [Fact] - public void Parse_InvalidFragmentNameInSpread_ThrowsExceptionWithCorrectMessage() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Parse_InvalidFragmentNameInSpread_ThrowsExceptionWithCorrectMessage(IgnoreOptions options) { - var exception = Should.Throw(() => "{ ...on }".Parse()); + var exception = Should.Throw(() => "{ ...on }".Parse(new ParserOptions { Ignore = options })); exception.Message.ShouldBe( "Syntax Error GraphQL (1:9) Expected Name, found }\n" + @@ -48,10 +57,13 @@ public void Parse_InvalidFragmentNameInSpread_ThrowsExceptionWithCorrectMessage( exception.Column.ShouldBe(9); } - [Fact] - public void Parse_LonelySpread_ThrowsExceptionWithCorrectMessage() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Parse_LonelySpread_ThrowsExceptionWithCorrectMessage(IgnoreOptions options) { - var exception = Should.Throw(() => "...".Parse()); + var exception = Should.Throw(() => "...".Parse(new ParserOptions { Ignore = options })); exception.Message.ShouldBe( "Syntax Error GraphQL (1:1) Unexpected ...\n" + @@ -62,10 +74,13 @@ public void Parse_LonelySpread_ThrowsExceptionWithCorrectMessage() exception.Column.ShouldBe(1); } - [Fact] - public void Parse_MissingEndingBrace_ThrowsExceptionWithCorrectMessage() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Parse_MissingEndingBrace_ThrowsExceptionWithCorrectMessage(IgnoreOptions options) { - var exception = Should.Throw(() => "{".Parse()); + var exception = Should.Throw(() => "{".Parse(new ParserOptions { Ignore = options })); exception.Message.ShouldBe( "Syntax Error GraphQL (1:2) Expected Name, found EOF\n" + @@ -76,10 +91,13 @@ public void Parse_MissingEndingBrace_ThrowsExceptionWithCorrectMessage() exception.Column.ShouldBe(2); } - [Fact] - public void Parse_MissingFieldNameWhenAliasing_ThrowsExceptionWithCorrectMessage() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Parse_MissingFieldNameWhenAliasing_ThrowsExceptionWithCorrectMessage(IgnoreOptions options) { - var exception = Should.Throw(() => "{ field: {} }".Parse()); + var exception = Should.Throw(() => "{ field: {} }".Parse(new ParserOptions { Ignore = options })); exception.Message.ShouldBe( "Syntax Error GraphQL (1:10) Expected Name, found {\n" + @@ -90,10 +108,13 @@ public void Parse_MissingFieldNameWhenAliasing_ThrowsExceptionWithCorrectMessage exception.Column.ShouldBe(10); } - [Fact] - public void Parse_MissingFragmentType_ThrowsExceptionWithCorrectMessage() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Parse_MissingFragmentType_ThrowsExceptionWithCorrectMessage(IgnoreOptions options) { - var exception = Should.Throw(() => "{ ...MissingOn }\nfragment MissingOn Type".Parse()); + var exception = Should.Throw(() => "{ ...MissingOn }\nfragment MissingOn Type".Parse(new ParserOptions { Ignore = options })); exception.Message.ShouldBe( "Syntax Error GraphQL (2:20) Expected \"on\", found Name \"Type\"\n" + @@ -105,10 +126,13 @@ public void Parse_MissingFragmentType_ThrowsExceptionWithCorrectMessage() exception.Column.ShouldBe(20); } - [Fact] - public void Parse_UnknownOperation_ThrowsExceptionWithCorrectMessage() + [Theory] + [InlineData(IgnoreOptions.None)] + [InlineData(IgnoreOptions.IgnoreComments)] + [InlineData(IgnoreOptions.IgnoreCommentsAndLocations)] + public void Parse_UnknownOperation_ThrowsExceptionWithCorrectMessage(IgnoreOptions options) { - var exception = Should.Throw(() => "notanoperation Foo { field }".Parse()); + var exception = Should.Throw(() => "notanoperation Foo { field }".Parse(new ParserOptions { Ignore = options })); exception.Message.ShouldBe( "Syntax Error GraphQL (1:1) Unexpected Name " + "\"notanoperation\"\n" + diff --git a/src/GraphQLParser/AST/ASTNode.cs b/src/GraphQLParser/AST/ASTNode.cs index aea2916c..026c302b 100644 --- a/src/GraphQLParser/AST/ASTNode.cs +++ b/src/GraphQLParser/AST/ASTNode.cs @@ -13,11 +13,11 @@ public abstract class ASTNode /// /// Location of a node within a document's original text. /// - public GraphQLLocation Location { get; set; } + public virtual GraphQLLocation Location { get => default; set { } } /// /// Comments for this node if any. /// - public GraphQLComment? Comment { get; set; } + public virtual GraphQLComment? Comment { get => default; set { } } } } diff --git a/src/GraphQLParser/AST/GraphQLArgument.cs b/src/GraphQLParser/AST/GraphQLArgument.cs index 8a83ebd0..a275f6fb 100644 --- a/src/GraphQLParser/AST/GraphQLArgument.cs +++ b/src/GraphQLParser/AST/GraphQLArgument.cs @@ -9,4 +9,22 @@ public class GraphQLArgument : ASTNode, INamedNode public GraphQLValue? Value { get; set; } } + + internal sealed class GraphQLArgumentFull : GraphQLArgument + { + private GraphQLLocation _location; + private GraphQLComment? _comment; + + public override GraphQLLocation Location + { + get => _location; + set => _location = value; + } + + public override GraphQLComment? Comment + { + get => _comment; + set => _comment = value; + } + } } diff --git a/src/GraphQLParser/AST/GraphQLComment.cs b/src/GraphQLParser/AST/GraphQLComment.cs index 2afd7b8e..08755534 100644 --- a/src/GraphQLParser/AST/GraphQLComment.cs +++ b/src/GraphQLParser/AST/GraphQLComment.cs @@ -6,6 +6,9 @@ namespace GraphQLParser.AST [DebuggerDisplay("{Text}")] public class GraphQLComment : ASTNode { + private GraphQLLocation _location; + //private GraphQLComment? _comment; + /// public override ASTNodeKind Kind => ASTNodeKind.Comment; @@ -13,5 +16,18 @@ public class GraphQLComment : ASTNode /// Comment value represented as . /// public ROM Text { get; set; } + + public override GraphQLLocation Location + { + get => _location; + set => _location = value; + } + + // Comment itself can't have a comment + //public override GraphQLComment? Comment + //{ + // get => _comment; + // set => _comment = value; + //} } } diff --git a/src/GraphQLParser/AST/GraphQLDirective.cs b/src/GraphQLParser/AST/GraphQLDirective.cs index 5a5d388c..acc5e2b4 100644 --- a/src/GraphQLParser/AST/GraphQLDirective.cs +++ b/src/GraphQLParser/AST/GraphQLDirective.cs @@ -11,4 +11,23 @@ public class GraphQLDirective : ASTNode, INamedNode public GraphQLName? Name { get; set; } } + + internal sealed class GraphQLDirectiveFull : GraphQLDirective + { + private GraphQLLocation _location; + //private GraphQLComment? _comment; + + public override GraphQLLocation Location + { + get => _location; + set => _location = value; + } + + // TODO: this property is not set anywhere (yet), so it makes no sense to create a field for it + //public override GraphQLComment? Comment + //{ + // get => _comment; + // set => _comment = value; + //} + } } diff --git a/src/GraphQLParser/AST/GraphQLDirectiveDefinition.cs b/src/GraphQLParser/AST/GraphQLDirectiveDefinition.cs index 0163d1ad..dc6adc5a 100644 --- a/src/GraphQLParser/AST/GraphQLDirectiveDefinition.cs +++ b/src/GraphQLParser/AST/GraphQLDirectiveDefinition.cs @@ -15,4 +15,22 @@ public class GraphQLDirectiveDefinition : GraphQLTypeDefinition public bool Repeatable { get; set; } } + + internal sealed class GraphQLDirectiveDefinitionFull : GraphQLDirectiveDefinition + { + private GraphQLLocation _location; + private GraphQLComment? _comment; + + public override GraphQLLocation Location + { + get => _location; + set => _location = value; + } + + public override GraphQLComment? Comment + { + get => _comment; + set => _comment = value; + } + } } diff --git a/src/GraphQLParser/AST/GraphQLDocument.cs b/src/GraphQLParser/AST/GraphQLDocument.cs index e6caca1d..7962a2c1 100644 --- a/src/GraphQLParser/AST/GraphQLDocument.cs +++ b/src/GraphQLParser/AST/GraphQLDocument.cs @@ -47,4 +47,23 @@ public void Dispose() Dispose(true); } } + + internal sealed class GraphQLDocumentFull : GraphQLDocument + { + private GraphQLLocation _location; + //private GraphQLComment? _comment; + + public override GraphQLLocation Location + { + get => _location; + set => _location = value; + } + + // TODO: this property is not set anywhere (yet), so it makes no sense to create a field for it + //public override GraphQLComment? Comment + //{ + // get => _comment; + // set => _comment = value; + //} + } } diff --git a/src/GraphQLParser/AST/GraphQLEnumTypeDefinition.cs b/src/GraphQLParser/AST/GraphQLEnumTypeDefinition.cs index 181bf2b5..d0cbcec5 100644 --- a/src/GraphQLParser/AST/GraphQLEnumTypeDefinition.cs +++ b/src/GraphQLParser/AST/GraphQLEnumTypeDefinition.cs @@ -11,4 +11,22 @@ public class GraphQLEnumTypeDefinition : GraphQLTypeDefinition, IHasDirectivesNo public List? Values { get; set; } } + + internal sealed class GraphQLEnumTypeDefinitionFull : GraphQLEnumTypeDefinition + { + private GraphQLLocation _location; + private GraphQLComment? _comment; + + public override GraphQLLocation Location + { + get => _location; + set => _location = value; + } + + public override GraphQLComment? Comment + { + get => _comment; + set => _comment = value; + } + } } diff --git a/src/GraphQLParser/AST/GraphQLEnumValueDefinition.cs b/src/GraphQLParser/AST/GraphQLEnumValueDefinition.cs index 48f96cd4..41d292e5 100644 --- a/src/GraphQLParser/AST/GraphQLEnumValueDefinition.cs +++ b/src/GraphQLParser/AST/GraphQLEnumValueDefinition.cs @@ -9,4 +9,22 @@ public class GraphQLEnumValueDefinition : GraphQLTypeDefinition, IHasDirectivesN /// public override ASTNodeKind Kind => ASTNodeKind.EnumValueDefinition; } + + internal sealed class GraphQLEnumValueDefinitionFull : GraphQLEnumValueDefinition + { + private GraphQLLocation _location; + private GraphQLComment? _comment; + + public override GraphQLLocation Location + { + get => _location; + set => _location = value; + } + + public override GraphQLComment? Comment + { + get => _comment; + set => _comment = value; + } + } } diff --git a/src/GraphQLParser/AST/GraphQLFieldDefinition.cs b/src/GraphQLParser/AST/GraphQLFieldDefinition.cs index bcfc46eb..9b6a4e53 100644 --- a/src/GraphQLParser/AST/GraphQLFieldDefinition.cs +++ b/src/GraphQLParser/AST/GraphQLFieldDefinition.cs @@ -13,4 +13,22 @@ public class GraphQLFieldDefinition : GraphQLTypeDefinition, IHasDirectivesNode public GraphQLType? Type { get; set; } } + + internal sealed class GraphQLFieldDefinitionFull : GraphQLFieldDefinition + { + private GraphQLLocation _location; + private GraphQLComment? _comment; + + public override GraphQLLocation Location + { + get => _location; + set => _location = value; + } + + public override GraphQLComment? Comment + { + get => _comment; + set => _comment = value; + } + } } diff --git a/src/GraphQLParser/AST/GraphQLFieldSelection.cs b/src/GraphQLParser/AST/GraphQLFieldSelection.cs index 7092762a..439f4fc1 100644 --- a/src/GraphQLParser/AST/GraphQLFieldSelection.cs +++ b/src/GraphQLParser/AST/GraphQLFieldSelection.cs @@ -17,4 +17,22 @@ public class GraphQLFieldSelection : ASTNode, IHasDirectivesNode, INamedNode public GraphQLSelectionSet? SelectionSet { get; set; } } + + internal sealed class GraphQLFieldSelectionFull : GraphQLFieldSelection + { + private GraphQLLocation _location; + private GraphQLComment? _comment; + + public override GraphQLLocation Location + { + get => _location; + set => _location = value; + } + + public override GraphQLComment? Comment + { + get => _comment; + set => _comment = value; + } + } } diff --git a/src/GraphQLParser/AST/GraphQLFragmentDefinition.cs b/src/GraphQLParser/AST/GraphQLFragmentDefinition.cs index 0a551600..0b26b750 100644 --- a/src/GraphQLParser/AST/GraphQLFragmentDefinition.cs +++ b/src/GraphQLParser/AST/GraphQLFragmentDefinition.cs @@ -7,4 +7,22 @@ public class GraphQLFragmentDefinition : GraphQLInlineFragment, INamedNode public GraphQLName? Name { get; set; } } + + internal sealed class GraphQLFragmentDefinitionFull : GraphQLFragmentDefinition + { + private GraphQLLocation _location; + private GraphQLComment? _comment; + + public override GraphQLLocation Location + { + get => _location; + set => _location = value; + } + + public override GraphQLComment? Comment + { + get => _comment; + set => _comment = value; + } + } } diff --git a/src/GraphQLParser/AST/GraphQLFragmentSpread.cs b/src/GraphQLParser/AST/GraphQLFragmentSpread.cs index 1c70ed68..bf85b418 100644 --- a/src/GraphQLParser/AST/GraphQLFragmentSpread.cs +++ b/src/GraphQLParser/AST/GraphQLFragmentSpread.cs @@ -11,4 +11,22 @@ public class GraphQLFragmentSpread : ASTNode, IHasDirectivesNode, INamedNode public GraphQLName? Name { get; set; } } + + internal sealed class GraphQLFragmentSpreadFull : GraphQLFragmentSpread + { + private GraphQLLocation _location; + private GraphQLComment? _comment; + + public override GraphQLLocation Location + { + get => _location; + set => _location = value; + } + + public override GraphQLComment? Comment + { + get => _comment; + set => _comment = value; + } + } } diff --git a/src/GraphQLParser/AST/GraphQLInlineFragment.cs b/src/GraphQLParser/AST/GraphQLInlineFragment.cs index 6086ebd1..846c3c00 100644 --- a/src/GraphQLParser/AST/GraphQLInlineFragment.cs +++ b/src/GraphQLParser/AST/GraphQLInlineFragment.cs @@ -13,4 +13,22 @@ public class GraphQLInlineFragment : ASTNode, IHasDirectivesNode public GraphQLNamedType? TypeCondition { get; set; } } + + internal sealed class GraphQLInlineFragmentFull : GraphQLInlineFragment + { + private GraphQLLocation _location; + private GraphQLComment? _comment; + + public override GraphQLLocation Location + { + get => _location; + set => _location = value; + } + + public override GraphQLComment? Comment + { + get => _comment; + set => _comment = value; + } + } } diff --git a/src/GraphQLParser/AST/GraphQLInputObjectTypeDefinition.cs b/src/GraphQLParser/AST/GraphQLInputObjectTypeDefinition.cs index 37f9117e..4192a18a 100644 --- a/src/GraphQLParser/AST/GraphQLInputObjectTypeDefinition.cs +++ b/src/GraphQLParser/AST/GraphQLInputObjectTypeDefinition.cs @@ -11,4 +11,22 @@ public class GraphQLInputObjectTypeDefinition : GraphQLTypeDefinition, IHasDirec /// public override ASTNodeKind Kind => ASTNodeKind.InputObjectTypeDefinition; } + + internal sealed class GraphQLInputObjectTypeDefinitionFull : GraphQLInputObjectTypeDefinition + { + private GraphQLLocation _location; + private GraphQLComment? _comment; + + public override GraphQLLocation Location + { + get => _location; + set => _location = value; + } + + public override GraphQLComment? Comment + { + get => _comment; + set => _comment = value; + } + } } diff --git a/src/GraphQLParser/AST/GraphQLInputValueDefinition.cs b/src/GraphQLParser/AST/GraphQLInputValueDefinition.cs index d2cf3f7e..abe27ca3 100644 --- a/src/GraphQLParser/AST/GraphQLInputValueDefinition.cs +++ b/src/GraphQLParser/AST/GraphQLInputValueDefinition.cs @@ -13,4 +13,22 @@ public class GraphQLInputValueDefinition : GraphQLTypeDefinition, IHasDirectives public GraphQLType? Type { get; set; } } + + internal sealed class GraphQLInputValueDefinitionFull : GraphQLInputValueDefinition + { + private GraphQLLocation _location; + private GraphQLComment? _comment; + + public override GraphQLLocation Location + { + get => _location; + set => _location = value; + } + + public override GraphQLComment? Comment + { + get => _comment; + set => _comment = value; + } + } } diff --git a/src/GraphQLParser/AST/GraphQLInterfaceTypeDefinition.cs b/src/GraphQLParser/AST/GraphQLInterfaceTypeDefinition.cs index db4cb9a4..395e0be9 100644 --- a/src/GraphQLParser/AST/GraphQLInterfaceTypeDefinition.cs +++ b/src/GraphQLParser/AST/GraphQLInterfaceTypeDefinition.cs @@ -11,4 +11,22 @@ public class GraphQLInterfaceTypeDefinition : GraphQLTypeDefinition, IHasDirecti /// public override ASTNodeKind Kind => ASTNodeKind.InterfaceTypeDefinition; } + + internal sealed class GraphQLInterfaceTypeDefinitionFull : GraphQLInterfaceTypeDefinition + { + private GraphQLLocation _location; + private GraphQLComment? _comment; + + public override GraphQLLocation Location + { + get => _location; + set => _location = value; + } + + public override GraphQLComment? Comment + { + get => _comment; + set => _comment = value; + } + } } diff --git a/src/GraphQLParser/AST/GraphQLListType.cs b/src/GraphQLParser/AST/GraphQLListType.cs index fcb9687b..a0fbc780 100644 --- a/src/GraphQLParser/AST/GraphQLListType.cs +++ b/src/GraphQLParser/AST/GraphQLListType.cs @@ -10,4 +10,23 @@ public class GraphQLListType : GraphQLType /// public override string ToString() => $"[{Type}]"; } + + internal sealed class GraphQLListTypeFull : GraphQLListType + { + private GraphQLLocation _location; + //private GraphQLComment? _comment; + + public override GraphQLLocation Location + { + get => _location; + set => _location = value; + } + + // TODO: this property is not set anywhere (yet), so it makes no sense to create a field for it + //public override GraphQLComment? Comment + //{ + // get => _comment; + // set => _comment = value; + //} + } } diff --git a/src/GraphQLParser/AST/GraphQLListValue.cs b/src/GraphQLParser/AST/GraphQLListValue.cs index 8e6e7b2f..15f169ad 100644 --- a/src/GraphQLParser/AST/GraphQLListValue.cs +++ b/src/GraphQLParser/AST/GraphQLListValue.cs @@ -21,4 +21,28 @@ public GraphQLListValue(ASTNodeKind kind) /// public override string ToString() => AstValue.ToString(); } + + internal sealed class GraphQLListValueFull : GraphQLListValue + { + private GraphQLLocation _location; + //private GraphQLComment? _comment; + + public GraphQLListValueFull(ASTNodeKind kind) + : base(kind) + { + } + + public override GraphQLLocation Location + { + get => _location; + set => _location = value; + } + + // TODO: this property is not set anywhere (yet), so it makes no sense to create a field for it + //public override GraphQLComment? Comment + //{ + // get => _comment; + // set => _comment = value; + //} + } } diff --git a/src/GraphQLParser/AST/GraphQLName.cs b/src/GraphQLParser/AST/GraphQLName.cs index e28c1830..fa88454b 100644 --- a/src/GraphQLParser/AST/GraphQLName.cs +++ b/src/GraphQLParser/AST/GraphQLName.cs @@ -14,4 +14,23 @@ public class GraphQLName : ASTNode /// public ROM Value { get; set; } } + + internal sealed class GraphQLNameFull : GraphQLName + { + private GraphQLLocation _location; + //private GraphQLComment? _comment; + + public override GraphQLLocation Location + { + get => _location; + set => _location = value; + } + + // TODO: this property is not set anywhere (yet), so it makes no sense to create a field for it + //public override GraphQLComment? Comment + //{ + // get => _comment; + // set => _comment = value; + //} + } } diff --git a/src/GraphQLParser/AST/GraphQLNamedType.cs b/src/GraphQLParser/AST/GraphQLNamedType.cs index ed3543f4..32d6943c 100644 --- a/src/GraphQLParser/AST/GraphQLNamedType.cs +++ b/src/GraphQLParser/AST/GraphQLNamedType.cs @@ -10,4 +10,23 @@ public class GraphQLNamedType : GraphQLType, INamedNode /// public override string ToString() => Name?.Value.ToString()!; } + + internal sealed class GraphQLNamedTypeFull : GraphQLNamedType + { + private GraphQLLocation _location; + //private GraphQLComment? _comment; + + public override GraphQLLocation Location + { + get => _location; + set => _location = value; + } + + // TODO: this property is not set anywhere (yet), so it makes no sense to create a field for it + //public override GraphQLComment? Comment + //{ + // get => _comment; + // set => _comment = value; + //} + } } diff --git a/src/GraphQLParser/AST/GraphQLNonNullType.cs b/src/GraphQLParser/AST/GraphQLNonNullType.cs index 450040f0..c47b9c19 100644 --- a/src/GraphQLParser/AST/GraphQLNonNullType.cs +++ b/src/GraphQLParser/AST/GraphQLNonNullType.cs @@ -10,4 +10,23 @@ public class GraphQLNonNullType : GraphQLType /// public override string ToString() => Type + "!"; } + + internal sealed class GraphQLNonNullTypeFull : GraphQLNonNullType + { + private GraphQLLocation _location; + //private GraphQLComment? _comment; + + public override GraphQLLocation Location + { + get => _location; + set => _location = value; + } + + // TODO: this property is not set anywhere (yet), so it makes no sense to create a field for it + //public override GraphQLComment? Comment + //{ + // get => _comment; + // set => _comment = value; + //} + } } diff --git a/src/GraphQLParser/AST/GraphQLObjectField.cs b/src/GraphQLParser/AST/GraphQLObjectField.cs index 0ffd2662..96fa465e 100644 --- a/src/GraphQLParser/AST/GraphQLObjectField.cs +++ b/src/GraphQLParser/AST/GraphQLObjectField.cs @@ -9,4 +9,22 @@ public class GraphQLObjectField : ASTNode, INamedNode public GraphQLValue? Value { get; set; } } + + internal sealed class GraphQLObjectFieldFull : GraphQLObjectField + { + private GraphQLLocation _location; + private GraphQLComment? _comment; + + public override GraphQLLocation Location + { + get => _location; + set => _location = value; + } + + public override GraphQLComment? Comment + { + get => _comment; + set => _comment = value; + } + } } diff --git a/src/GraphQLParser/AST/GraphQLObjectTypeDefinition.cs b/src/GraphQLParser/AST/GraphQLObjectTypeDefinition.cs index 15c497ea..45f29096 100644 --- a/src/GraphQLParser/AST/GraphQLObjectTypeDefinition.cs +++ b/src/GraphQLParser/AST/GraphQLObjectTypeDefinition.cs @@ -13,4 +13,22 @@ public class GraphQLObjectTypeDefinition : GraphQLTypeDefinition, IHasDirectives /// public override ASTNodeKind Kind => ASTNodeKind.ObjectTypeDefinition; } + + internal sealed class GraphQLObjectTypeDefinitionFull : GraphQLObjectTypeDefinition + { + private GraphQLLocation _location; + private GraphQLComment? _comment; + + public override GraphQLLocation Location + { + get => _location; + set => _location = value; + } + + public override GraphQLComment? Comment + { + get => _comment; + set => _comment = value; + } + } } diff --git a/src/GraphQLParser/AST/GraphQLObjectValue.cs b/src/GraphQLParser/AST/GraphQLObjectValue.cs index 08d09f92..c42aaa7c 100644 --- a/src/GraphQLParser/AST/GraphQLObjectValue.cs +++ b/src/GraphQLParser/AST/GraphQLObjectValue.cs @@ -9,4 +9,22 @@ public class GraphQLObjectValue : GraphQLValue /// public override ASTNodeKind Kind => ASTNodeKind.ObjectValue; } + + internal sealed class GraphQLObjectValueFull : GraphQLObjectValue + { + private GraphQLLocation _location; + private GraphQLComment? _comment; + + public override GraphQLLocation Location + { + get => _location; + set => _location = value; + } + + public override GraphQLComment? Comment + { + get => _comment; + set => _comment = value; + } + } } diff --git a/src/GraphQLParser/AST/GraphQLOperationDefinition.cs b/src/GraphQLParser/AST/GraphQLOperationDefinition.cs index 7f3f9009..62bcb46e 100644 --- a/src/GraphQLParser/AST/GraphQLOperationDefinition.cs +++ b/src/GraphQLParser/AST/GraphQLOperationDefinition.cs @@ -17,4 +17,22 @@ public class GraphQLOperationDefinition : ASTNode, IHasDirectivesNode, INamedNod public List? VariableDefinitions { get; set; } } + + internal sealed class GraphQLOperationDefinitionFull : GraphQLOperationDefinition + { + private GraphQLLocation _location; + private GraphQLComment? _comment; + + public override GraphQLLocation Location + { + get => _location; + set => _location = value; + } + + public override GraphQLComment? Comment + { + get => _comment; + set => _comment = value; + } + } } diff --git a/src/GraphQLParser/AST/GraphQLOperationTypeDefinition.cs b/src/GraphQLParser/AST/GraphQLOperationTypeDefinition.cs index 569dd2aa..7661052b 100644 --- a/src/GraphQLParser/AST/GraphQLOperationTypeDefinition.cs +++ b/src/GraphQLParser/AST/GraphQLOperationTypeDefinition.cs @@ -9,4 +9,23 @@ public class GraphQLOperationTypeDefinition : ASTNode public GraphQLNamedType? Type { get; set; } } + + internal sealed class GraphQLOperationTypeDefinitionFull : GraphQLOperationTypeDefinition + { + private GraphQLLocation _location; + //private GraphQLComment? _comment; + + public override GraphQLLocation Location + { + get => _location; + set => _location = value; + } + + // TODO: this property is not set anywhere (yet), so it makes no sense to create a field for it + //public override GraphQLComment? Comment + //{ + // get => _comment; + // set => _comment = value; + //} + } } diff --git a/src/GraphQLParser/AST/GraphQLScalarTypeDefinition.cs b/src/GraphQLParser/AST/GraphQLScalarTypeDefinition.cs index 707978f7..7d122f8f 100644 --- a/src/GraphQLParser/AST/GraphQLScalarTypeDefinition.cs +++ b/src/GraphQLParser/AST/GraphQLScalarTypeDefinition.cs @@ -9,4 +9,22 @@ public class GraphQLScalarTypeDefinition : GraphQLTypeDefinition, IHasDirectives /// public override ASTNodeKind Kind => ASTNodeKind.ScalarTypeDefinition; } + + internal sealed class GraphQLScalarTypeDefinitionFull : GraphQLScalarTypeDefinition + { + private GraphQLLocation _location; + private GraphQLComment? _comment; + + public override GraphQLLocation Location + { + get => _location; + set => _location = value; + } + + public override GraphQLComment? Comment + { + get => _comment; + set => _comment = value; + } + } } diff --git a/src/GraphQLParser/AST/GraphQLScalarValue.cs b/src/GraphQLParser/AST/GraphQLScalarValue.cs index ff4f4acc..0077cf2e 100644 --- a/src/GraphQLParser/AST/GraphQLScalarValue.cs +++ b/src/GraphQLParser/AST/GraphQLScalarValue.cs @@ -44,4 +44,28 @@ public GraphQLScalarValue(ASTNodeKind kind) /// public override string? ToString() => Kind == ASTNodeKind.StringValue ? $"\"{Value}\"" : Value.ToString(); } + + internal sealed class GraphQLScalarValueFull : GraphQLScalarValue + { + private GraphQLLocation _location; + //private GraphQLComment? _comment; + + public GraphQLScalarValueFull(ASTNodeKind kind) + :base(kind) + { + } + + public override GraphQLLocation Location + { + get => _location; + set => _location = value; + } + + // TODO: this property is not set anywhere (yet), so it makes no sense to create a field for it + //public override GraphQLComment? Comment + //{ + // get => _comment; + // set => _comment = value; + //} + } } diff --git a/src/GraphQLParser/AST/GraphQLSchemaDefinition.cs b/src/GraphQLParser/AST/GraphQLSchemaDefinition.cs index e723d0c7..3d967ef9 100644 --- a/src/GraphQLParser/AST/GraphQLSchemaDefinition.cs +++ b/src/GraphQLParser/AST/GraphQLSchemaDefinition.cs @@ -11,4 +11,22 @@ public class GraphQLSchemaDefinition : ASTNode, IHasDirectivesNode public List? OperationTypes { get; set; } } + + internal sealed class GraphQLSchemaDefinitionFull : GraphQLSchemaDefinition + { + private GraphQLLocation _location; + private GraphQLComment? _comment; + + public override GraphQLLocation Location + { + get => _location; + set => _location = value; + } + + public override GraphQLComment? Comment + { + get => _comment; + set => _comment = value; + } + } } diff --git a/src/GraphQLParser/AST/GraphQLSelectionSet.cs b/src/GraphQLParser/AST/GraphQLSelectionSet.cs index bb90bbd9..92f7c8e9 100644 --- a/src/GraphQLParser/AST/GraphQLSelectionSet.cs +++ b/src/GraphQLParser/AST/GraphQLSelectionSet.cs @@ -9,4 +9,23 @@ public class GraphQLSelectionSet : ASTNode public List? Selections { get; set; } } + + internal sealed class GraphQLSelectionSetFull : GraphQLSelectionSet + { + private GraphQLLocation _location; + //private GraphQLComment? _comment; + + public override GraphQLLocation Location + { + get => _location; + set => _location = value; + } + + // TODO: this property is not set anywhere (yet), so it makes no sense to create a field for it + //public override GraphQLComment? Comment + //{ + // get => _comment; + // set => _comment = value; + //} + } } diff --git a/src/GraphQLParser/AST/GraphQLTypeExtensionDefinition.cs b/src/GraphQLParser/AST/GraphQLTypeExtensionDefinition.cs index 63cccd4f..5a64ef88 100644 --- a/src/GraphQLParser/AST/GraphQLTypeExtensionDefinition.cs +++ b/src/GraphQLParser/AST/GraphQLTypeExtensionDefinition.cs @@ -7,4 +7,22 @@ public class GraphQLTypeExtensionDefinition : GraphQLTypeDefinition /// public override ASTNodeKind Kind => ASTNodeKind.TypeExtensionDefinition; } + + internal sealed class GraphQLTypeExtensionDefinitionFull : GraphQLTypeExtensionDefinition + { + private GraphQLLocation _location; + private GraphQLComment? _comment; + + public override GraphQLLocation Location + { + get => _location; + set => _location = value; + } + + public override GraphQLComment? Comment + { + get => _comment; + set => _comment = value; + } + } } diff --git a/src/GraphQLParser/AST/GraphQLUnionTypeDefinition.cs b/src/GraphQLParser/AST/GraphQLUnionTypeDefinition.cs index 3325f6d8..88c2995b 100644 --- a/src/GraphQLParser/AST/GraphQLUnionTypeDefinition.cs +++ b/src/GraphQLParser/AST/GraphQLUnionTypeDefinition.cs @@ -11,4 +11,22 @@ public class GraphQLUnionTypeDefinition : GraphQLTypeDefinition, IHasDirectivesN public List? Types { get; set; } } + + internal sealed class GraphQLUnionTypeDefinitionFull : GraphQLUnionTypeDefinition + { + private GraphQLLocation _location; + private GraphQLComment? _comment; + + public override GraphQLLocation Location + { + get => _location; + set => _location = value; + } + + public override GraphQLComment? Comment + { + get => _comment; + set => _comment = value; + } + } } diff --git a/src/GraphQLParser/AST/GraphQLVariable.cs b/src/GraphQLParser/AST/GraphQLVariable.cs index 01259bed..86f135bc 100644 --- a/src/GraphQLParser/AST/GraphQLVariable.cs +++ b/src/GraphQLParser/AST/GraphQLVariable.cs @@ -7,4 +7,23 @@ public class GraphQLVariable : GraphQLValue, INamedNode public GraphQLName? Name { get; set; } } + + internal sealed class GraphQLVariableFull : GraphQLVariable + { + private GraphQLLocation _location; + //private GraphQLComment? _comment; + + public override GraphQLLocation Location + { + get => _location; + set => _location = value; + } + + // TODO: this property is not set anywhere (yet), so it makes no sense to create a field for it + //public override GraphQLComment? Comment + //{ + // get => _comment; + // set => _comment = value; + //} + } } diff --git a/src/GraphQLParser/AST/GraphQLVariableDefinition.cs b/src/GraphQLParser/AST/GraphQLVariableDefinition.cs index 3a489465..666424b7 100644 --- a/src/GraphQLParser/AST/GraphQLVariableDefinition.cs +++ b/src/GraphQLParser/AST/GraphQLVariableDefinition.cs @@ -11,4 +11,22 @@ public class GraphQLVariableDefinition : ASTNode public GraphQLVariable? Variable { get; set; } } + + internal sealed class GraphQLVariableDefinitionFull : GraphQLVariableDefinition + { + private GraphQLLocation _location; + private GraphQLComment? _comment; + + public override GraphQLLocation Location + { + get => _location; + set => _location = value; + } + + public override GraphQLComment? Comment + { + get => _comment; + set => _comment = value; + } + } } diff --git a/src/GraphQLParser/IgnoreOptions.cs b/src/GraphQLParser/IgnoreOptions.cs new file mode 100644 index 00000000..5e03e7a9 --- /dev/null +++ b/src/GraphQLParser/IgnoreOptions.cs @@ -0,0 +1,23 @@ +namespace GraphQLParser +{ + /// + /// Options to selectively ignore some information when parsing GraphQL document. + /// + public enum IgnoreOptions + { + /// + /// Specifies whether to ignore comments when parsing GraphQL document. + /// + IgnoreComments = 0, + + /// + /// Specifies whether to ignore comments and token locations when parsing GraphQL document. + /// + IgnoreCommentsAndLocations = 1, + + /// + /// No information is ignored. + /// + None = 2 + } +} diff --git a/src/GraphQLParser/Parser.cs b/src/GraphQLParser/Parser.cs index b81a061d..11af5898 100644 --- a/src/GraphQLParser/Parser.cs +++ b/src/GraphQLParser/Parser.cs @@ -11,8 +11,8 @@ public static class Parser /// Generates AST based on input text. /// /// Input data as a sequence of characters. - /// Specifies whether to ignore comments when parsing GraphQL document. By default, all comments are ignored. + /// Parser options. /// AST (Abstract Syntax Tree) for GraphQL document. - public static GraphQLDocument Parse(ROM source, bool ignoreComments = true) => new ParserContext(source, ignoreComments).Parse(); + public static GraphQLDocument Parse(ROM source, ParserOptions options = default) => new ParserContext(source, options).Parse(); } } diff --git a/src/GraphQLParser/ParserContext.Parse.cs b/src/GraphQLParser/ParserContext.Parse.cs new file mode 100644 index 00000000..da1d7d52 --- /dev/null +++ b/src/GraphQLParser/ParserContext.Parse.cs @@ -0,0 +1,1198 @@ +using System.Collections.Generic; +using GraphQLParser.AST; +using GraphQLParser.Exceptions; + +namespace GraphQLParser +{ + // WARNING: mutable struct, pass it by reference to those methods that will change it + internal partial struct ParserContext + { + public GraphQLComment? GetComment() + { + var ret = _currentComment; + _currentComment = null; + return ret; + } + + public GraphQLDocument Parse() => ParseDocument(); + + private GraphQLNamedType? ParseTypeCondition() + { + GraphQLNamedType? typeCondition = null; + if (_currentToken.Value == "on") + { + Advance(); + typeCondition = ParseNamedType(); + } + + return typeCondition; + } + + private GraphQLArgument ParseArgument() + { + var comment = GetComment(); + int start = _currentToken.Start; + + return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLArgument + { + Name = ParseName(), + Value = ExpectColonAndParseValueLiteral(false), + } + : new GraphQLArgumentFull + { + Comment = comment, + Name = ParseName(), + Value = ExpectColonAndParseValueLiteral(false), + Location = GetLocation(start) + }; + } + + private List? ParseArgumentDefs() + { + return Peek(TokenKind.PAREN_L) + ? OneOrMore(TokenKind.PAREN_L, (ref ParserContext context) => context.ParseInputValueDef(), TokenKind.PAREN_R) + : null; + } + + private List? ParseArguments() + { + return Peek(TokenKind.PAREN_L) ? + OneOrMore(TokenKind.PAREN_L, (ref ParserContext context) => context.ParseArgument(), TokenKind.PAREN_R) : + null; + } + + private GraphQLValue ParseBooleanValue(Token token) + { + Advance(); + return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLScalarValue(ASTNodeKind.BooleanValue) + { + Value = token.Value, + } + : new GraphQLScalarValueFull(ASTNodeKind.BooleanValue) + { + Value = token.Value, + Location = GetLocation(token.Start) + }; + } + + private ASTNode ParseDefinition() + { + ParseComment(); + + if (Peek(TokenKind.BRACE_L)) + { + return ParseOperationDefinition(); + } + + if (Peek(TokenKind.NAME)) + { + ASTNode? definition; + if ((definition = ParseNamedDefinition()) != null) + return definition; + } + + return Throw_From_ParseDefinition(); + } + + private ASTNode Throw_From_ParseDefinition() + { + throw new GraphQLSyntaxErrorException($"Unexpected {_currentToken}", _source, _currentToken.Start); + } + + private List ParseDefinitionsIfNotEOF() + { + var result = new List(); + + if (_currentToken.Kind != TokenKind.EOF) + { + do + { + result.Add(ParseDefinition()); + } + while (!Skip(TokenKind.EOF)); + } + + return result; + } + + private GraphQLComment? ParseComment() + { + if (_ignoreOptions != IgnoreOptions.None) + { + while (Peek(TokenKind.COMMENT)) + { + Advance(); + } + return null; + } + + if (!Peek(TokenKind.COMMENT)) + { + return null; + } + + var text = new List(); + int start = _currentToken.Start; + int end; + + do + { + text.Add(_currentToken.Value); + end = _currentToken.End; + Advance(); + } + while (_currentToken.Kind == TokenKind.COMMENT); + + var comment = new GraphQLComment + { + Location = new GraphQLLocation + ( + start, + end + ) + }; + + if (text.Count == 1) + { + comment.Text = text[0]; + } + else if (text.Count > 1) + { + var (owner, result) = text.Concat(); + comment.Text = result; + (_document!.RentedMemoryTracker ??= new List<(System.Buffers.IMemoryOwner, ASTNode)>()).Add((owner, comment)); + } + + SetCurrentComment(comment); + + return comment; + } + + private void SetCurrentComment(GraphQLComment? comment) + { + if (_currentComment != null) + (_unattachedComments ??= new List()).Add(_currentComment); + + _currentComment = comment; + } + + private GraphQLDirective ParseDirective() + { + int start = _currentToken.Start; + Expect(TokenKind.AT); + return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLDirective + { + Name = ParseName(), + Arguments = ParseArguments(), + } + : new GraphQLDirectiveFull + { + Name = ParseName(), + Arguments = ParseArguments(), + Location = GetLocation(start) + }; + } + + /// + /// http://spec.graphql.org/draft/#DirectiveDefinition + /// DirectiveDefinition: + /// Description(opt) directive @ Name ArgumentsDefinition(opt) repeatable(opt) on DirectiveLocations + /// + /// + private GraphQLDirectiveDefinition ParseDirectiveDefinition() + { + var comment = GetComment(); + int start = _currentToken.Start; + ExpectKeyword("directive"); + Expect(TokenKind.AT); + + var name = ParseName(); + var args = ParseArgumentDefs(); + bool repeatable = ParseRepeatable(); + + ExpectKeyword("on"); + var locations = ParseDirectiveLocations(); + + return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLDirectiveDefinition + { + Name = name, + Repeatable = repeatable, + Arguments = args, + Locations = locations, + } + : new GraphQLDirectiveDefinitionFull + { + Comment = comment, + Name = name, + Repeatable = repeatable, + Arguments = args, + Locations = locations, + Location = GetLocation(start) + }; + } + + private bool ParseRepeatable() + { + if (Peek(TokenKind.NAME)) + { + if (_currentToken.Value == "on") + return false; + + if (_currentToken.Value == "repeatable") + { + Advance(); + return true; + } + + Throw_From_ParseRepeatable(); + } + + return false; + } + + private void Throw_From_ParseRepeatable() + { + throw new GraphQLSyntaxErrorException($"Unexpected {_currentToken}", _source, _currentToken.Start); + } + + private List ParseDirectiveLocations() + { + var locations = new List(); + + // Directive locations may be defined with an optional leading | character + // to aid formatting when representing a longer list of possible locations + Skip(TokenKind.PIPE); + + do + { + locations.Add(ParseName()); + } + while (Skip(TokenKind.PIPE)); + + return locations; + } + + private List? ParseDirectives() + { + List? directives = null; + while (Peek(TokenKind.AT)) + (directives ??= new List()).Add(ParseDirective()); + + return directives; + } + + private GraphQLDocument ParseDocument() + { + _document = _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations ? new GraphQLDocument() : new GraphQLDocumentFull(); + + int start = _currentToken.Start; + var definitions = ParseDefinitionsIfNotEOF(); + + SetCurrentComment(null); + + if (_ignoreOptions != IgnoreOptions.IgnoreCommentsAndLocations) + { + _document.Location = new GraphQLLocation + ( + start, + // Formally, to denote the end of the document, it is better to use _prevToken.End, + // since _prevToken represents some real meaningful token; _currentToken here is always EOF. + // EOF is a technical token with length = 0, _prevToken.End and _currentToken.End have the same value here. + _prevToken.End // equals to _currentToken.End (EOF) + ); + } + _document.Definitions = definitions; + _document.UnattachedComments = _unattachedComments; + return _document; + } + + private GraphQLEnumTypeDefinition ParseEnumTypeDefinition() + { + var comment = GetComment(); + int start = _currentToken.Start; + ExpectKeyword("enum"); + + return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLEnumTypeDefinition + { + Name = ParseName(), + Directives = ParseDirectives(), + Values = OneOrMore(TokenKind.BRACE_L, (ref ParserContext context) => context.ParseEnumValueDefinition(), TokenKind.BRACE_R), + } + : new GraphQLEnumTypeDefinitionFull + { + Comment = comment, + Name = ParseName(), + Directives = ParseDirectives(), + Values = OneOrMore(TokenKind.BRACE_L, (ref ParserContext context) => context.ParseEnumValueDefinition(), TokenKind.BRACE_R), + Location = GetLocation(start) + }; + } + + private GraphQLValue ParseEnumValue(Token token) + { + Advance(); + return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLScalarValue(ASTNodeKind.EnumValue) + { + Value = token.Value, + } + : new GraphQLScalarValueFull(ASTNodeKind.EnumValue) + { + Value = token.Value, + Location = GetLocation(token.Start) + }; + } + + private GraphQLEnumValueDefinition ParseEnumValueDefinition() + { + var comment = GetComment(); + int start = _currentToken.Start; + + return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLEnumValueDefinition + { + Name = ParseName(), + Directives = ParseDirectives(), + } + : new GraphQLEnumValueDefinitionFull + { + Comment = comment, + Name = ParseName(), + Directives = ParseDirectives(), + Location = GetLocation(start) + }; + } + + private GraphQLFieldDefinition ParseFieldDefinition() + { + var comment = GetComment(); + int start = _currentToken.Start; + var name = ParseName(); + var args = ParseArgumentDefs(); + Expect(TokenKind.COLON); + + return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLFieldDefinition + { + Name = name, + Arguments = args, + Type = ParseType(), + Directives = ParseDirectives(), + } + : new GraphQLFieldDefinitionFull + { + Comment = comment, + Name = name, + Arguments = args, + Type = ParseType(), + Directives = ParseDirectives(), + Location = GetLocation(start) + }; + } + + private GraphQLFieldSelection ParseFieldSelection() + { + var comment = GetComment(); + int start = _currentToken.Start; + var nameOrAlias = ParseName(); + GraphQLName name; + GraphQLName? alias; + + if (Skip(TokenKind.COLON)) + { + name = ParseName(); + alias = nameOrAlias; + } + else + { + alias = null; + name = nameOrAlias; + } + + return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLFieldSelection + { + Alias = alias, + Name = name, + Arguments = ParseArguments(), + Directives = ParseDirectives(), + SelectionSet = Peek(TokenKind.BRACE_L) ? ParseSelectionSet() : null, + } + : new GraphQLFieldSelectionFull + { + Comment = comment, + Alias = alias, + Name = name, + Arguments = ParseArguments(), + Directives = ParseDirectives(), + SelectionSet = Peek(TokenKind.BRACE_L) ? ParseSelectionSet() : null, + Location = GetLocation(start) + }; + } + + private GraphQLValue ParseFloat(/*bool isConstant*/) + { + var token = _currentToken; + Advance(); + return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLScalarValue(ASTNodeKind.FloatValue) + { + Value = token.Value, + } + : new GraphQLScalarValueFull(ASTNodeKind.FloatValue) + { + Value = token.Value, + Location = GetLocation(token.Start) + }; + } + + private ASTNode ParseFragment() + { + var comment = GetComment(); + int start = _currentToken.Start; + Expect(TokenKind.SPREAD); + + return Peek(TokenKind.NAME) && _currentToken.Value != "on" + ? CreateGraphQLFragmentSpread(start, comment) + : CreateInlineFragment(start, comment); + } + + private ASTNode CreateGraphQLFragmentSpread(int start, GraphQLComment? comment) + { + return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLFragmentSpread + { + Name = ParseFragmentName(), + Directives = ParseDirectives(), + } + : new GraphQLFragmentSpreadFull + { + Comment = comment, + Name = ParseFragmentName(), + Directives = ParseDirectives(), + Location = GetLocation(start) + }; + } + + private ASTNode CreateInlineFragment(int start, GraphQLComment? comment) + { + return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLInlineFragment + { + TypeCondition = ParseTypeCondition(), + Directives = ParseDirectives(), + SelectionSet = ParseSelectionSet(), + } + : new GraphQLInlineFragmentFull + { + Comment = comment, + TypeCondition = ParseTypeCondition(), + Directives = ParseDirectives(), + SelectionSet = ParseSelectionSet(), + Location = GetLocation(start) + }; + } + + private GraphQLFragmentDefinition ParseFragmentDefinition() + { + var comment = GetComment(); + int start = _currentToken.Start; + ExpectKeyword("fragment"); + + return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLFragmentDefinition + { + Name = ParseFragmentName(), + TypeCondition = ExpectOnKeywordAndParseNamedType(), + Directives = ParseDirectives(), + SelectionSet = ParseSelectionSet(), + } + : new GraphQLFragmentDefinitionFull + { + Comment = comment, + Name = ParseFragmentName(), + TypeCondition = ExpectOnKeywordAndParseNamedType(), + Directives = ParseDirectives(), + SelectionSet = ParseSelectionSet(), + Location = GetLocation(start) + }; + } + + private GraphQLName ParseFragmentName() + { + if (_currentToken.Value == "on") + { + Throw_From_ParseFragmentName(); + } + + return ParseName(); + } + + private void Throw_From_ParseFragmentName() + { + throw new GraphQLSyntaxErrorException($"Unexpected {_currentToken}", _source, _currentToken.Start); + } + + private List? ParseImplementsInterfaces() + { + List? types = null; + if (_currentToken.Value == "implements") + { + types = new List(); + Advance(); + + do + { + types.Add(ParseNamedType()); + } + while (Peek(TokenKind.NAME)); + } + + return types; + } + + private GraphQLInputObjectTypeDefinition ParseInputObjectTypeDefinition() + { + var comment = GetComment(); + int start = _currentToken.Start; + ExpectKeyword("input"); + + return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLInputObjectTypeDefinition + { + Name = ParseName(), + Directives = ParseDirectives(), + Fields = ZeroOrMore(TokenKind.BRACE_L, (ref ParserContext context) => context.ParseInputValueDef(), TokenKind.BRACE_R), + } + : new GraphQLInputObjectTypeDefinitionFull + { + Comment = comment, + Name = ParseName(), + Directives = ParseDirectives(), + Fields = ZeroOrMore(TokenKind.BRACE_L, (ref ParserContext context) => context.ParseInputValueDef(), TokenKind.BRACE_R), + Location = GetLocation(start) + }; + } + + private GraphQLInputValueDefinition ParseInputValueDef() + { + var comment = GetComment(); + int start = _currentToken.Start; + var name = ParseName(); + Expect(TokenKind.COLON); + + return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLInputValueDefinition + { + Name = name, + Type = ParseType(), + DefaultValue = Skip(TokenKind.EQUALS) ? ParseValueLiteral(true) : null, + Directives = ParseDirectives(), + } + : new GraphQLInputValueDefinitionFull + { + Comment = comment, + Name = name, + Type = ParseType(), + DefaultValue = Skip(TokenKind.EQUALS) ? ParseValueLiteral(true) : null, + Directives = ParseDirectives(), + Location = GetLocation(start) + }; + } + + private GraphQLValue ParseInt(/*bool isConstant*/) + { + var token = _currentToken; + Advance(); + + return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLScalarValue(ASTNodeKind.IntValue) + { + Value = token.Value, + } + : new GraphQLScalarValueFull(ASTNodeKind.IntValue) + { + Value = token.Value, + Location = GetLocation(token.Start) + }; + } + + private GraphQLInterfaceTypeDefinition ParseInterfaceTypeDefinition() + { + var comment = GetComment(); + int start = _currentToken.Start; + ExpectKeyword("interface"); + + return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLInterfaceTypeDefinition + { + Name = ParseName(), + Directives = ParseDirectives(), + Fields = ZeroOrMore(TokenKind.BRACE_L, (ref ParserContext context) => context.ParseFieldDefinition(), TokenKind.BRACE_R), + } + : new GraphQLInterfaceTypeDefinitionFull + { + Comment = comment, + Name = ParseName(), + Directives = ParseDirectives(), + Fields = ZeroOrMore(TokenKind.BRACE_L, (ref ParserContext context) => context.ParseFieldDefinition(), TokenKind.BRACE_R), + Location = GetLocation(start) + }; + } + + private GraphQLValue ParseList(bool isConstant) + { + int start = _currentToken.Start; + // the compiler caches these delegates in the generated code + ParseCallback constant = (ref ParserContext context) => context.ParseValueLiteral(true); + ParseCallback value = (ref ParserContext context) => context.ParseValueLiteral(false); + + return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLListValue(ASTNodeKind.ListValue) + { + Values = ZeroOrMore(TokenKind.BRACKET_L, isConstant ? constant : value, TokenKind.BRACKET_R), + AstValue = _source.Slice(start, _currentToken.End - start - 1) + } + : new GraphQLListValueFull(ASTNodeKind.ListValue) + { + Values = ZeroOrMore(TokenKind.BRACKET_L, isConstant ? constant : value, TokenKind.BRACKET_R), + Location = GetLocation(start), + AstValue = _source.Slice(start, _currentToken.End - start - 1) + }; + } + + private GraphQLName ParseName() + { + int start = _currentToken.Start; + var value = _currentToken.Value; + + Expect(TokenKind.NAME); + + return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLName + { + Value = value + } + : new GraphQLNameFull + { + Location = GetLocation(start), + Value = value + }; + } + + private ASTNode? ParseNamedDefinition() + { + var value = _currentToken.Value; + + if (value == "query") + return ParseOperationDefinition(); + + if (value == "mutation") + return ParseOperationDefinition(); + + if (value == "subscription") + return ParseOperationDefinition(); + + if (value == "fragment") + return ParseFragmentDefinition(); + + if (value == "schema") + return ParseSchemaDefinition(); + + 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 == "extend") + return ParseTypeExtensionDefinition(); + + if (value == "directive") + return ParseDirectiveDefinition(); + + return null; + } + + private GraphQLNamedType ParseNamedType() + { + int start = _currentToken.Start; + return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLNamedType + { + Name = ParseName(), + } + : new GraphQLNamedTypeFull + { + Name = ParseName(), + Location = GetLocation(start) + }; + } + + private GraphQLValue ParseNameValue(/*bool isConstant*/) + { + var token = _currentToken; + + if (token.Value == "true" || token.Value == "false") + { + return ParseBooleanValue(token); + } + else if (!token.Value.IsEmpty) + { + return token.Value == "null" + ? ParseNullValue(token) + : ParseEnumValue(token); + } + + return Throw_From_ParseNameValue(); + } + + private GraphQLValue Throw_From_ParseNameValue() + { + throw new GraphQLSyntaxErrorException($"Unexpected {_currentToken}", _source, _currentToken.Start); + } + + private GraphQLValue ParseObject(bool isConstant) + { + var comment = GetComment(); + int start = _currentToken.Start; + + return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLObjectValue + { + Fields = ParseObjectFields(isConstant), + } + : new GraphQLObjectValueFull + { + Comment = comment, + Fields = ParseObjectFields(isConstant), + Location = GetLocation(start) + }; + } + + private GraphQLValue ParseNullValue(Token token) + { + Advance(); + return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLScalarValue(ASTNodeKind.NullValue) + { + Value = token.Value, + } + : new GraphQLScalarValueFull(ASTNodeKind.NullValue) + { + Value = token.Value, + Location = GetLocation(token.Start) + }; + } + + private GraphQLObjectField ParseObjectField(bool isConstant) + { + var comment = GetComment(); + int start = _currentToken.Start; + return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLObjectField + { + Name = ParseName(), + Value = ExpectColonAndParseValueLiteral(isConstant), + } + : new GraphQLObjectFieldFull + { + Comment = comment, + Name = ParseName(), + Value = ExpectColonAndParseValueLiteral(isConstant), + Location = GetLocation(start) + }; + } + + private List ParseObjectFields(bool isConstant) + { + var fields = new List(); + + Expect(TokenKind.BRACE_L); + while (!Skip(TokenKind.BRACE_R)) + fields.Add(ParseObjectField(isConstant)); + + return fields; + } + + private GraphQLObjectTypeDefinition ParseObjectTypeDefinition() + { + var comment = GetComment(); + + int start = _currentToken.Start; + ExpectKeyword("type"); + + return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLObjectTypeDefinition + { + Name = ParseName(), + Interfaces = ParseImplementsInterfaces(), + Directives = ParseDirectives(), + Fields = ZeroOrMore(TokenKind.BRACE_L, (ref ParserContext context) => context.ParseFieldDefinition(), TokenKind.BRACE_R), + } + : new GraphQLObjectTypeDefinitionFull + { + Comment = comment, + Name = ParseName(), + Interfaces = ParseImplementsInterfaces(), + Directives = ParseDirectives(), + Fields = ZeroOrMore(TokenKind.BRACE_L, (ref ParserContext context) => context.ParseFieldDefinition(), TokenKind.BRACE_R), + Location = GetLocation(start) + }; + } + + private ASTNode ParseOperationDefinition() + { + int start = _currentToken.Start; + + return Peek(TokenKind.BRACE_L) + ? CreateOperationDefinition(start) + : CreateOperationDefinition(start, ParseOperationType(), Peek(TokenKind.NAME) ? ParseName() : null); // Peek(TokenKind.NAME) because of anonymous query + } + + private ASTNode CreateOperationDefinition(int start) + { + var comment = GetComment(); + return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLOperationDefinition + { + Operation = OperationType.Query, + SelectionSet = ParseSelectionSet(), + } + : new GraphQLOperationDefinitionFull + { + Comment = comment, + Operation = OperationType.Query, + SelectionSet = ParseSelectionSet(), + Location = GetLocation(start) + }; + } + + private ASTNode CreateOperationDefinition(int start, OperationType operation, GraphQLName? name) + { + var comment = GetComment(); + return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLOperationDefinition + { + Operation = operation, + Name = name, + VariableDefinitions = ParseVariableDefinitions(), + Directives = ParseDirectives(), + SelectionSet = ParseSelectionSet(), + } + : new GraphQLOperationDefinitionFull + { + Comment = comment, + Operation = operation, + Name = name, + VariableDefinitions = ParseVariableDefinitions(), + Directives = ParseDirectives(), + SelectionSet = ParseSelectionSet(), + Location = GetLocation(start) + }; + } + + private OperationType ParseOperationType() + { + var token = _currentToken; + Expect(TokenKind.NAME); + + if (token.Value == "mutation") + return OperationType.Mutation; + + if (token.Value == "subscription") + return OperationType.Subscription; + + return OperationType.Query; + } + + private GraphQLOperationTypeDefinition ParseOperationTypeDefinition() + { + int start = _currentToken.Start; + var operation = ParseOperationType(); + Expect(TokenKind.COLON); + var type = ParseNamedType(); + + return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLOperationTypeDefinition + { + Operation = operation, + Type = type, + } + : new GraphQLOperationTypeDefinitionFull + { + Operation = operation, + Type = type, + Location = GetLocation(start) + }; + } + + private GraphQLScalarTypeDefinition ParseScalarTypeDefinition() + { + var comment = GetComment(); + int start = _currentToken.Start; + ExpectKeyword("scalar"); + var name = ParseName(); + var directives = ParseDirectives(); + + return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLScalarTypeDefinition + { + Name = name, + Directives = directives, + } + : new GraphQLScalarTypeDefinitionFull + { + Comment = comment, + Name = name, + Directives = directives, + Location = GetLocation(start) + }; + } + + private GraphQLSchemaDefinition ParseSchemaDefinition() + { + var comment = GetComment(); + int start = _currentToken.Start; + ExpectKeyword("schema"); + var directives = ParseDirectives(); + var operationTypes = OneOrMore(TokenKind.BRACE_L, (ref ParserContext context) => context.ParseOperationTypeDefinition(), TokenKind.BRACE_R); + + return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLSchemaDefinition + { + Directives = directives, + OperationTypes = operationTypes, + } + : new GraphQLSchemaDefinitionFull + { + Comment = comment, + Directives = directives, + OperationTypes = operationTypes, + Location = GetLocation(start) + }; + } + + private ASTNode ParseSelection() + { + return Peek(TokenKind.SPREAD) ? + ParseFragment() : + ParseFieldSelection(); + } + + private GraphQLSelectionSet ParseSelectionSet() + { + int start = _currentToken.Start; + return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLSelectionSet + { + Selections = OneOrMore(TokenKind.BRACE_L, (ref ParserContext context) => context.ParseSelection(), TokenKind.BRACE_R), + } + : new GraphQLSelectionSetFull + { + Selections = OneOrMore(TokenKind.BRACE_L, (ref ParserContext context) => context.ParseSelection(), TokenKind.BRACE_R), + Location = GetLocation(start) + }; + } + + private GraphQLValue ParseString(/*bool isConstant*/) + { + var token = _currentToken; + Advance(); + return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLScalarValue(ASTNodeKind.StringValue) + { + Value = token.Value, + } + : new GraphQLScalarValueFull(ASTNodeKind.StringValue) + { + Value = token.Value, + Location = GetLocation(token.Start) + }; + } + + private GraphQLType ParseType() + { + GraphQLType type; + int start = _currentToken.Start; + if (Skip(TokenKind.BRACKET_L)) + { + type = ParseType(); + Expect(TokenKind.BRACKET_R); + type = _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLListType + { + Type = type, + } + : new GraphQLListTypeFull + { + Type = type, + Location = GetLocation(start) + }; + } + else + { + type = ParseNamedType(); + } + + return Skip(TokenKind.BANG) + ? (_ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLNonNullType + { + Type = type, + } + : new GraphQLNonNullTypeFull + { + Type = type, + Location = GetLocation(start) + }) + : type; + } + + private GraphQLTypeExtensionDefinition ParseTypeExtensionDefinition() + { + var comment = GetComment(); + int start = _currentToken.Start; + ExpectKeyword("extend"); + var definition = ParseObjectTypeDefinition(); + + return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLTypeExtensionDefinition + { + Name = definition.Name, + Definition = definition, + } + : new GraphQLTypeExtensionDefinitionFull + { + Comment = comment, + Name = definition.Name, + Definition = definition, + Location = GetLocation(start) + }; + } + + private List ParseUnionMembers() + { + var members = new List(); + + // Union members may be defined with an optional leading | character + // to aid formatting when representing a longer list of possible types + Skip(TokenKind.PIPE); + + do + { + members.Add(ParseNamedType()); + } + while (Skip(TokenKind.PIPE)); + + return members; + } + + private GraphQLUnionTypeDefinition ParseUnionTypeDefinition() + { + var comment = GetComment(); + int start = _currentToken.Start; + ExpectKeyword("union"); + var name = ParseName(); + var directives = ParseDirectives(); + Expect(TokenKind.EQUALS); + var types = ParseUnionMembers(); + + return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLUnionTypeDefinition + { + Name = name, + Directives = directives, + Types = types, + } + : new GraphQLUnionTypeDefinitionFull + { + Comment = comment, + Name = name, + Directives = directives, + Types = types, + Location = GetLocation(start) + }; + } + + private GraphQLValue ParseValueLiteral(bool isConstant) => _currentToken.Kind switch + { + TokenKind.BRACKET_L => ParseList(isConstant), + TokenKind.BRACE_L => ParseObject(isConstant), + TokenKind.INT => ParseInt(/*isConstant*/), + TokenKind.FLOAT => ParseFloat(/*isConstant*/), + TokenKind.STRING => ParseString(/*isConstant*/), + TokenKind.NAME => ParseNameValue(/*isConstant*/), + TokenKind.DOLLAR when !isConstant => ParseVariable(), + _ => Throw_From_ParseValueLiteral() + }; + + private GraphQLValue Throw_From_ParseValueLiteral() + { + throw new GraphQLSyntaxErrorException($"Unexpected {_currentToken}", _source, _currentToken.Start); + } + + private GraphQLVariable ParseVariable() + { + int start = _currentToken.Start; + Expect(TokenKind.DOLLAR); + + return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLVariable + { + Name = ParseName(), + } + : new GraphQLVariableFull + { + Name = ParseName(), + Location = GetLocation(start) + }; + } + + private GraphQLVariableDefinition ParseVariableDefinition() + { + var comment = GetComment(); + int start = _currentToken.Start; + + return _ignoreOptions == IgnoreOptions.IgnoreCommentsAndLocations + ? new GraphQLVariableDefinition + { + Variable = ParseVariable(), + Type = ExpectColonAndParseType(), + DefaultValue = Skip(TokenKind.EQUALS) ? ParseValueLiteral(true) : null, + } + : new GraphQLVariableDefinitionFull + { + Comment = comment, + Variable = ParseVariable(), + Type = ExpectColonAndParseType(), + DefaultValue = Skip(TokenKind.EQUALS) ? ParseValueLiteral(true) : null, + Location = GetLocation(start) + }; + } + + private List? ParseVariableDefinitions() + { + return Peek(TokenKind.PAREN_L) ? + OneOrMore(TokenKind.PAREN_L, (ref ParserContext context) => context.ParseVariableDefinition(), TokenKind.PAREN_R) : + null; + } + } +} diff --git a/src/GraphQLParser/ParserContext.cs b/src/GraphQLParser/ParserContext.cs index d3c87db9..1275d334 100644 --- a/src/GraphQLParser/ParserContext.cs +++ b/src/GraphQLParser/ParserContext.cs @@ -5,27 +5,27 @@ namespace GraphQLParser { // WARNING: mutable struct, pass it by reference to those methods that will change it - internal struct ParserContext + internal partial struct ParserContext { private delegate TResult ParseCallback(ref ParserContext context); private readonly ROM _source; - private readonly bool _ignoreComments; + private readonly IgnoreOptions _ignoreOptions; private GraphQLComment? _currentComment; - private List? _unattachedComments; + private List? _unattachedComments; private Token _currentToken; private Token _prevToken; - private GraphQLDocument? _document; + private GraphQLDocument? _document; - public ParserContext(ROM source, bool ignoreComments) + public ParserContext(ROM source, ParserOptions options) { - _document = null; + _document = null; _currentComment = null; - _unattachedComments = null; + _unattachedComments = null; _source = source; - _ignoreComments = ignoreComments; + _ignoreOptions = options.Ignore; - _currentToken = Lexer.Lex(source); + _currentToken = Lexer.Lex(source); _prevToken = new Token ( TokenKind.UNKNOWN, @@ -35,32 +35,16 @@ public ParserContext(ROM source, bool ignoreComments) ); } - public GraphQLComment? GetComment() - { - var ret = _currentComment; - _currentComment = null; - return ret; - } - - public GraphQLDocument Parse() => ParseDocument(); - - private void Advance() - { - // We should not advance further if we have already reached the EOF. - if (_currentToken.Kind != TokenKind.EOF) - { - _prevToken = _currentToken; - _currentToken = Lexer.Lex(_source, _currentToken.End); - } - } - - private GraphQLType AdvanceThroughColonAndParseType() + private GraphQLLocation GetLocation(int start) { - Expect(TokenKind.COLON); - return ParseType(); + return new GraphQLLocation + ( + start, + _prevToken.End + ); } - private List? Any(TokenKind open, ParseCallback next, TokenKind close) + private List? ZeroOrMore(TokenKind open, ParseCallback next, TokenKind close) where T : ASTNode { Expect(open); @@ -74,147 +58,8 @@ private GraphQLType AdvanceThroughColonAndParseType() return nodes; } - private GraphQLFieldSelection CreateFieldSelection(int start, GraphQLName name, GraphQLName? alias, GraphQLComment? comment) - { - return new GraphQLFieldSelection - { - Comment = comment, - Alias = alias, - Name = name, - Arguments = ParseArguments(), - Directives = ParseDirectives(), - SelectionSet = Peek(TokenKind.BRACE_L) ? ParseSelectionSet() : null, - Location = GetLocation(start) - }; - } - - private ASTNode CreateGraphQLFragmentSpread(int start, GraphQLComment? comment) - { - return new GraphQLFragmentSpread - { - Comment = comment, - Name = ParseFragmentName(), - Directives = ParseDirectives(), - Location = GetLocation(start) - }; - } - - private ASTNode CreateInlineFragment(int start, GraphQLComment? comment) - { - return new GraphQLInlineFragment - { - Comment = comment, - TypeCondition = GetTypeCondition(), - Directives = ParseDirectives(), - SelectionSet = ParseSelectionSet(), - Location = GetLocation(start) - }; - } - - private ASTNode CreateOperationDefinition(int start, OperationType operation, GraphQLName? name) - { - var comment = GetComment(); - return new GraphQLOperationDefinition - { - Comment = comment, - Operation = operation, - Name = name, - VariableDefinitions = ParseVariableDefinitions(), - Directives = ParseDirectives(), - SelectionSet = ParseSelectionSet(), - Location = GetLocation(start) - }; - } - - private ASTNode CreateOperationDefinition(int start) - { - var comment = GetComment(); - return new GraphQLOperationDefinition - { - Comment = comment, - Operation = OperationType.Query, - SelectionSet = ParseSelectionSet(), - Location = GetLocation(start) - }; - } - - private void Expect(TokenKind kind) - { - if (_currentToken.Kind == kind) - { - Advance(); - } - else - { - Throw_From_Expect(kind); - } - } - - private void Throw_From_Expect(TokenKind kind) - { - throw new GraphQLSyntaxErrorException($"Expected {Token.GetTokenKindDescription(kind)}, found {_currentToken}", _source, _currentToken.Start); - } - - private GraphQLValue ExpectColonAndParseValueLiteral(bool isConstant) - { - Expect(TokenKind.COLON); - return ParseValueLiteral(isConstant); - } - - private void ExpectKeyword(string keyword) - { - if (_currentToken.Kind == TokenKind.NAME && _currentToken.Value == keyword) - Advance(); - else - Throw_From_ExpectKeyword(keyword); - } - - private void Throw_From_ExpectKeyword(string keyword) - { - throw new GraphQLSyntaxErrorException($"Expected \"{keyword}\", found Name \"{_currentToken.Value}\"", _source, _currentToken.Start); - } - - private GraphQLNamedType ExpectOnKeywordAndParseNamedType() - { - ExpectKeyword("on"); - return ParseNamedType(); - } - - private GraphQLValue? GetDefaultConstantValue() - { - GraphQLValue? defaultValue = null; - if (Skip(TokenKind.EQUALS)) - { - defaultValue = ParseConstantValue(); - } - - return defaultValue; - } - - private GraphQLLocation GetLocation(int start) - { - return new GraphQLLocation - ( - start, - _prevToken.End - ); - } - - private GraphQLName? GetName() => Peek(TokenKind.NAME) ? ParseName() : null; - - private GraphQLNamedType? GetTypeCondition() - { - GraphQLNamedType? typeCondition = null; - if (_currentToken.Value == "on") - { - Advance(); - typeCondition = ParseNamedType(); - } - - return typeCondition; - } - - private List Many(TokenKind open, ParseCallback next, TokenKind close) + private List OneOrMore(TokenKind open, ParseCallback next, TokenKind close) + where T : ASTNode { Expect(open); @@ -227,900 +72,78 @@ private List Many(TokenKind open, ParseCallback next, TokenKind close) return nodes; } - private GraphQLArgument ParseArgument() - { - var comment = GetComment(); - int start = _currentToken.Start; - - return new GraphQLArgument - { - Comment = comment, - Name = ParseName(), - Value = ExpectColonAndParseValueLiteral(false), - Location = GetLocation(start) - }; - } - - private List? ParseArgumentDefs() - { - return Peek(TokenKind.PAREN_L) - ? Many(TokenKind.PAREN_L, (ref ParserContext context) => context.ParseInputValueDef(), TokenKind.PAREN_R) - : null; - } - - private List? ParseArguments() - { - return Peek(TokenKind.PAREN_L) ? - Many(TokenKind.PAREN_L, (ref ParserContext context) => context.ParseArgument(), TokenKind.PAREN_R) : - null; - } - - private GraphQLValue ParseBooleanValue(Token token) - { - Advance(); - return new GraphQLScalarValue(ASTNodeKind.BooleanValue) - { - Value = token.Value, - Location = GetLocation(token.Start) - }; - } - - private GraphQLValue ParseConstantValue() => ParseValueLiteral(true); + private bool Peek(TokenKind kind) => _currentToken.Kind == kind; - private ASTNode ParseDefinition() + private bool Skip(TokenKind kind) { ParseComment(); - if (Peek(TokenKind.BRACE_L)) - { - return ParseOperationDefinition(); - } + bool isCurrentTokenMatching = _currentToken.Kind == kind; - if (Peek(TokenKind.NAME)) + if (isCurrentTokenMatching) { - ASTNode? definition; - if ((definition = ParseNamedDefinition()) != null) - return definition; + Advance(); } - return Throw_From_ParseDefinition(); + return isCurrentTokenMatching; } - private ASTNode Throw_From_ParseDefinition() - { - throw new GraphQLSyntaxErrorException($"Unexpected {_currentToken}", _source, _currentToken.Start); - } - - private List ParseDefinitionsIfNotEOF() + private void Advance() { - var result = new List(); - + // We should not advance further if we have already reached the EOF. if (_currentToken.Kind != TokenKind.EOF) { - do - { - result.Add(ParseDefinition()); - } - while (!Skip(TokenKind.EOF)); + _prevToken = _currentToken; + _currentToken = Lexer.Lex(_source, _currentToken.End); } - - return result; } - private GraphQLComment? ParseComment() + private void Expect(TokenKind kind) { - if (_ignoreComments) - { - while (Peek(TokenKind.COMMENT)) - { - Advance(); - } - return null; - } - - if (!Peek(TokenKind.COMMENT)) - { - return null; - } - - var text = new List(); - int start = _currentToken.Start; - int end; - - do + if (_currentToken.Kind == kind) { - text.Add(_currentToken.Value); - end = _currentToken.End; Advance(); } - while (_currentToken.Kind == TokenKind.COMMENT); - - var comment = new GraphQLComment - { - Location = new GraphQLLocation - ( - start, - end - ) - }; - - if (text.Count == 1) - { - comment.Text = text[0]; - } - else if (text.Count > 1) - { - var (owner, result) = text.Concat(); - comment.Text = result; - (_document!.RentedMemoryTracker ??= new List<(System.Buffers.IMemoryOwner, ASTNode)>()).Add((owner, comment)); - } - - SetCurrentComment(comment); - - return comment; - } - - private void SetCurrentComment(GraphQLComment? comment) - { - if (_currentComment != null) - (_unattachedComments ??= new List()).Add(_currentComment); - - _currentComment = comment; - } - - private GraphQLDirective ParseDirective() - { - int start = _currentToken.Start; - Expect(TokenKind.AT); - return new GraphQLDirective - { - Name = ParseName(), - Arguments = ParseArguments(), - Location = GetLocation(start) - }; - } - - /// - /// http://spec.graphql.org/draft/#DirectiveDefinition - /// DirectiveDefinition: - /// Description(opt) directive @ Name ArgumentsDefinition(opt) repeatable(opt) on DirectiveLocations - /// - /// - private GraphQLDirectiveDefinition ParseDirectiveDefinition() - { - var comment = GetComment(); - int start = _currentToken.Start; - ExpectKeyword("directive"); - Expect(TokenKind.AT); - - var name = ParseName(); - var args = ParseArgumentDefs(); - bool repeatable = ParseRepeatable(); - - ExpectKeyword("on"); - var locations = ParseDirectiveLocations(); - - return new GraphQLDirectiveDefinition - { - Comment = comment, - Name = name, - Repeatable = repeatable, - Arguments = args, - Locations = locations, - Location = GetLocation(start) - }; - } - - private bool ParseRepeatable() - { - if (Peek(TokenKind.NAME)) - { - if (_currentToken.Value == "on") - return false; - - if (_currentToken.Value == "repeatable") - { - Advance(); - return true; - } - - Throw_From_ParseRepeatable(); - } - - return false; - } - - private void Throw_From_ParseRepeatable() - { - throw new GraphQLSyntaxErrorException($"Unexpected {_currentToken}", _source, _currentToken.Start); - } - - private List ParseDirectiveLocations() - { - var locations = new List(); - - // Directive locations may be defined with an optional leading | character - // to aid formatting when representing a longer list of possible locations - Skip(TokenKind.PIPE); - - do - { - locations.Add(ParseName()); - } - while (Skip(TokenKind.PIPE)); - - return locations; - } - - private List? ParseDirectives() - { - List? directives = null; - while (Peek(TokenKind.AT)) - (directives ??= new List()).Add(ParseDirective()); - - return directives; - } - - private GraphQLDocument ParseDocument() - { - _document = new GraphQLDocument(); - - int start = _currentToken.Start; - var definitions = ParseDefinitionsIfNotEOF(); - - SetCurrentComment(null); - - _document.Location = new GraphQLLocation - ( - start, - // Formally, to denote the end of the document, it is better to use _prevToken.End, - // since _prevToken represents some real meaningful token; _currentToken here is always EOF. - // EOF is a technical token with length = 0, _prevToken.End and _currentToken.End have the same value here. - _prevToken.End // equals to _currentToken.End (EOF) - ); - _document.Definitions = definitions; - _document.UnattachedComments = _unattachedComments; - return _document; - } - - private GraphQLEnumTypeDefinition ParseEnumTypeDefinition() - { - var comment = GetComment(); - int start = _currentToken.Start; - ExpectKeyword("enum"); - - return new GraphQLEnumTypeDefinition - { - Comment = comment, - Name = ParseName(), - Directives = ParseDirectives(), - Values = Many(TokenKind.BRACE_L, (ref ParserContext context) => context.ParseEnumValueDefinition(), TokenKind.BRACE_R), - Location = GetLocation(start) - }; - } - - private GraphQLValue ParseEnumValue(Token token) - { - Advance(); - return new GraphQLScalarValue(ASTNodeKind.EnumValue) - { - Value = token.Value, - Location = GetLocation(token.Start) - }; - } - - private GraphQLEnumValueDefinition ParseEnumValueDefinition() - { - var comment = GetComment(); - int start = _currentToken.Start; - - return new GraphQLEnumValueDefinition - { - Comment = comment, - Name = ParseName(), - Directives = ParseDirectives(), - Location = GetLocation(start) - }; - } - - private GraphQLFieldDefinition ParseFieldDefinition() - { - var comment = GetComment(); - int start = _currentToken.Start; - var name = ParseName(); - var args = ParseArgumentDefs(); - Expect(TokenKind.COLON); - - return new GraphQLFieldDefinition - { - Comment = comment, - Name = name, - Arguments = args, - Type = ParseType(), - Directives = ParseDirectives(), - Location = GetLocation(start) - }; - } - - private GraphQLFieldSelection ParseFieldSelection() - { - var comment = GetComment(); - int start = _currentToken.Start; - var nameOrAlias = ParseName(); - GraphQLName name; - GraphQLName? alias; - - if (Skip(TokenKind.COLON)) - { - name = ParseName(); - alias = nameOrAlias; - } else { - alias = null; - name = nameOrAlias; + Throw_From_Expect(kind); } - - return CreateFieldSelection(start, name, alias, comment); - } - - private GraphQLValue ParseFloat(/*bool isConstant*/) - { - var token = _currentToken; - Advance(); - return new GraphQLScalarValue(ASTNodeKind.FloatValue) - { - Value = token.Value, - Location = GetLocation(token.Start) - }; - } - - private ASTNode ParseFragment() - { - var comment = GetComment(); - int start = _currentToken.Start; - Expect(TokenKind.SPREAD); - - return Peek(TokenKind.NAME) && _currentToken.Value != "on" - ? CreateGraphQLFragmentSpread(start, comment) - : CreateInlineFragment(start, comment); } - private GraphQLFragmentDefinition ParseFragmentDefinition() - { - var comment = GetComment(); - int start = _currentToken.Start; - ExpectKeyword("fragment"); - - return new GraphQLFragmentDefinition - { - Comment = comment, - Name = ParseFragmentName(), - TypeCondition = ExpectOnKeywordAndParseNamedType(), - Directives = ParseDirectives(), - SelectionSet = ParseSelectionSet(), - Location = GetLocation(start) - }; - } - - private GraphQLName ParseFragmentName() + private void Throw_From_Expect(TokenKind kind) { - if (_currentToken.Value == "on") - { - Throw_From_ParseFragmentName(); - } - - return ParseName(); + throw new GraphQLSyntaxErrorException($"Expected {Token.GetTokenKindDescription(kind)}, found {_currentToken}", _source, _currentToken.Start); } - private void Throw_From_ParseFragmentName() - { - throw new GraphQLSyntaxErrorException($"Unexpected {_currentToken}", _source, _currentToken.Start); - } - - private List? ParseImplementsInterfaces() + private void ExpectKeyword(string keyword) { - List? types = null; - if (_currentToken.Value == "implements") - { - types = new List(); + if (_currentToken.Kind == TokenKind.NAME && _currentToken.Value == keyword) Advance(); - - do - { - types.Add(ParseNamedType()); - } - while (Peek(TokenKind.NAME)); - } - - return types; - } - - private GraphQLInputObjectTypeDefinition ParseInputObjectTypeDefinition() - { - var comment = GetComment(); - int start = _currentToken.Start; - ExpectKeyword("input"); - - return new GraphQLInputObjectTypeDefinition - { - Comment = comment, - Name = ParseName(), - Directives = ParseDirectives(), - Fields = Any(TokenKind.BRACE_L, (ref ParserContext context) => context.ParseInputValueDef(), TokenKind.BRACE_R), - Location = GetLocation(start) - }; - } - - private GraphQLInputValueDefinition ParseInputValueDef() - { - var comment = GetComment(); - int start = _currentToken.Start; - var name = ParseName(); - Expect(TokenKind.COLON); - - return new GraphQLInputValueDefinition - { - Comment = comment, - Name = name, - Type = ParseType(), - DefaultValue = GetDefaultConstantValue(), - Directives = ParseDirectives(), - Location = GetLocation(start) - }; - } - - private GraphQLValue ParseInt(/*bool isConstant*/) - { - var token = _currentToken; - Advance(); - - return new GraphQLScalarValue(ASTNodeKind.IntValue) - { - Value = token.Value, - Location = GetLocation(token.Start) - }; - } - - private GraphQLInterfaceTypeDefinition ParseInterfaceTypeDefinition() - { - var comment = GetComment(); - int start = _currentToken.Start; - ExpectKeyword("interface"); - - return new GraphQLInterfaceTypeDefinition - { - Comment = comment, - Name = ParseName(), - Directives = ParseDirectives(), - Fields = Any(TokenKind.BRACE_L, (ref ParserContext context) => context.ParseFieldDefinition(), TokenKind.BRACE_R), - Location = GetLocation(start) - }; - } - - private GraphQLValue ParseList(bool isConstant) - { - int start = _currentToken.Start; - // the compiler caches these delegates in the generated code - ParseCallback constant = (ref ParserContext context) => context.ParseConstantValue(); - ParseCallback value = (ref ParserContext context) => context.ParseValueValue(); - - return new GraphQLListValue(ASTNodeKind.ListValue) - { - Values = Any(TokenKind.BRACKET_L, isConstant ? constant : value, TokenKind.BRACKET_R), - Location = GetLocation(start), - AstValue = _source.Slice(start, _currentToken.End - start - 1) - }; - } - - private GraphQLName ParseName() - { - int start = _currentToken.Start; - var value = _currentToken.Value; - - Expect(TokenKind.NAME); - - return new GraphQLName - { - Location = GetLocation(start), - Value = value - }; - } - - private ASTNode? ParseNamedDefinition() - { - var value = _currentToken.Value; - - if (value == "query") - return ParseOperationDefinition(); - - if (value == "mutation") - return ParseOperationDefinition(); - - if (value == "subscription") - return ParseOperationDefinition(); - - if (value == "fragment") - return ParseFragmentDefinition(); - - if (value == "schema") - return ParseSchemaDefinition(); - - 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 == "extend") - return ParseTypeExtensionDefinition(); - - if (value == "directive") - return ParseDirectiveDefinition(); - - return null; - } - - private GraphQLNamedType ParseNamedType() - { - int start = _currentToken.Start; - return new GraphQLNamedType - { - Name = ParseName(), - Location = GetLocation(start) - }; - } - - private GraphQLValue ParseNameValue(/*bool isConstant*/) - { - var token = _currentToken; - - if (token.Value == "true" || token.Value == "false") - { - return ParseBooleanValue(token); - } - else if (!token.Value.IsEmpty) - { - return token.Value == "null" - ? ParseNullValue(token) - : ParseEnumValue(token); - } - - return Throw_From_ParseNameValue(); - } - - private GraphQLValue Throw_From_ParseNameValue() - { - throw new GraphQLSyntaxErrorException($"Unexpected {_currentToken}", _source, _currentToken.Start); - } - - private GraphQLValue ParseObject(bool isConstant) - { - var comment = GetComment(); - int start = _currentToken.Start; - - return new GraphQLObjectValue - { - Comment = comment, - Fields = ParseObjectFields(isConstant), - Location = GetLocation(start) - }; - } - - private GraphQLValue ParseNullValue(Token token) - { - Advance(); - return new GraphQLScalarValue(ASTNodeKind.NullValue) - { - Value = default, - Location = GetLocation(token.Start) - }; - } - - private GraphQLObjectField ParseObjectField(bool isConstant) - { - var comment = GetComment(); - int start = _currentToken.Start; - return new GraphQLObjectField - { - Comment = comment, - Name = ParseName(), - Value = ExpectColonAndParseValueLiteral(isConstant), - Location = GetLocation(start) - }; - } - - private List ParseObjectFields(bool isConstant) - { - var fields = new List(); - - Expect(TokenKind.BRACE_L); - while (!Skip(TokenKind.BRACE_R)) - fields.Add(ParseObjectField(isConstant)); - - return fields; - } - - private GraphQLObjectTypeDefinition ParseObjectTypeDefinition() - { - var comment = GetComment(); - - int start = _currentToken.Start; - ExpectKeyword("type"); - - return new GraphQLObjectTypeDefinition - { - Comment = comment, - Name = ParseName(), - Interfaces = ParseImplementsInterfaces(), - Directives = ParseDirectives(), - Fields = Any(TokenKind.BRACE_L, (ref ParserContext context) => context.ParseFieldDefinition(), TokenKind.BRACE_R), - Location = GetLocation(start) - }; - } - - private ASTNode ParseOperationDefinition() - { - int start = _currentToken.Start; - - return Peek(TokenKind.BRACE_L) - ? CreateOperationDefinition(start) - : CreateOperationDefinition(start, ParseOperationType(), GetName()); - } - - private OperationType ParseOperationType() - { - var token = _currentToken; - Expect(TokenKind.NAME); - - if (token.Value == "mutation") - return OperationType.Mutation; - - if (token.Value == "subscription") - return OperationType.Subscription; - - return OperationType.Query; - } - - private GraphQLOperationTypeDefinition ParseOperationTypeDefinition() - { - int start = _currentToken.Start; - var operation = ParseOperationType(); - Expect(TokenKind.COLON); - var type = ParseNamedType(); - - return new GraphQLOperationTypeDefinition - { - Operation = operation, - Type = type, - Location = GetLocation(start) - }; - } - - private GraphQLScalarTypeDefinition ParseScalarTypeDefinition() - { - var comment = GetComment(); - int start = _currentToken.Start; - ExpectKeyword("scalar"); - var name = ParseName(); - var directives = ParseDirectives(); - - return new GraphQLScalarTypeDefinition - { - Comment = comment, - Name = name, - Directives = directives, - Location = GetLocation(start) - }; - } - - private GraphQLSchemaDefinition ParseSchemaDefinition() - { - var comment = GetComment(); - int start = _currentToken.Start; - ExpectKeyword("schema"); - var directives = ParseDirectives(); - var operationTypes = Many(TokenKind.BRACE_L, (ref ParserContext context) => context.ParseOperationTypeDefinition(), TokenKind.BRACE_R); - - return new GraphQLSchemaDefinition - { - Comment = comment, - Directives = directives, - OperationTypes = operationTypes, - Location = GetLocation(start) - }; - } - - private ASTNode ParseSelection() - { - return Peek(TokenKind.SPREAD) ? - ParseFragment() : - ParseFieldSelection(); - } - - private GraphQLSelectionSet ParseSelectionSet() - { - int start = _currentToken.Start; - return new GraphQLSelectionSet - { - Selections = Many(TokenKind.BRACE_L, (ref ParserContext context) => context.ParseSelection(), TokenKind.BRACE_R), - Location = GetLocation(start) - }; - } - - private GraphQLValue ParseString(/*bool isConstant*/) - { - var token = _currentToken; - Advance(); - return new GraphQLScalarValue(ASTNodeKind.StringValue) - { - Value = token.Value, - Location = GetLocation(token.Start) - }; - } - - private GraphQLType ParseType() - { - GraphQLType type; - int start = _currentToken.Start; - if (Skip(TokenKind.BRACKET_L)) - { - type = ParseType(); - Expect(TokenKind.BRACKET_R); - type = new GraphQLListType - { - Type = type, - Location = GetLocation(start) - }; - } else - { - type = ParseNamedType(); - } - - return Skip(TokenKind.BANG) - ? new GraphQLNonNullType - { - Type = type, - Location = GetLocation(start) - } - : type; - } - - private GraphQLTypeExtensionDefinition ParseTypeExtensionDefinition() - { - var comment = GetComment(); - int start = _currentToken.Start; - ExpectKeyword("extend"); - var definition = ParseObjectTypeDefinition(); - - return new GraphQLTypeExtensionDefinition - { - Comment = comment, - Name = definition.Name, - Definition = definition, - Location = GetLocation(start) - }; - } - - private List ParseUnionMembers() - { - var members = new List(); - - // Union members may be defined with an optional leading | character - // to aid formatting when representing a longer list of possible types - Skip(TokenKind.PIPE); - - do - { - members.Add(ParseNamedType()); - } - while (Skip(TokenKind.PIPE)); - - return members; - } - - private GraphQLUnionTypeDefinition ParseUnionTypeDefinition() - { - var comment = GetComment(); - int start = _currentToken.Start; - ExpectKeyword("union"); - var name = ParseName(); - var directives = ParseDirectives(); - Expect(TokenKind.EQUALS); - var types = ParseUnionMembers(); - - return new GraphQLUnionTypeDefinition - { - Comment = comment, - Name = name, - Directives = directives, - Types = types, - Location = GetLocation(start) - }; + Throw_From_ExpectKeyword(keyword); } - private GraphQLValue ParseValueLiteral(bool isConstant) => _currentToken.Kind switch - { - TokenKind.BRACKET_L => ParseList(isConstant), - TokenKind.BRACE_L => ParseObject(isConstant), - TokenKind.INT => ParseInt(/*isConstant*/), - TokenKind.FLOAT => ParseFloat(/*isConstant*/), - TokenKind.STRING => ParseString(/*isConstant*/), - TokenKind.NAME => ParseNameValue(/*isConstant*/), - TokenKind.DOLLAR when !isConstant => ParseVariable(), - _ => Throw_From_ParseValueLiteral() - }; - - private GraphQLValue Throw_From_ParseValueLiteral() - { - throw new GraphQLSyntaxErrorException($"Unexpected {_currentToken}", _source, _currentToken.Start); - } - - private GraphQLValue ParseValueValue() => ParseValueLiteral(false); - - private GraphQLVariable ParseVariable() + private void Throw_From_ExpectKeyword(string keyword) { - int start = _currentToken.Start; - Expect(TokenKind.DOLLAR); - - return new GraphQLVariable - { - Name = GetName(), - Location = GetLocation(start) - }; + throw new GraphQLSyntaxErrorException($"Expected \"{keyword}\", found Name \"{_currentToken.Value}\"", _source, _currentToken.Start); } - private GraphQLVariableDefinition ParseVariableDefinition() + private GraphQLNamedType ExpectOnKeywordAndParseNamedType() { - var comment = GetComment(); - int start = _currentToken.Start; - - return new GraphQLVariableDefinition - { - Comment = comment, - Variable = ParseVariable(), - Type = AdvanceThroughColonAndParseType(), - DefaultValue = SkipEqualsAndParseValueLiteral(), - Location = GetLocation(start) - }; + ExpectKeyword("on"); + return ParseNamedType(); } - private List? ParseVariableDefinitions() + private GraphQLType ExpectColonAndParseType() { - return Peek(TokenKind.PAREN_L) ? - Many(TokenKind.PAREN_L, (ref ParserContext context) => context.ParseVariableDefinition(), TokenKind.PAREN_R) : - null; + Expect(TokenKind.COLON); + return ParseType(); } - private bool Peek(TokenKind kind) => _currentToken.Kind == kind; - - private bool Skip(TokenKind kind) + private GraphQLValue ExpectColonAndParseValueLiteral(bool isConstant) { - ParseComment(); - - bool isCurrentTokenMatching = _currentToken.Kind == kind; - - if (isCurrentTokenMatching) - { - Advance(); - } - - return isCurrentTokenMatching; + Expect(TokenKind.COLON); + return ParseValueLiteral(isConstant); } - - private object? SkipEqualsAndParseValueLiteral() => Skip(TokenKind.EQUALS) ? ParseValueLiteral(true) : null; } } diff --git a/src/GraphQLParser/ParserExtensions.cs b/src/GraphQLParser/ParserExtensions.cs index fd04b34b..385cd49a 100644 --- a/src/GraphQLParser/ParserExtensions.cs +++ b/src/GraphQLParser/ParserExtensions.cs @@ -22,9 +22,9 @@ public static class ParserExtensions /// Generates AST based on input text. /// /// Input data as a string. - /// Specifies whether to ignore comments when parsing GraphQL document. By default, all comments are ignored. + /// Parser options. /// AST (Abstract Syntax Tree) for GraphQL document. - public static GraphQLDocument Parse(this string source, bool ignoreComments = true) => Parser.Parse(source, ignoreComments); + public static GraphQLDocument Parse(this string source, ParserOptions options = default) => Parser.Parse(source, options); internal static (IMemoryOwner owner, ROM result) Concat(this List parts) { diff --git a/src/GraphQLParser/ParserOptions.cs b/src/GraphQLParser/ParserOptions.cs new file mode 100644 index 00000000..9af56523 --- /dev/null +++ b/src/GraphQLParser/ParserOptions.cs @@ -0,0 +1,14 @@ +namespace GraphQLParser +{ + /// + /// Parser options. + /// + public struct ParserOptions + { + /// + /// Options to selectively ignore some information when parsing GraphQL document. + /// By default, all comments are ignored. + /// + public IgnoreOptions Ignore { get; set; } + } +} diff --git a/src/GraphQLParser/Token.cs b/src/GraphQLParser/Token.cs index 8283b8f1..9aa03e9d 100644 --- a/src/GraphQLParser/Token.cs +++ b/src/GraphQLParser/Token.cs @@ -17,7 +17,7 @@ public Token(TokenKind kind, ROM value, int start, int end) } /// - /// Kind of token. + /// Kind of token. /// public TokenKind Kind { get; }