diff --git a/src/GraphQLParser.ApiTests/GraphQLParser.approved.txt b/src/GraphQLParser.ApiTests/GraphQLParser.approved.txt index a3573690..bbd773b7 100644 --- a/src/GraphQLParser.ApiTests/GraphQLParser.approved.txt +++ b/src/GraphQLParser.ApiTests/GraphQLParser.approved.txt @@ -907,6 +907,12 @@ namespace GraphQLParser.Visitors public System.IO.TextWriter Writer { get; } } } + public enum SDLPrinterArgumentsMode + { + None = 0, + PreferNewLine = 1, + ForceNewLine = 2, + } public static class SDLPrinterExtensions { public static string Print(this GraphQLParser.Visitors.SDLPrinter printer, GraphQLParser.AST.ASTNode node) { } @@ -916,6 +922,7 @@ namespace GraphQLParser.Visitors public class SDLPrinterOptions { public SDLPrinterOptions() { } + public GraphQLParser.Visitors.SDLPrinterArgumentsMode ArgumentsPrintMode { get; set; } public bool EachDirectiveLocationOnNewLine { get; set; } public bool EachUnionMemberOnNewLine { get; set; } public int IndentSize { get; set; } diff --git a/src/GraphQLParser.Tests/Visitors/SDLPrinterFromParsedTextTests.cs b/src/GraphQLParser.Tests/Visitors/SDLPrinterFromParsedTextTests.cs index 0fdff1e3..8a3c6faa 100644 --- a/src/GraphQLParser.Tests/Visitors/SDLPrinterFromParsedTextTests.cs +++ b/src/GraphQLParser.Tests/Visitors/SDLPrinterFromParsedTextTests.cs @@ -557,8 +557,19 @@ type Query { @"directive @skip(""Skipped when true."" if: Boolean!, x: Some) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT", @"directive @skip( ""Skipped when true."" - if: Boolean!, x: Some) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT")] + if: Boolean!, x: Some) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT", true, true, false, false, 2, SDLPrinterArgumentsMode.None)] [InlineData(43, +@"directive @skip(if: Boolean!, x: Some) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT", +@"directive @skip( + if: Boolean!, + x: Some) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT", true, true, false, false, 2, SDLPrinterArgumentsMode.ForceNewLine)] + [InlineData(44, +@"directive @skip(""Skipped when true."" if: Boolean!, x: Some) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT", +@"directive @skip( + ""Skipped when true."" + if: Boolean!, + x: Some) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT")] + [InlineData(45, """directive @skip("Skipped when true." if: Boolean!, "Second argument" x: Some) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT""", """ directive @skip( "Skipped when true." @@ -566,7 +577,7 @@ directive @skip( "Second argument" x: Some) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT """)] - [InlineData(44, + [InlineData(46, "schema { query: Q mutation: M subscription: S }", """ schema { @@ -575,7 +586,7 @@ directive @skip( subscription: S } """, true, true, false, false, 5)] - [InlineData(45, + [InlineData(47, """ "A component contains the parametric details of a PCB part." input DesComponentFilterInput { @@ -606,7 +617,7 @@ input DesComponentFilterInput { revision: DesRevisionFilterInput } """)] - [InlineData(46, + [InlineData(48, """ # comment directive @my on FIELD @@ -615,7 +626,7 @@ directive @my on FIELD # comment directive @my on FIELD """)] - [InlineData(47, + [InlineData(49, """ query q # comment @@ -628,7 +639,7 @@ query q x } """)] - [InlineData(48, + [InlineData(50, """ query q ( @@ -642,7 +653,7 @@ query q( x } """)] - [InlineData(49, + [InlineData(51, """ "description" schema { @@ -655,7 +666,7 @@ query q( query: Query } """)] - [InlineData(50, + [InlineData(52, """ type Query { "Fetches an object given its ID." @@ -691,7 +702,7 @@ type Query { id: ID!): DesWorkspace } """)] - [InlineData(51, + [InlineData(53, """ type Query { user @@ -710,8 +721,50 @@ type Query { # comment 2 id: ID!, name: Name!): Node } +""", true, true, false, false, 2, SDLPrinterArgumentsMode.None)] + [InlineData(54, +""" +type Query { + user + # comment 1 + ( + # comment 2 + id: ID! + name: Name!): Node +} +""", +""" +type Query { + user + # comment 1 + ( + # comment 2 + id: ID!, + name: Name!): Node +} +""", true, true, false, false, 2, SDLPrinterArgumentsMode.ForceNewLine)] + [InlineData(55, +""" +type Query { + user + # comment 1 + ( + # comment 2 + id: ID! + name: Name!): Node +} +""", +""" +type Query { + user + # comment 1 + ( + # comment 2 + id: ID!, + name: Name!): Node +} """)] - [InlineData(52, + [InlineData(56, """ directive @my # comment 1 @@ -726,7 +779,7 @@ directive @my # comment 2 arg: Boolean!) on FIELD """)] - [InlineData(53, + [InlineData(57, """ query Q { field1(arg1: 1) { @@ -745,7 +798,7 @@ query Q { } } """)] - [InlineData(54, + [InlineData(58, """ query Q { field1 @@ -791,7 +844,7 @@ query Q { } } """)] - [InlineData(55, + [InlineData(59, """ fragment f #comment @@ -804,7 +857,7 @@ on Person { name } """)] - [InlineData(56, + [InlineData(60, """ type Person #comment @@ -817,7 +870,7 @@ implements Entity { name: String } """)] - [InlineData(57, + [InlineData(61, """ type Person #comment @@ -834,7 +887,7 @@ implements Entity & name: String } """)] - [InlineData(58, + [InlineData(62, """" "description" type Person { @@ -846,6 +899,28 @@ type Person { name: String } """, false, false)] + [InlineData(63, // https://github.com/graphql-dotnet/parser/issues/330 +"""" +type DesPcb { + designItems("An optional array of designators to search." designators: [String!] "Returns the first _n_ elements from the list." first: Int "Returns the elements in the list that come after the specified cursor." after: String "Returns the last _n_ elements from the list." last: Int "Returns the elements in the list that come before the specified cursor." before: String where: DesDesignItemFilterInput): DesDesignItemConnection +} +"""", +""" +type DesPcb { + designItems( + "An optional array of designators to search." + designators: [String!], + "Returns the first _n_ elements from the list." + first: Int, + "Returns the elements in the list that come after the specified cursor." + after: String, + "Returns the last _n_ elements from the list." + last: Int, + "Returns the elements in the list that come before the specified cursor." + before: String, + where: DesDesignItemFilterInput): DesDesignItemConnection +} +""")] public async Task SDLPrinter_Should_Print_Document( int number, string text, @@ -854,7 +929,8 @@ public async Task SDLPrinter_Should_Print_Document( bool writeDescriptions = true, bool eachDirectiveLocationOnNewLine = false, bool eachUnionMemberOnNewLine = false, - int indentSize = 2) + int indentSize = 2, + SDLPrinterArgumentsMode mode = SDLPrinterArgumentsMode.PreferNewLine) { var printer = new SDLPrinter(new SDLPrinterOptions { @@ -863,6 +939,7 @@ public async Task SDLPrinter_Should_Print_Document( EachDirectiveLocationOnNewLine = eachDirectiveLocationOnNewLine, EachUnionMemberOnNewLine = eachUnionMemberOnNewLine, IndentSize = indentSize, + ArgumentsPrintMode = mode, }); var writer = new StringWriter(); var document = text.Parse(); diff --git a/src/GraphQLParser/AST/Definitions/GraphQLInputValueDefinition.cs b/src/GraphQLParser/AST/Definitions/GraphQLInputValueDefinition.cs index 13ff289e..0cd52d2e 100644 --- a/src/GraphQLParser/AST/Definitions/GraphQLInputValueDefinition.cs +++ b/src/GraphQLParser/AST/Definitions/GraphQLInputValueDefinition.cs @@ -1,8 +1,11 @@ +using System.Diagnostics; + namespace GraphQLParser.AST; /// /// AST node for . /// +[DebuggerDisplay("GraphQLInputValueDefinition: {Name}: {Type}")] public class GraphQLInputValueDefinition : GraphQLTypeDefinition, IHasDirectivesNode, IHasDefaultValueNode { internal GraphQLInputValueDefinition() diff --git a/src/GraphQLParser/Visitors/SDLPrinter.cs b/src/GraphQLParser/Visitors/SDLPrinter.cs index 7945ef7d..db14e4eb 100644 --- a/src/GraphQLParser/Visitors/SDLPrinter.cs +++ b/src/GraphQLParser/Visitors/SDLPrinter.cs @@ -480,13 +480,6 @@ protected override async ValueTask VisitInputObjectTypeExtensionAsync(GraphQLInp /// protected override async ValueTask VisitInputValueDefinitionAsync(GraphQLInputValueDefinition inputValueDefinition, TContext context) { - bool hasParent = TryPeekParent(context, out var parent); - - if (hasParent && parent is GraphQLArgumentsDefinition argsDef && argsDef.Items.IndexOf(inputValueDefinition) > 0) - { - await context.WriteAsync(inputValueDefinition.Description == null ? ", " : ",").ConfigureAwait(false); - } - await VisitAsync(inputValueDefinition.Comments, context).ConfigureAwait(false); await VisitAsync(inputValueDefinition.Description, context).ConfigureAwait(false); await VisitAsync(inputValueDefinition.Name, context).ConfigureAwait(false); @@ -714,10 +707,12 @@ protected override async ValueTask VisitArgumentsDefinitionAsync(GraphQLArgument { await VisitAsync(argumentsDefinition.Comments, context).ConfigureAwait(false); await VisitAsync(LiteralNode.Wrap("("), context).ConfigureAwait(false); - - foreach (var argumentDefinition in argumentsDefinition.Items) - await VisitAsync(argumentDefinition, context).ConfigureAwait(false); - + for (int i = 0; i < argumentsDefinition.Items.Count; ++i) + { + await VisitAsync(argumentsDefinition.Items[i], context).ConfigureAwait(false); + if (i < argumentsDefinition.Items.Count - 1) + await context.WriteAsync(",").ConfigureAwait(false); + } await context.WriteAsync(")").ConfigureAwait(false); } @@ -905,9 +900,39 @@ node is GraphQLInputValueDefinition if ((context.LastVisitedNode is GraphQLFragmentSpread || context.LastVisitedNode is GraphQLSelectionSet) && !context.IndentPrinted) await context.WriteLineAsync().ConfigureAwait(false); + // ensure NewLine before printing argument if previous node did not print NewLine + // https://github.com/graphql-dotnet/parser/issues/330 + _ = TryPeekParent(context, out var parent); + if (!context.IndentPrinted && node is GraphQLInputValueDefinition && parent is GraphQLArgumentsDefinition arguments) + { + switch (Options.ArgumentsPrintMode) + { + case SDLPrinterArgumentsMode.ForceNewLine: + await context.WriteLineAsync().ConfigureAwait(false); + break; + + case SDLPrinterArgumentsMode.PreferNewLine: + foreach (var arg in arguments.Items) + { + if (HasPrintableComments(arg) || HasPrintableDescription(arg)) + { + await context.WriteLineAsync().ConfigureAwait(false); + break; + } + } + break; + + default: + break; + } + } + // ensure proper indentation on the current line before printing new node if (context.NewLinePrinted) await WriteIndentAsync(context).ConfigureAwait(false); + // otherwise ensure single whitespace indentation for all arguments in list except the first one + else if (parent is GraphQLArgumentsDefinition argsDef && node is GraphQLInputValueDefinition input && argsDef.Items.IndexOf(input) != 0) + await context.WriteAsync(" ").ConfigureAwait(false); if (node is LiteralNode literalNode) // base.VisitAsync will throw on unknown node await context.WriteAsync(literalNode.Literal).ConfigureAwait(false); @@ -969,6 +994,8 @@ private async ValueTask WriteIndentAsync(TContext context) private bool HasPrintableComments(ASTNode node) => node.Comments?.Count > 0 && Options.PrintComments; + private bool HasPrintableDescription(IHasDescriptionNode node) => node.Description != null && Options.PrintDescriptions; + // Returns parent if called inside VisitXXX i.e. after context.Parents.Push(node); // Returns grand-parent if called inside VisitAsync i.e. before context.Parents.Push(node); private static bool TryPeekParent(TContext context, [NotNullWhen(true)] out ASTNode? node) @@ -1136,6 +1163,12 @@ public class SDLPrinterOptions /// public bool EachUnionMemberOnNewLine { get; set; } + /// + /// How to print each argument definition. + /// By default . + /// + public SDLPrinterArgumentsMode ArgumentsPrintMode { get; set; } = SDLPrinterArgumentsMode.PreferNewLine; + /// /// The size of the horizontal indentation in spaces. /// By default 2. @@ -1144,7 +1177,7 @@ public class SDLPrinterOptions } /// -/// Preudo AST node to allow calls to +/// Pseudo AST node to allow calls to /// for indentation purposes. Any literal printed first after optional comment or description nodes in /// any VisitXXX method should be wrapped into for proper indentation. /// diff --git a/src/GraphQLParser/Visitors/SDLPrinterArgumentsMode.cs b/src/GraphQLParser/Visitors/SDLPrinterArgumentsMode.cs new file mode 100644 index 00000000..69930bc7 --- /dev/null +++ b/src/GraphQLParser/Visitors/SDLPrinterArgumentsMode.cs @@ -0,0 +1,24 @@ +namespace GraphQLParser.Visitors; + +/// +/// Defines how to print arguments definitions. +/// +public enum SDLPrinterArgumentsMode +{ + /// + /// Print argument on new line only if it requires new line, i.e. has comment or description. + /// Otherwise argument shares the same line with previous argument. + /// + None, + + /// + /// Print all arguments on new line if any argument requires new line, i.e. has comment or description. + /// This is default mode used by . + /// + PreferNewLine, + + /// + /// Always print all arguments on new line. + /// + ForceNewLine, +}