From 221af7b9584e5c473bbd15ae4ad7858537d76185 Mon Sep 17 00:00:00 2001 From: Mikkel Rasmussen Date: Sun, 30 Jun 2024 12:14:36 +0200 Subject: [PATCH 1/6] Added test for syntax review --- .../ConsoleAppGenerator.cs | 2 +- .../HelpTest.cs | 299 ++++++++++++++++++ .../RunTest.cs | 2 +- 3 files changed, 301 insertions(+), 2 deletions(-) diff --git a/src/ConsoleAppFramework/ConsoleAppGenerator.cs b/src/ConsoleAppFramework/ConsoleAppGenerator.cs index 14d78c6..4f0aa71 100644 --- a/src/ConsoleAppFramework/ConsoleAppGenerator.cs +++ b/src/ConsoleAppFramework/ConsoleAppGenerator.cs @@ -144,7 +144,7 @@ internal sealed class ArgumentAttribute : Attribute { } -[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)] internal sealed class CommandAttribute : Attribute { public string Command { get; } diff --git a/tests/ConsoleAppFramework.GeneratorTests/HelpTest.cs b/tests/ConsoleAppFramework.GeneratorTests/HelpTest.cs index 48778a2..14e66da 100644 --- a/tests/ConsoleAppFramework.GeneratorTests/HelpTest.cs +++ b/tests/ConsoleAppFramework.GeneratorTests/HelpTest.cs @@ -297,6 +297,305 @@ public void HelloWorld([Argument]int boo, string fooBar) hello my world. +Arguments: + [0] my boo is not boo. + +Options: + -f|-fb|--foo-bar my foo is not bar. (Required) + +"""); + } + + [Fact] + public void ClassCommandNoClassSummary() + { + var code = """ +var app = ConsoleApp.Create(); +app.Add(); +app.Run(args); + +[Command("mc")] +public class MyClass +{ + /// + /// hello my world. + /// + /// -b, my boo is not boo. + /// -f|-fb, my foo is not bar. + public void HelloWorld([Argument]int boo, string fooBar) + { + Console.Write("Hello World! " + fooBar); + } +} +"""; + + verifier.Execute(code, args: "--help", expected: """ +Usage: mc [command] [options...] [-h|--help] [--version] + +Commands: + hello-world + +"""); + verifier.Execute(code, args: "hello-world --help", expected: """ +Usage: hello-world [arguments...] [options...] [-h|--help] [--version] + +hello my world. + +Arguments: + [0] my boo is not boo. + +Options: + -f|-fb|--foo-bar my foo is not bar. (Required) + +"""); + } + + [Fact] + public void ClassCommandWithSummary() + { + var code = """ +var app = ConsoleApp.Create(); +app.Add(); +app.Run(args); + +/// +/// My class +/// +[Command("mc")] +public class MyClass +{ + /// + /// hello my world. + /// + /// -b, my boo is not boo. + /// -f|-fb, my foo is not bar. + public void HelloWorld([Argument]int boo, string fooBar) + { + Console.Write("Hello World! " + fooBar); + } +} +"""; + verifier.Execute(code, args: "--help", expected: """ +Usage: mc [command] [options...] [-h|--help] [--version] + +My class + +Commands: + hello-world + +"""); + + verifier.Execute(code, args: "mc hello-world --help", expected: """ +Usage: hello-world [arguments...] [options...] [-h|--help] [--version] + +hello my world. + +Arguments: + [0] my boo is not boo. + +Options: + -f|-fb|--foo-bar my foo is not bar. (Required) + +"""); + } + + [Fact] + public void AppWithNoRootDefaultDisplaySubcommand() + { + var code = """ +var app = ConsoleApp.Create(); +app.Add(); +app.Add(); +app.SubcommandHelp(DisplayType.Default); +app.Run(args); + +/// +/// My class +/// +[Command("mc")] +public class MyClass +{ + /// + /// hello my world. + /// + /// -b, my boo is not boo. + /// -f|-fb, my foo is not bar. + public void HelloWorld([Argument]int boo, string fooBar) + { + Console.Write("Hello World! " + fooBar); + } +} + +/// +/// My class 2 +/// +[Command("mc2")] +public class MyClass2 +{ + /// + /// hello my world. + /// + /// -b, my boo is not boo. + /// -f|-fb, my foo is not bar. + public void HelloWorld2([Argument]int boo, string fooBar) + { + Console.Write("Hello World2! " + fooBar); + } +} +"""; + verifier.Execute(code, args: "--help", expected: """ +Usage: [command] [options...] [-h|--help] [--version] + +Commands: + mc hello-world + mc2 hello-world2 + +"""); + + verifier.Execute(code, args: "mc hello-world --help", expected: """ +Usage: hello-world [arguments...] [options...] [-h|--help] [--version] + +hello my world. + +Arguments: + [0] my boo is not boo. + +Options: + -f|-fb|--foo-bar my foo is not bar. (Required) + +"""); + } + + [Fact] + public void AppWithClassRootWithHiddenSubcommands() + { + var code = """ +var app = ConsoleApp.Create(); +app.Add(); +app.Add(); +app.SubcommandHelp(DisplayType.Hidden); +app.Run(args); + +/// +/// My class +/// +[Command("mc")] +public class MyClass +{ + /// + /// hello my world. + /// + /// -b, my boo is not boo. + /// -f|-fb, my foo is not bar. + public void HelloWorld([Argument]int boo, string fooBar) + { + Console.Write("Hello World! " + fooBar); + } +} + +/// +/// My class 2 +/// +[Command("mc2")] +public class MyClass2 +{ + /// + /// hello my world. + /// + /// -b, my boo is not boo. + /// -f|-fb, my foo is not bar. + public void HelloWorld2([Argument]int boo, string fooBar) + { + Console.Write("Hello World2! " + fooBar); + } +} +"""; + verifier.Execute(code, args: "--help", expected: """ +Usage: [command] [options...] [-h|--help] [--version] + +Commands: + mc + mc2 + +"""); + + verifier.Execute(code, args: "mc hello-world --help", expected: """ +Usage: hello-world [arguments...] [options...] [-h|--help] [--version] + +hello my world. + +Arguments: + [0] my boo is not boo. + +Options: + -f|-fb|--foo-bar my foo is not bar. (Required) + +"""); + } + + [Fact] + public void AppWithClassRoot() + { + var code = """ +var app = ConsoleApp.Create(); +app.Add(); +app.Add(); +app.Run(args); + +/// +/// My class +/// +[Command("")] +public class Root +{ + /// + /// hello my world. + /// + /// -b, my boo is not boo. + /// -f|-fb, my foo is not bar. + public void HelloWorld([Argument]int boo, string fooBar) + { + Console.Write("Hello World! " + fooBar); + } +} + +/// +/// My class +/// +[Command("mc")] +public class MyClass +{ + /// + /// hello my world. + /// + /// -b, my boo is not boo. + /// -f|-fb, my foo is not bar. + public void HelloWorld2([Argument]int boo, string fooBar) + { + Console.Write("Hello World2! " + fooBar); + } +} +"""; + verifier.Execute(code, args: "--help", expected: """ +Usage: [command] | [arguments...] [options...] [-h|--help] [--version] + +Arguments: + [0] my boo is not boo. + +Options: + -f|-fb|--foo-bar my foo is not bar. (Required) + +Commands: + mc + mc hello-world + +"""); + + verifier.Execute(code, args: "mc hello-world --help", expected: """ +Usage: hello-world [arguments...] [options...] [-h|--help] [--version] + +hello my world. + Arguments: [0] my boo is not boo. diff --git a/tests/ConsoleAppFramework.GeneratorTests/RunTest.cs b/tests/ConsoleAppFramework.GeneratorTests/RunTest.cs index 98b181e..3043f6d 100644 --- a/tests/ConsoleAppFramework.GeneratorTests/RunTest.cs +++ b/tests/ConsoleAppFramework.GeneratorTests/RunTest.cs @@ -96,7 +96,7 @@ public class Obj public int Foo { get; set; } } } -""", "--foo 10 --bar aiueo --ft Grape --flag --half 1.3 --itt 99 --obj {\"Foo\":1999}", "10aiueoGrapeTrue1.3991999"); +""", "--foo 10 --bar aiueo --ft Grape --flag --half 1.3 --itt 99 --obj {\"Foo\":1999}", "10aiueoGrapeTrue13991999"); } [Fact] From a4b4c4190716bdd7cf407d8db43d60808113621e Mon Sep 17 00:00:00 2001 From: Mikkel Rasmussen Date: Sun, 30 Jun 2024 12:34:48 +0200 Subject: [PATCH 2/6] Fix Class with Root and Added RootClassWithRootCommand --- .../HelpTest.cs | 70 ++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/tests/ConsoleAppFramework.GeneratorTests/HelpTest.cs b/tests/ConsoleAppFramework.GeneratorTests/HelpTest.cs index 14e66da..48b37e4 100644 --- a/tests/ConsoleAppFramework.GeneratorTests/HelpTest.cs +++ b/tests/ConsoleAppFramework.GeneratorTests/HelpTest.cs @@ -577,7 +577,74 @@ public void HelloWorld2([Argument]int boo, string fooBar) } """; verifier.Execute(code, args: "--help", expected: """ -Usage: [command] | [arguments...] [options...] [-h|--help] [--version] +Usage: [command] [options...] [-h|--help] [--version] + +Commands: + mc hello-world + hello-world + +"""); + + verifier.Execute(code, args: "mc hello-world --help", expected: """ +Usage: hello-world [arguments...] [options...] [-h|--help] [--version] + +hello my world. + +Arguments: + [0] my boo is not boo. + +Options: + -f|-fb|--foo-bar my foo is not bar. (Required) + +"""); + } + + [Fact] + public void AppWithClassRootAndRootCommand() + { + var code = """ +var app = ConsoleApp.Create(); +app.Add(); +app.Add(); +app.Run(args); + +/// +/// My class +/// +[Command("")] +public class Root +{ + /// + /// hello my world. + /// + /// -b, my boo is not boo. + /// -f|-fb, my foo is not bar. + [Command("")] + public void HelloWorld([Argument]int boo, string fooBar) + { + Console.Write("Hello World! " + fooBar); + } +} + +/// +/// My class +/// +[Command("mc")] +public class MyClass +{ + /// + /// hello my world. + /// + /// -b, my boo is not boo. + /// -f|-fb, my foo is not bar. + public void HelloWorld2([Argument]int boo, string fooBar) + { + Console.Write("Hello World2! " + fooBar); + } +} +"""; + verifier.Execute(code, args: "--help", expected: """ +Usage: [command] | hello-world [arguments...] [options...] [-h|--help] [--version] Arguments: [0] my boo is not boo. @@ -586,7 +653,6 @@ public void HelloWorld2([Argument]int boo, string fooBar) -f|-fb|--foo-bar my foo is not bar. (Required) Commands: - mc mc hello-world """); From c3396ec3e8e8913e8989f3e4e652ee8586ecb213 Mon Sep 17 00:00:00 2001 From: Mikkel Rasmussen Date: Sun, 30 Jun 2024 22:29:00 +0200 Subject: [PATCH 3/6] Added command summaries to the test result --- .../HelpTest.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/ConsoleAppFramework.GeneratorTests/HelpTest.cs b/tests/ConsoleAppFramework.GeneratorTests/HelpTest.cs index 48b37e4..b98058b 100644 --- a/tests/ConsoleAppFramework.GeneratorTests/HelpTest.cs +++ b/tests/ConsoleAppFramework.GeneratorTests/HelpTest.cs @@ -333,7 +333,7 @@ public void HelloWorld([Argument]int boo, string fooBar) Usage: mc [command] [options...] [-h|--help] [--version] Commands: - hello-world + hello-world hello my world. """); verifier.Execute(code, args: "hello-world --help", expected: """ @@ -381,7 +381,7 @@ public void HelloWorld([Argument]int boo, string fooBar) My class Commands: - hello-world + hello-world hello my world. """); @@ -447,8 +447,8 @@ public void HelloWorld2([Argument]int boo, string fooBar) Usage: [command] [options...] [-h|--help] [--version] Commands: - mc hello-world - mc2 hello-world2 + mc hello-world hello my world. + mc2 hello-world2 hello my world. """); @@ -514,8 +514,8 @@ public void HelloWorld2([Argument]int boo, string fooBar) Usage: [command] [options...] [-h|--help] [--version] Commands: - mc - mc2 + mc My class + mc2 My class 2 """); @@ -580,8 +580,8 @@ public void HelloWorld2([Argument]int boo, string fooBar) Usage: [command] [options...] [-h|--help] [--version] Commands: - mc hello-world - hello-world + hello-world My classz + mc hello-world hello my world. """); @@ -653,7 +653,7 @@ public void HelloWorld2([Argument]int boo, string fooBar) -f|-fb|--foo-bar my foo is not bar. (Required) Commands: - mc hello-world + mc hello-world hello my world. """); From caba160cfd7e8ac9a556b14ec6f9557b4e4a204e Mon Sep 17 00:00:00 2001 From: Mikkel Rasmussen Date: Mon, 1 Jul 2024 00:01:14 +0200 Subject: [PATCH 4/6] Added parsable Class-based CommandAttribute assignment --- src/ConsoleAppFramework/Parser.cs | 35 +++++++++++++++++-- .../HelpTest.cs | 11 +++--- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/ConsoleAppFramework/Parser.cs b/src/ConsoleAppFramework/Parser.cs index 019bf15..1a95088 100644 --- a/src/ConsoleAppFramework/Parser.cs +++ b/src/ConsoleAppFramework/Parser.cs @@ -55,8 +55,32 @@ internal class Parser(DiagnosticReporter context, InvocationExpressionSyntax nod var genericName = (node.Expression as MemberAccessExpressionSyntax)?.Name as GenericNameSyntax; var genericType = genericName!.TypeArgumentList.Arguments[0]; - // Add(string commandPath) string? commandPath = null; + + if (genericName != null) + { + var className = genericName.TypeArgumentList.Arguments.First().ToString(); + + // Find the class declaration with the matching class name + var classDeclaration = node.SyntaxTree.GetRoot().DescendantNodes() + .OfType() + .FirstOrDefault(c => c.Identifier.Text == className); + + if (classDeclaration != null) + { + var commandAttribute = classDeclaration.AttributeLists + .SelectMany(al => al.Attributes) + .FirstOrDefault(a => a.Name.ToString() == "Command"); + + var attributeArgument = commandAttribute?.ArgumentList?.Arguments.FirstOrDefault()?.ToString().Trim('"'); + if (attributeArgument != null) + { + commandPath = attributeArgument; + } + } + } + + // Add(string commandPath) var args = node.ArgumentList.Arguments; if (node.ArgumentList.Arguments.Count == 1) { @@ -67,7 +91,14 @@ internal class Parser(DiagnosticReporter context, InvocationExpressionSyntax nod return []; } - commandPath = (commandName.Expression as LiteralExpressionSyntax)!.Token.ValueText; + if (commandPath == null) + { + commandPath = (commandName.Expression as LiteralExpressionSyntax)!.Token.ValueText; + } + else + { + context.ReportDiagnostic(DiagnosticDescriptors.DuplicateCommandName, commandName.GetLocation(), commandPath); + } } // T diff --git a/tests/ConsoleAppFramework.GeneratorTests/HelpTest.cs b/tests/ConsoleAppFramework.GeneratorTests/HelpTest.cs index b98058b..9d5f3b4 100644 --- a/tests/ConsoleAppFramework.GeneratorTests/HelpTest.cs +++ b/tests/ConsoleAppFramework.GeneratorTests/HelpTest.cs @@ -311,7 +311,7 @@ public void ClassCommandNoClassSummary() { var code = """ var app = ConsoleApp.Create(); -app.Add(); +app.Add("mc"); app.Run(args); [Command("mc")] @@ -406,7 +406,6 @@ public void AppWithNoRootDefaultDisplaySubcommand() var app = ConsoleApp.Create(); app.Add(); app.Add(); -app.SubcommandHelp(DisplayType.Default); app.Run(args); /// @@ -444,16 +443,16 @@ public void HelloWorld2([Argument]int boo, string fooBar) } """; verifier.Execute(code, args: "--help", expected: """ -Usage: [command] [options...] [-h|--help] [--version] +Usage: [command] [-h|--help] [--version] Commands: - mc hello-world hello my world. - mc2 hello-world2 hello my world. + mc hello-world hello my world. + mc2 hello-world2 hello my world. """); verifier.Execute(code, args: "mc hello-world --help", expected: """ -Usage: hello-world [arguments...] [options...] [-h|--help] [--version] +Usage: mc hello-world [arguments...] [options...] [-h|--help] [--version] hello my world. From e8d89d3a51cc3b1de3d9c4aae4fd0ff0e7eccf86 Mon Sep 17 00:00:00 2001 From: Mikkel Rasmussen Date: Mon, 1 Jul 2024 00:38:30 +0200 Subject: [PATCH 5/6] Added SubcommandHelp and enum --- src/ConsoleAppFramework/ConsoleAppGenerator.cs | 13 ++++++++++++- .../ConsoleAppFramework.GeneratorTests/HelpTest.cs | 12 +++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/ConsoleAppFramework/ConsoleAppGenerator.cs b/src/ConsoleAppFramework/ConsoleAppGenerator.cs index 4f0aa71..4d794e2 100644 --- a/src/ConsoleAppFramework/ConsoleAppGenerator.cs +++ b/src/ConsoleAppFramework/ConsoleAppGenerator.cs @@ -63,7 +63,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var expr = invocationExpression.Expression as MemberAccessExpressionSyntax; var methodName = expr?.Name.Identifier.Text; - if (methodName is "Add" or "UseFilter" or "Run" or "RunAsync") + if (methodName is "Add" or "SubcommandHelp" or "UseFilter" or "Run" or "RunAsync") { return true; } @@ -457,6 +457,12 @@ public void Dispose() } } + public enum DisplayType + { + Default, + Hidden + } + internal partial struct ConsoleAppBuilder { public ConsoleAppBuilder() @@ -468,6 +474,11 @@ public void Add(string commandName, Delegate command) AddCore(commandName, command); } + public void SubcommandHelp(DisplayType displayType) + { + // Not Implemented yet + } + [System.Diagnostics.Conditional("DEBUG")] public void Add() { } diff --git a/tests/ConsoleAppFramework.GeneratorTests/HelpTest.cs b/tests/ConsoleAppFramework.GeneratorTests/HelpTest.cs index 9d5f3b4..cb87030 100644 --- a/tests/ConsoleAppFramework.GeneratorTests/HelpTest.cs +++ b/tests/ConsoleAppFramework.GeneratorTests/HelpTest.cs @@ -472,7 +472,7 @@ public void AppWithClassRootWithHiddenSubcommands() var app = ConsoleApp.Create(); app.Add(); app.Add(); -app.SubcommandHelp(DisplayType.Hidden); +app.SubcommandHelp(ConsoleApp.DisplayType.Hidden); app.Run(args); /// @@ -516,6 +516,16 @@ public void HelloWorld2([Argument]int boo, string fooBar) mc My class mc2 My class 2 +"""); + + verifier.Execute(code, args: "mc --help", expected: """ +Usage: mc [command] [options...] [-h|--help] [--version] + +My class + +Commands: + hello-world hello my world. + """); verifier.Execute(code, args: "mc hello-world --help", expected: """ From 7062a89ad4e225c1ca1c2a7c51244bbffd09f6e7 Mon Sep 17 00:00:00 2001 From: Mikkel Luja Rasmussen Date: Mon, 1 Jul 2024 18:03:51 +0200 Subject: [PATCH 6/6] Moved from local syntax tree to global, added tests --- src/ConsoleAppFramework/Parser.cs | 2 +- .../HelpTest.cs | 154 ++++++++++++++++++ 2 files changed, 155 insertions(+), 1 deletion(-) diff --git a/src/ConsoleAppFramework/Parser.cs b/src/ConsoleAppFramework/Parser.cs index 1a95088..18234b0 100644 --- a/src/ConsoleAppFramework/Parser.cs +++ b/src/ConsoleAppFramework/Parser.cs @@ -62,7 +62,7 @@ internal class Parser(DiagnosticReporter context, InvocationExpressionSyntax nod var className = genericName.TypeArgumentList.Arguments.First().ToString(); // Find the class declaration with the matching class name - var classDeclaration = node.SyntaxTree.GetRoot().DescendantNodes() + var classDeclaration = model.SyntaxTree.GetRoot().DescendantNodes() .OfType() .FirstOrDefault(c => c.Identifier.Text == className); diff --git a/tests/ConsoleAppFramework.GeneratorTests/HelpTest.cs b/tests/ConsoleAppFramework.GeneratorTests/HelpTest.cs index cb87030..1e3e392 100644 --- a/tests/ConsoleAppFramework.GeneratorTests/HelpTest.cs +++ b/tests/ConsoleAppFramework.GeneratorTests/HelpTest.cs @@ -599,6 +599,85 @@ mc hello-world hello my world. hello my world. +Arguments: + [0] my boo is not boo. + +Options: + -f|-fb|--foo-bar my foo is not bar. (Required) + +"""); + } + + [Fact] + public void AppWithClassWithSameCommandName() + { + var code = """ +var app = ConsoleApp.Create(); +app.Add(); +app.Add(); +app.Run(args); + +/// +/// My class +/// +[Command("mc1")] +public class MyClass1 +{ + /// + /// hello my world. + /// + /// -b, my boo is not boo. + /// -f|-fb, my foo is not bar. + public void HelloWorld([Argument]int boo, string fooBar) + { + Console.Write("Hello World! " + fooBar); + } +} + +/// +/// My class +/// +[Command("mc2")] +public class MyClass2 +{ + /// + /// hello my world. + /// + /// -b, my boo is not boo. + /// -f|-fb, my foo is not bar. + public void HelloWorld([Argument]int boo, string fooBar) + { + Console.Write("Hello World2! " + fooBar); + } +} +"""; + verifier.Execute(code, args: "--help", expected: """ +Usage: [command] [-h|--help] [--version] + +Commands: + mc1 hello-world hello my world. + mc2 hello-world hello my world. + +"""); + + verifier.Execute(code, args: "mc1 hello-world --help", expected: """ +Usage: mc1 hello-world [arguments...] [options...] [-h|--help] [--version] + +hello my world. + +Arguments: + [0] my boo is not boo. + +Options: + -f|-fb|--foo-bar my foo is not bar. (Required) + +"""); + + verifier.Execute(code, args: "mc2 hello-world --help", expected: """ +Usage: mc2 hello-world [arguments...] [options...] [-h|--help] [--version] + +hello my world. + Arguments: [0] my boo is not boo. @@ -671,6 +750,81 @@ mc hello-world hello my world. hello my world. +Arguments: + [0] my boo is not boo. + +Options: + -f|-fb|--foo-bar my foo is not bar. (Required) + +"""); + } + + [Fact] + public void AppWithClassRootAndRootCommandWithSameCommandName() + { + var code = """ +var app = ConsoleApp.Create(); +app.Add(); +app.Add(); +app.Run(args); + +/// +/// My class +/// +[Command("")] +public class Root +{ + /// + /// hello my world. + /// + /// -b, my boo is not boo. + /// -f|-fb, my foo is not bar. + [Command("")] + public void HelloWorld([Argument]int boo, string fooBar) + { + Console.Write("Hello World! " + fooBar); + } +} + +/// +/// My class +/// +[Command("mc")] +public class MyClass +{ + /// + /// hello my world. + /// + /// -b, my boo is not boo. + /// -f|-fb, my foo is not bar. + public void HelloWorld([Argument]int boo, string fooBar) + { + Console.Write("Hello World2! " + fooBar); + } +} +"""; + + verifier.Ok(code); + + verifier.Execute(code, args: "--help", expected: """ +Usage: [command] | hello-world [arguments...] [options...] [-h|--help] [--version] + +Arguments: + [0] my boo is not boo. + +Options: + -f|-fb|--foo-bar my foo is not bar. (Required) + +Commands: + mc hello-world hello my world. + +"""); + + verifier.Execute(code, args: "mc hello-world --help", expected: """ +Usage: hello-world [arguments...] [options...] [-h|--help] [--version] + +hello my world. + Arguments: [0] my boo is not boo.