diff --git a/System.CommandLine.sln b/System.CommandLine.sln
index bb83d4f820..7441d0ef12 100644
--- a/System.CommandLine.sln
+++ b/System.CommandLine.sln
@@ -58,6 +58,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.CommandLine.Hosting.
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HostingPlayground", "samples\HostingPlayground\HostingPlayground.csproj", "{0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.CommandLine.CommandGenerator", "src\System.CommandLine.CommandGenerator\System.CommandLine.CommandGenerator.csproj", "{B0D00128-E41B-4648-9D22-9B91F8F6BF0C}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -236,6 +238,18 @@ Global
{0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}.Release|x64.Build.0 = Release|Any CPU
{0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}.Release|x86.ActiveCfg = Release|Any CPU
{0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}.Release|x86.Build.0 = Release|Any CPU
+ {B0D00128-E41B-4648-9D22-9B91F8F6BF0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B0D00128-E41B-4648-9D22-9B91F8F6BF0C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B0D00128-E41B-4648-9D22-9B91F8F6BF0C}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {B0D00128-E41B-4648-9D22-9B91F8F6BF0C}.Debug|x64.Build.0 = Debug|Any CPU
+ {B0D00128-E41B-4648-9D22-9B91F8F6BF0C}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {B0D00128-E41B-4648-9D22-9B91F8F6BF0C}.Debug|x86.Build.0 = Debug|Any CPU
+ {B0D00128-E41B-4648-9D22-9B91F8F6BF0C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B0D00128-E41B-4648-9D22-9B91F8F6BF0C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B0D00128-E41B-4648-9D22-9B91F8F6BF0C}.Release|x64.ActiveCfg = Release|Any CPU
+ {B0D00128-E41B-4648-9D22-9B91F8F6BF0C}.Release|x64.Build.0 = Release|Any CPU
+ {B0D00128-E41B-4648-9D22-9B91F8F6BF0C}.Release|x86.ActiveCfg = Release|Any CPU
+ {B0D00128-E41B-4648-9D22-9B91F8F6BF0C}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -255,6 +269,7 @@ Global
{644C4B4A-4A32-4307-9F71-C3BF901FFB66} = {E5B1EC71-0FC4-4FAA-9C65-32D5016FBC45}
{39483140-BC26-4CAD-BBAE-3DC76C2F16CF} = {E5B1EC71-0FC4-4FAA-9C65-32D5016FBC45}
{0BF6958D-9EE3-4623-B3D6-4DA77EAC1906} = {6749FB3E-39DE-4321-A39E-525278E9408D}
+ {B0D00128-E41B-4648-9D22-9B91F8F6BF0C} = {E5B1EC71-0FC4-4FAA-9C65-32D5016FBC45}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5C159F93-800B-49E7-9905-EE09F8B8434A}
diff --git a/eng/source-build-patches/0001-Exclude-dotnet-suggest-from-source-build.patch b/eng/source-build-patches/0001-Exclude-dotnet-suggest-from-source-build.patch
index c27a90c5db..d14367d108 100644
--- a/eng/source-build-patches/0001-Exclude-dotnet-suggest-from-source-build.patch
+++ b/eng/source-build-patches/0001-Exclude-dotnet-suggest-from-source-build.patch
@@ -6,15 +6,14 @@ Subject: [PATCH 1/3] Exclude dotnet-suggest from source-build.
This is not used by any downstream repos in source-build and adds a
bunch of prebuilts, so we can leave this out for now.
---
- System.CommandLine.sln | 134 ------------------
- .../dotnet-suggest.csproj | 1 +
- 2 files changed, 1 insertion(+), 134 deletions(-)
+ System.CommandLine.sln | 156 +------------------------------------------------
+ 1 file changed, 3 insertions(+), 153 deletions(-)
diff --git a/System.CommandLine.sln b/System.CommandLine.sln
-index ec97010..102aefe 100644
+index 7441d0ef..1519e16c 100644
--- a/System.CommandLine.sln
+++ b/System.CommandLine.sln
-@@ -30,31 +30,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E5B1EC71-0FC
+@@ -30,35 +30,15 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E5B1EC71-0FC
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.CommandLine", "src\System.CommandLine\System.CommandLine.csproj", "{0BE8E56E-7580-4526-BE24-D304E1779724}"
EndProject
@@ -43,10 +42,15 @@ index ec97010..102aefe 100644
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.CommandLine.Hosting", "src\System.CommandLine.Hosting\System.CommandLine.Hosting.csproj", "{644C4B4A-4A32-4307-9F71-C3BF901FFB66}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.CommandLine.Hosting.Tests", "src\System.CommandLine.Hosting.Tests\System.CommandLine.Hosting.Tests.csproj", "{39483140-BC26-4CAD-BBAE-3DC76C2F16CF}"
+-EndProject
+-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HostingPlayground", "samples\HostingPlayground\HostingPlayground.csproj", "{0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}"
+-EndProject
+-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.CommandLine.CommandGenerator", "src\System.CommandLine.CommandGenerator\System.CommandLine.CommandGenerator.csproj", "{B0D00128-E41B-4648-9D22-9B91F8F6BF0C}"
++Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HostingPlayground", "samples\HostingPlayground\HostingPlayground.csproj", "{0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}"
EndProject
- Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HostingPlayground", "samples\HostingPlayground\HostingPlayground.csproj", "{0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}"
- EndProject
-@@ -78,30 +61,6 @@ Global
+ Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+@@ -82,30 +62,6 @@ Global
{0BE8E56E-7580-4526-BE24-D304E1779724}.Release|x64.Build.0 = Release|Any CPU
{0BE8E56E-7580-4526-BE24-D304E1779724}.Release|x86.ActiveCfg = Release|Any CPU
{0BE8E56E-7580-4526-BE24-D304E1779724}.Release|x86.Build.0 = Release|Any CPU
@@ -77,7 +81,7 @@ index ec97010..102aefe 100644
{EEC30462-078F-45EB-AA70-12E3170CD51E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EEC30462-078F-45EB-AA70-12E3170CD51E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EEC30462-078F-45EB-AA70-12E3170CD51E}.Debug|x64.ActiveCfg = Debug|Any CPU
-@@ -114,54 +73,6 @@ Global
+@@ -118,54 +74,6 @@ Global
{EEC30462-078F-45EB-AA70-12E3170CD51E}.Release|x64.Build.0 = Release|Any CPU
{EEC30462-078F-45EB-AA70-12E3170CD51E}.Release|x86.ActiveCfg = Release|Any CPU
{EEC30462-078F-45EB-AA70-12E3170CD51E}.Release|x86.Build.0 = Release|Any CPU
@@ -132,7 +136,7 @@ index ec97010..102aefe 100644
{27E3BFFC-4412-4E4C-A656-B9D35B8A0F3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{27E3BFFC-4412-4E4C-A656-B9D35B8A0F3E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{27E3BFFC-4412-4E4C-A656-B9D35B8A0F3E}.Debug|x64.ActiveCfg = Debug|Any CPU
-@@ -174,30 +85,6 @@ Global
+@@ -178,30 +86,6 @@ Global
{27E3BFFC-4412-4E4C-A656-B9D35B8A0F3E}.Release|x64.Build.0 = Release|Any CPU
{27E3BFFC-4412-4E4C-A656-B9D35B8A0F3E}.Release|x86.ActiveCfg = Release|Any CPU
{27E3BFFC-4412-4E4C-A656-B9D35B8A0F3E}.Release|x86.Build.0 = Release|Any CPU
@@ -163,7 +167,7 @@ index ec97010..102aefe 100644
{644C4B4A-4A32-4307-9F71-C3BF901FFB66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{644C4B4A-4A32-4307-9F71-C3BF901FFB66}.Debug|Any CPU.Build.0 = Debug|Any CPU
{644C4B4A-4A32-4307-9F71-C3BF901FFB66}.Debug|x64.ActiveCfg = Debug|Any CPU
-@@ -210,48 +97,27 @@ Global
+@@ -214,18 +98,6 @@ Global
{644C4B4A-4A32-4307-9F71-C3BF901FFB66}.Release|x64.Build.0 = Release|Any CPU
{644C4B4A-4A32-4307-9F71-C3BF901FFB66}.Release|x86.ActiveCfg = Release|Any CPU
{644C4B4A-4A32-4307-9F71-C3BF901FFB66}.Release|x86.Build.0 = Release|Any CPU
@@ -182,15 +186,22 @@ index ec97010..102aefe 100644
{0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}.Debug|x64.ActiveCfg = Debug|Any CPU
- {0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}.Debug|x64.Build.0 = Debug|Any CPU
- {0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}.Debug|x86.ActiveCfg = Debug|Any CPU
- {0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}.Debug|x86.Build.0 = Debug|Any CPU
- {0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}.Release|Any CPU.Build.0 = Release|Any CPU
- {0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}.Release|x64.ActiveCfg = Release|Any CPU
+@@ -238,38 +110,16 @@ Global
{0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}.Release|x64.Build.0 = Release|Any CPU
{0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}.Release|x86.ActiveCfg = Release|Any CPU
{0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}.Release|x86.Build.0 = Release|Any CPU
+- {B0D00128-E41B-4648-9D22-9B91F8F6BF0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+- {B0D00128-E41B-4648-9D22-9B91F8F6BF0C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+- {B0D00128-E41B-4648-9D22-9B91F8F6BF0C}.Debug|x64.ActiveCfg = Debug|Any CPU
+- {B0D00128-E41B-4648-9D22-9B91F8F6BF0C}.Debug|x64.Build.0 = Debug|Any CPU
+- {B0D00128-E41B-4648-9D22-9B91F8F6BF0C}.Debug|x86.ActiveCfg = Debug|Any CPU
+- {B0D00128-E41B-4648-9D22-9B91F8F6BF0C}.Debug|x86.Build.0 = Debug|Any CPU
+- {B0D00128-E41B-4648-9D22-9B91F8F6BF0C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+- {B0D00128-E41B-4648-9D22-9B91F8F6BF0C}.Release|Any CPU.Build.0 = Release|Any CPU
+- {B0D00128-E41B-4648-9D22-9B91F8F6BF0C}.Release|x64.ActiveCfg = Release|Any CPU
+- {B0D00128-E41B-4648-9D22-9B91F8F6BF0C}.Release|x64.Build.0 = Release|Any CPU
+- {B0D00128-E41B-4648-9D22-9B91F8F6BF0C}.Release|x86.ActiveCfg = Release|Any CPU
+- {B0D00128-E41B-4648-9D22-9B91F8F6BF0C}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -210,20 +221,7 @@ index ec97010..102aefe 100644
{644C4B4A-4A32-4307-9F71-C3BF901FFB66} = {E5B1EC71-0FC4-4FAA-9C65-32D5016FBC45}
- {39483140-BC26-4CAD-BBAE-3DC76C2F16CF} = {E5B1EC71-0FC4-4FAA-9C65-32D5016FBC45}
{0BF6958D-9EE3-4623-B3D6-4DA77EAC1906} = {6749FB3E-39DE-4321-A39E-525278E9408D}
+- {B0D00128-E41B-4648-9D22-9B91F8F6BF0C} = {E5B1EC71-0FC4-4FAA-9C65-32D5016FBC45}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
-diff --git a/src/System.CommandLine.Suggest/dotnet-suggest.csproj b/src/System.CommandLine.Suggest/dotnet-suggest.csproj
-index f529484..ef9c3bb 100644
---- a/src/System.CommandLine.Suggest/dotnet-suggest.csproj
-+++ b/src/System.CommandLine.Suggest/dotnet-suggest.csproj
-@@ -13,6 +13,7 @@
- .$(VersionSuffixDateStamp).$(VersionSuffixBuildOfTheDay)
- 1.1$(DotnetSuggestBuildNumber)
-
-+ true
-
-
-
---
-2.18.0
-
+ SolutionGuid = {5C159F93-800B-49E7-9905-EE09F8B8434A}
diff --git a/eng/source-build-patches/0003-Update-to-netcoreapp3.1-to-avoid-prebuilts.patch b/eng/source-build-patches/0003-Update-to-netcoreapp3.1-to-avoid-prebuilts.patch
index ae461533d8..dfe7a07aca 100644
--- a/eng/source-build-patches/0003-Update-to-netcoreapp3.1-to-avoid-prebuilts.patch
+++ b/eng/source-build-patches/0003-Update-to-netcoreapp3.1-to-avoid-prebuilts.patch
@@ -8,15 +8,30 @@ rest of source-build: runtime and roslyn.
Includes a code fix for ref nullability with the new framework.
---
- .../System.CommandLine.DragonFruit.csproj | 2 +-
- .../System.CommandLine.Hosting.csproj | 2 +-
- .../System.CommandLine.Rendering.csproj | 2 +-
- src/System.CommandLine/Binding/BindingContext.cs | 2 +-
- src/System.CommandLine/System.CommandLine.csproj | 7 +------
- 5 files changed, 5 insertions(+), 10 deletions(-)
+ .../System.CommandLine.CommandGenerator.csproj | 4 ++--
+ .../System.CommandLine.DragonFruit.csproj | 2 +-
+ src/System.CommandLine.Hosting/System.CommandLine.Hosting.csproj | 2 +-
+ .../System.CommandLine.Rendering.csproj | 2 +-
+ src/System.CommandLine/Binding/BindingContext.cs | 2 +-
+ src/System.CommandLine/System.CommandLine.csproj | 7 +------
+ 6 files changed, 7 insertions(+), 12 deletions(-)
+diff --git a/src/System.CommandLine.CommandGenerator/System.CommandLine.CommandGenerator.csproj b/src/System.CommandLine.CommandGenerator/System.CommandLine.CommandGenerator.csproj
+index d419906e..8644194a 100644
+--- a/src/System.CommandLine.CommandGenerator/System.CommandLine.CommandGenerator.csproj
++++ b/src/System.CommandLine.CommandGenerator/System.CommandLine.CommandGenerator.csproj
+@@ -1,7 +1,7 @@
+-
++
+
+
+- netstandard2.0
++ net5.0
+ enable
+ 9.0
+ true
diff --git a/src/System.CommandLine.DragonFruit/System.CommandLine.DragonFruit.csproj b/src/System.CommandLine.DragonFruit/System.CommandLine.DragonFruit.csproj
-index b3a542fd..06167997 100644
+index b3a542fd..0e49d818 100644
--- a/src/System.CommandLine.DragonFruit/System.CommandLine.DragonFruit.csproj
+++ b/src/System.CommandLine.DragonFruit/System.CommandLine.DragonFruit.csproj
@@ -1,7 +1,7 @@
@@ -29,7 +44,7 @@ index b3a542fd..06167997 100644
diff --git a/src/System.CommandLine.Hosting/System.CommandLine.Hosting.csproj b/src/System.CommandLine.Hosting/System.CommandLine.Hosting.csproj
-index 1d00cff2..7342c1c5 100644
+index bed1b893..913edb72 100644
--- a/src/System.CommandLine.Hosting/System.CommandLine.Hosting.csproj
+++ b/src/System.CommandLine.Hosting/System.CommandLine.Hosting.csproj
@@ -2,7 +2,7 @@
@@ -42,7 +57,7 @@ index 1d00cff2..7342c1c5 100644
This package provides support for using System.CommandLine with Microsoft.Extensions.Hosting.
diff --git a/src/System.CommandLine.Rendering/System.CommandLine.Rendering.csproj b/src/System.CommandLine.Rendering/System.CommandLine.Rendering.csproj
-index d552286e..84026ebe 100644
+index bd5ce1d7..762481d9 100644
--- a/src/System.CommandLine.Rendering/System.CommandLine.Rendering.csproj
+++ b/src/System.CommandLine.Rendering/System.CommandLine.Rendering.csproj
@@ -2,7 +2,7 @@
@@ -55,10 +70,10 @@ index d552286e..84026ebe 100644
This package provides support for structured command line output rendering. Write code once that renders correctly in multiple output modes, including System.Console, virtual terminal (using ANSI escape sequences), and plain text.
diff --git a/src/System.CommandLine/Binding/BindingContext.cs b/src/System.CommandLine/Binding/BindingContext.cs
-index b942ba6a..bbb83891 100644
+index a80a4172..305a95ab 100644
--- a/src/System.CommandLine/Binding/BindingContext.cs
+++ b/src/System.CommandLine/Binding/BindingContext.cs
-@@ -55,7 +55,7 @@ namespace System.CommandLine.Binding
+@@ -56,7 +56,7 @@ namespace System.CommandLine.Binding
public ModelBinder GetModelBinder(IValueDescriptor valueDescriptor)
{
@@ -68,7 +83,7 @@ index b942ba6a..bbb83891 100644
return binder;
}
diff --git a/src/System.CommandLine/System.CommandLine.csproj b/src/System.CommandLine/System.CommandLine.csproj
-index aaa2c4a3..5e875a73 100644
+index 125e9c78..3a10c14d 100644
--- a/src/System.CommandLine/System.CommandLine.csproj
+++ b/src/System.CommandLine/System.CommandLine.csproj
@@ -3,7 +3,7 @@
@@ -79,8 +94,8 @@ index aaa2c4a3..5e875a73 100644
+ net5.0
9
enable
- This package includes a powerful command line parser and other tools for building command line applications, including:
-@@ -18,9 +18,4 @@
+
+@@ -26,11 +26,6 @@
@@ -90,6 +105,5 @@ index aaa2c4a3..5e875a73 100644
-
-
---
-2.25.2
-
+
+ True
diff --git a/global.json b/global.json
index 8e9411ee92..a4b61ddd28 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"tools": {
- "dotnet": "5.0.100",
+ "dotnet": "5.0.300",
"vs": {
"version": "16.8"
},
diff --git a/src/System.CommandLine.CommandGenerator/CommandHandlerGenerator.cs b/src/System.CommandLine.CommandGenerator/CommandHandlerGenerator.cs
new file mode 100644
index 0000000000..95ba1f6296
--- /dev/null
+++ b/src/System.CommandLine.CommandGenerator/CommandHandlerGenerator.cs
@@ -0,0 +1,122 @@
+using Microsoft.CodeAnalysis;
+using System.Linq;
+using System.Text;
+
+namespace System.CommandLine.CommandGenerator
+{
+ [Generator]
+ public class CommandHandlerGenerator : ISourceGenerator
+ {
+ private const string ICommandHandlerType = "System.CommandLine.Invocation.ICommandHandler";
+
+ public void Execute(GeneratorExecutionContext context)
+ {
+ SyntaxReceiver rx = (SyntaxReceiver)context.SyntaxContextReceiver!;
+
+ StringBuilder builder = new();
+ builder.AppendLine(@"
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+#nullable disable
+using System.CommandLine.Binding;
+using System.Reflection;
+using System.Threading.Tasks;
+
+namespace System.CommandLine.Invocation
+{
+ public static partial class CommandHandlerGeneratorExtensions_Generated
+ {
+");
+ int count = 1;
+ foreach (var invocation in rx.Invocations)
+ {
+ var methodParamters = invocation.Parameters
+ .Select(x => x.GetMethodParameter())
+ .Where(x => !string.IsNullOrWhiteSpace(x.Name))
+ .ToList();
+
+ builder.AppendLine(@$"public static {ICommandHandlerType} Generate<{string.Join(", ", Enumerable.Range(1, invocation.NumberOfGenerericParameters).Select(x => $"Unused{x}"))}>(this CommandHandlerGenerator handler,");
+ builder.AppendLine($"{invocation.DelegateType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)} method");
+ if (methodParamters.Count > 0)
+ {
+ builder.AppendLine(",");
+ builder.AppendLine(string.Join($", ", methodParamters.Select(x => $"{x.Type} {x.Name}")));
+ }
+ builder.AppendLine(")");
+ builder.AppendLine("{");
+ builder.Append($"return new GeneratedHandler_{count}(method");
+ if (methodParamters.Count > 0)
+ {
+ builder.Append(", ");
+ builder.Append(string.Join(", ", methodParamters.Select(x => x.Name)));
+ }
+ builder.AppendLine(");");
+
+ builder.AppendLine("}");
+
+
+ //TODO: fully qualify type names
+ builder.AppendLine($@"
+ private class GeneratedHandler_{count} : {ICommandHandlerType}
+ {{
+ public GeneratedHandler_{count}({invocation.DelegateType} method");
+
+ if (methodParamters.Count > 0)
+ {
+ builder.AppendLine(",");
+ builder.AppendLine(string.Join($", ", methodParamters.Select(x => $"{x.Type} {x.Name}")));
+ }
+
+ builder.AppendLine($@")
+ {{
+ Method = method;");
+ foreach (var propertyAssignment in invocation.Parameters
+ .Select(x => x.GetPropertyAssignment())
+ .Where(x => !string.IsNullOrWhiteSpace(x)))
+ {
+ builder.AppendLine(propertyAssignment);
+ }
+ builder.AppendLine($@"
+ }}
+
+ public {invocation.DelegateType} Method {{ get; }}");
+
+ foreach (var propertyDeclaration in invocation.Parameters
+ .Select(x => x.GetPropertyDeclaration())
+ .Where(x => !string.IsNullOrWhiteSpace(x)))
+ {
+ builder.AppendLine(propertyDeclaration);
+ }
+
+ builder.AppendLine($@"
+ public async Task InvokeAsync(InvocationContext context)
+ {{");
+ builder.AppendLine(invocation.InvokeContents());
+ builder.AppendLine($@"
+ }}
+ }}");
+ count++;
+ }
+
+ builder.AppendLine(@"
+ }
+}
+#nullable restore");
+
+ context.AddSource("CommandHandlerGeneratorExtensions_Generated.g.cs", builder.ToString());
+ }
+
+ public void Initialize(GeneratorInitializationContext context)
+ {
+#if DEBUG
+ if (!System.Diagnostics.Debugger.IsAttached)
+ {
+ //System.Diagnostics.Debugger.Launch();
+ }
+#endif
+
+ context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
+ }
+ }
+}
diff --git a/src/System.CommandLine.CommandGenerator/Invocations/ConstructorModelBindingInvocation.cs b/src/System.CommandLine.CommandGenerator/Invocations/ConstructorModelBindingInvocation.cs
new file mode 100644
index 0000000000..1e7de35efd
--- /dev/null
+++ b/src/System.CommandLine.CommandGenerator/Invocations/ConstructorModelBindingInvocation.cs
@@ -0,0 +1,79 @@
+using Microsoft.CodeAnalysis;
+using System.Linq;
+using System.Text;
+
+namespace System.CommandLine.CommandGenerator.Invocations
+{
+ internal class ConstructorModelBindingInvocation : DelegateInvocation, IEquatable
+ {
+ public ConstructorModelBindingInvocation(
+ IMethodSymbol constructor,
+ ReturnPattern returnPattern,
+ ITypeSymbol delegateType)
+ : base(delegateType, returnPattern, 1)
+ {
+ Constructor = constructor;
+ }
+
+ public IMethodSymbol Constructor { get; }
+
+ public override string InvokeContents()
+ {
+ StringBuilder builder = new();
+ builder.Append($"var model = new {Constructor.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}(");
+ builder.Append(string.Join(", ", Parameters.Take(Constructor.Parameters.Length)
+ .Select(x => x.GetValueFromContext())));
+ builder.AppendLine(");");
+
+ switch (ReturnPattern)
+ {
+ case ReturnPattern.FunctionReturnValue:
+ case ReturnPattern.AwaitFunction:
+ case ReturnPattern.AwaitFunctionReturnValue:
+ builder.Append("var rv = ");
+ break;
+ }
+ builder.Append("Method.Invoke(model");
+ var remainigParameters = Parameters.Skip(Constructor.Parameters.Length).ToList();
+ if (remainigParameters.Count > 0)
+ {
+ builder.Append(", ");
+ builder.Append(string.Join(", ", remainigParameters.Select(x => x.GetValueFromContext())));
+ }
+ builder.AppendLine(");");
+ switch (ReturnPattern)
+ {
+ case ReturnPattern.InvocationContextExitCode:
+ builder.AppendLine("return await Task.FromResult(context.ExitCode);");
+ break;
+ case ReturnPattern.FunctionReturnValue:
+ builder.AppendLine("return await Task.FromResult(rv);");
+ break;
+ case ReturnPattern.AwaitFunction:
+ builder.AppendLine("await rv;");
+ builder.AppendLine("return context.ExitCode;");
+ break;
+ case ReturnPattern.AwaitFunctionReturnValue:
+ builder.AppendLine("return await rv;");
+ break;
+ }
+ return builder.ToString();
+ }
+
+ public override int GetHashCode()
+ {
+ return base.GetHashCode() * -1521134295 +
+ SymbolComparer.GetHashCode(Constructor);
+ }
+
+ public override bool Equals(object? obj)
+ => Equals(obj as ConstructorModelBindingInvocation);
+
+ public bool Equals(ConstructorModelBindingInvocation? other)
+ {
+ if (other is null) return false;
+ return base.Equals(other) &&
+ SymbolComparer.Equals(Constructor, other.Constructor);
+ }
+ }
+}
diff --git a/src/System.CommandLine.CommandGenerator/Invocations/DelegateInvocation.cs b/src/System.CommandLine.CommandGenerator/Invocations/DelegateInvocation.cs
new file mode 100644
index 0000000000..9145160f39
--- /dev/null
+++ b/src/System.CommandLine.CommandGenerator/Invocations/DelegateInvocation.cs
@@ -0,0 +1,106 @@
+using Microsoft.CodeAnalysis;
+using System.Collections.Generic;
+using System.CommandLine.CommandGenerator.Parameters;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Text;
+
+namespace System.CommandLine.CommandGenerator.Invocations
+{
+ internal class DelegateInvocation : IEquatable
+ {
+ protected static SymbolEqualityComparer SymbolComparer { get; } = SymbolEqualityComparer.Default;
+
+ public ITypeSymbol DelegateType { get; }
+ public ReturnPattern ReturnPattern { get; }
+ public int NumberOfGenerericParameters { get; }
+
+ public DelegateInvocation(
+ ITypeSymbol delegateType,
+ ReturnPattern returnPattern,
+ int numberOfGenerericParameters)
+ {
+ DelegateType = delegateType;
+ ReturnPattern = returnPattern;
+ NumberOfGenerericParameters = numberOfGenerericParameters;
+ }
+
+ public List Parameters { get; } = new();
+
+ public virtual string InvokeContents()
+ {
+ StringBuilder builder = new();
+
+ switch (ReturnPattern)
+ {
+ case ReturnPattern.FunctionReturnValue:
+ case ReturnPattern.AwaitFunction:
+ case ReturnPattern.AwaitFunctionReturnValue:
+ builder.Append("var rv = ");
+ break;
+ }
+
+ builder.Append("Method.Invoke(");
+ builder.Append(string.Join(", ", Parameters.Select(x => x.GetValueFromContext())));
+ builder.AppendLine(");");
+
+ switch (ReturnPattern)
+ {
+ case ReturnPattern.InvocationContextExitCode:
+ builder.AppendLine("return await Task.FromResult(context.ExitCode);");
+ break;
+ case ReturnPattern.FunctionReturnValue:
+ builder.AppendLine("return await Task.FromResult(rv);");
+ break;
+ case ReturnPattern.AwaitFunction:
+ builder.AppendLine("await rv;");
+ builder.AppendLine("return context.ExitCode;");
+ break;
+ case ReturnPattern.AwaitFunctionReturnValue:
+ builder.AppendLine("return await rv;");
+ break;
+ }
+ return builder.ToString();
+ }
+
+ public override int GetHashCode()
+ {
+ int hashCode = SymbolComparer.GetHashCode(DelegateType) * -1521134295 +
+ HashCode(ReturnPattern) * -1521134295 +
+ HashCode(NumberOfGenerericParameters) * -1521134295;
+
+ foreach(Parameter parameter in Parameters)
+ {
+ hashCode += HashCode(parameter) * -1521134295;
+ }
+
+ return hashCode;
+ }
+
+ protected static int HashCode([DisallowNull] T value)
+ => EqualityComparer.Default.GetHashCode(value);
+
+ public override bool Equals(object? obj)
+ {
+ return Equals(obj as DelegateInvocation);
+ }
+
+ public bool Equals(DelegateInvocation? other)
+ {
+ if (other is null) return false;
+
+ bool areEqual = SymbolComparer.Equals(DelegateType, other.DelegateType) &&
+ Equals(ReturnPattern, other.ReturnPattern) &&
+ Equals(NumberOfGenerericParameters, other.NumberOfGenerericParameters) &&
+ Equals(Parameters.Count, other.Parameters.Count);
+ for(int i = 0; areEqual && i < Parameters.Count; i++)
+ {
+ areEqual &= Equals(Parameters[i], other.Parameters[i]);
+ }
+ return areEqual;
+ }
+
+ protected static bool Equals(T first, T second)
+ => EqualityComparer.Default.Equals(first, second);
+ }
+}
diff --git a/src/System.CommandLine.CommandGenerator/Invocations/FactoryModelBindingInvocation.cs b/src/System.CommandLine.CommandGenerator/Invocations/FactoryModelBindingInvocation.cs
new file mode 100644
index 0000000000..516c3f2603
--- /dev/null
+++ b/src/System.CommandLine.CommandGenerator/Invocations/FactoryModelBindingInvocation.cs
@@ -0,0 +1,70 @@
+using Microsoft.CodeAnalysis;
+using System.CommandLine.CommandGenerator.Parameters;
+using System.Linq;
+using System.Text;
+
+namespace System.CommandLine.CommandGenerator.Invocations
+{
+ internal class FactoryModelBindingInvocation : DelegateInvocation, IEquatable
+ {
+ public FactoryModelBindingInvocation(
+ ITypeSymbol delegateType,
+ ReturnPattern returnPattern)
+ : base(delegateType, returnPattern, 2)
+ { }
+
+ public override string InvokeContents()
+ {
+ StringBuilder builder = new();
+
+ var factoryParam = (FactoryParameter)Parameters[0];
+ builder.AppendLine($"var model = {factoryParam.LocalName}.Invoke(context);");
+
+ switch (ReturnPattern)
+ {
+ case ReturnPattern.FunctionReturnValue:
+ case ReturnPattern.AwaitFunction:
+ case ReturnPattern.AwaitFunctionReturnValue:
+ builder.Append("var rv = ");
+ break;
+ }
+ builder.Append("Method.Invoke(model");
+ var remainigParameters = Parameters.Skip(1).ToList();
+ if (remainigParameters.Count > 0)
+ {
+ builder.Append(", ");
+ builder.Append(string.Join(", ", remainigParameters.Select(x => x.GetValueFromContext())));
+ }
+ builder.AppendLine(");");
+ switch (ReturnPattern)
+ {
+ case ReturnPattern.InvocationContextExitCode:
+ builder.AppendLine("return await Task.FromResult(context.ExitCode);");
+ break;
+ case ReturnPattern.FunctionReturnValue:
+ builder.AppendLine("return await Task.FromResult(rv);");
+ break;
+ case ReturnPattern.AwaitFunction:
+ builder.AppendLine("await rv;");
+ builder.AppendLine("return context.ExitCode;");
+ break;
+ case ReturnPattern.AwaitFunctionReturnValue:
+ builder.AppendLine("return await rv;");
+ break;
+ }
+ return builder.ToString();
+ }
+
+ public override int GetHashCode()
+ => base.GetHashCode();
+
+ public override bool Equals(object? obj)
+ => Equals(obj as FactoryModelBindingInvocation);
+
+ public bool Equals(FactoryModelBindingInvocation? other)
+ {
+ if (other is null) return false;
+ return base.Equals(other);
+ }
+ }
+}
diff --git a/src/System.CommandLine.CommandGenerator/Invocations/ReturnPattern.cs b/src/System.CommandLine.CommandGenerator/Invocations/ReturnPattern.cs
new file mode 100644
index 0000000000..7a54f707d8
--- /dev/null
+++ b/src/System.CommandLine.CommandGenerator/Invocations/ReturnPattern.cs
@@ -0,0 +1,11 @@
+namespace System.CommandLine.CommandGenerator.Invocations
+{
+ internal enum ReturnPattern
+ {
+ None,
+ InvocationContextExitCode,
+ FunctionReturnValue,
+ AwaitFunction,
+ AwaitFunctionReturnValue
+ }
+}
diff --git a/src/System.CommandLine.CommandGenerator/Parameters/ArgumentParameter.cs b/src/System.CommandLine.CommandGenerator/Parameters/ArgumentParameter.cs
new file mode 100644
index 0000000000..a8b8fe3d8b
--- /dev/null
+++ b/src/System.CommandLine.CommandGenerator/Parameters/ArgumentParameter.cs
@@ -0,0 +1,27 @@
+using Microsoft.CodeAnalysis;
+
+namespace System.CommandLine.CommandGenerator.Parameters
+{
+ internal class ArgumentParameter : PropertyParameter, IEquatable
+ {
+ public ArgumentParameter(string localName, INamedTypeSymbol type, ITypeSymbol valueType)
+ : base(localName, type, valueType)
+ {
+ }
+
+ public override string GetValueFromContext()
+ => $"context.ParseResult.ValueForArgument({LocalName})";
+
+ public override int GetHashCode()
+ => base.GetHashCode();
+
+ public override bool Equals(object? obj)
+ => Equals(obj as ArgumentParameter);
+
+ public bool Equals(ArgumentParameter? other)
+ {
+ if (other is null) return false;
+ return base.Equals(other);
+ }
+ }
+}
diff --git a/src/System.CommandLine.CommandGenerator/Parameters/BindingContextParameter.cs b/src/System.CommandLine.CommandGenerator/Parameters/BindingContextParameter.cs
new file mode 100644
index 0000000000..f3b8311aad
--- /dev/null
+++ b/src/System.CommandLine.CommandGenerator/Parameters/BindingContextParameter.cs
@@ -0,0 +1,27 @@
+using Microsoft.CodeAnalysis;
+
+namespace System.CommandLine.CommandGenerator.Parameters
+{
+ internal class BindingContextParameter : Parameter, IEquatable
+ {
+ public BindingContextParameter(ITypeSymbol bindingContextType)
+ : base(bindingContextType)
+ {
+ }
+
+ public override string GetValueFromContext()
+ => "context.BindingContext";
+
+ public override int GetHashCode()
+ => base.GetHashCode();
+
+ public override bool Equals(object? obj)
+ => Equals(obj as BindingContextParameter);
+
+ public bool Equals(BindingContextParameter? other)
+ {
+ if (other is null) return false;
+ return base.Equals(other);
+ }
+ }
+}
diff --git a/src/System.CommandLine.CommandGenerator/Parameters/ConsoleParameter.cs b/src/System.CommandLine.CommandGenerator/Parameters/ConsoleParameter.cs
new file mode 100644
index 0000000000..f207a66cfc
--- /dev/null
+++ b/src/System.CommandLine.CommandGenerator/Parameters/ConsoleParameter.cs
@@ -0,0 +1,27 @@
+using Microsoft.CodeAnalysis;
+
+namespace System.CommandLine.CommandGenerator.Parameters
+{
+ internal class ConsoleParameter : Parameter, IEquatable
+ {
+ public ConsoleParameter(ITypeSymbol consoleType)
+ : base(consoleType)
+ {
+ }
+
+ public override string GetValueFromContext()
+ => "context.Console";
+
+ public override int GetHashCode()
+ => base.GetHashCode();
+
+ public override bool Equals(object? obj)
+ => Equals(obj as ConsoleParameter);
+
+ public bool Equals(ConsoleParameter? other)
+ {
+ if (other is null) return false;
+ return base.Equals(other);
+ }
+ }
+}
diff --git a/src/System.CommandLine.CommandGenerator/Parameters/FactoryParameter.cs b/src/System.CommandLine.CommandGenerator/Parameters/FactoryParameter.cs
new file mode 100644
index 0000000000..49eee23907
--- /dev/null
+++ b/src/System.CommandLine.CommandGenerator/Parameters/FactoryParameter.cs
@@ -0,0 +1,46 @@
+using Microsoft.CodeAnalysis;
+
+namespace System.CommandLine.CommandGenerator.Parameters
+{
+ internal class FactoryParameter : Parameter, IEquatable
+ {
+ public ITypeSymbol FactoryType { get; }
+ public string LocalName { get; }
+ private string ParameterName => LocalName.ToLowerInvariant();
+
+ public FactoryParameter(RawParameter rawParameter, ITypeSymbol factoryType)
+ : base(rawParameter.ValueType)
+ {
+ LocalName = rawParameter.LocalName;
+ FactoryType = factoryType;
+ }
+
+ public override string GetValueFromContext() => "";
+
+ public override string GetPropertyDeclaration()
+ => $"private {FactoryType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)} {LocalName} {{ get; }}";
+
+ public override string GetPropertyAssignment()
+ => $"{LocalName} = {ParameterName};";
+
+ public override (string Type, string Name) GetMethodParameter()
+ => (FactoryType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), ParameterName);
+
+ public override int GetHashCode()
+ {
+ return base.GetHashCode() * -1521134295 +
+ SymbolComparer.GetHashCode(FactoryType) * -1521134295 +
+ HashCode(LocalName);
+ }
+
+ public override bool Equals(object? obj)
+ => Equals(obj as FactoryParameter);
+
+ public bool Equals(FactoryParameter? other)
+ {
+ return base.Equals(other) &&
+ SymbolComparer.Equals(FactoryType, other.FactoryType) &&
+ Equals(LocalName, other.LocalName);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/System.CommandLine.CommandGenerator/Parameters/HelpBuilderParameter.cs b/src/System.CommandLine.CommandGenerator/Parameters/HelpBuilderParameter.cs
new file mode 100644
index 0000000000..d3d8841e9d
--- /dev/null
+++ b/src/System.CommandLine.CommandGenerator/Parameters/HelpBuilderParameter.cs
@@ -0,0 +1,27 @@
+using Microsoft.CodeAnalysis;
+
+namespace System.CommandLine.CommandGenerator.Parameters
+{
+ internal class HelpBuilderParameter : Parameter, IEquatable
+ {
+ public HelpBuilderParameter(ITypeSymbol helpBuilderType)
+ : base(helpBuilderType)
+ {
+ }
+
+ public override string GetValueFromContext()
+ => "context.HelpBuilder";
+
+ public override int GetHashCode()
+ => base.GetHashCode();
+
+ public override bool Equals(object? obj)
+ => Equals(obj as HelpBuilderParameter);
+
+ public bool Equals(HelpBuilderParameter? other)
+ {
+ if (other is null) return false;
+ return base.Equals(other);
+ }
+ }
+}
diff --git a/src/System.CommandLine.CommandGenerator/Parameters/InvocationContextParameter.cs b/src/System.CommandLine.CommandGenerator/Parameters/InvocationContextParameter.cs
new file mode 100644
index 0000000000..7eeb965191
--- /dev/null
+++ b/src/System.CommandLine.CommandGenerator/Parameters/InvocationContextParameter.cs
@@ -0,0 +1,27 @@
+using Microsoft.CodeAnalysis;
+
+namespace System.CommandLine.CommandGenerator.Parameters
+{
+ internal class InvocationContextParameter : Parameter, IEquatable
+ {
+ public InvocationContextParameter(ITypeSymbol invocationContextType)
+ : base(invocationContextType)
+ {
+ }
+
+ public override string GetValueFromContext()
+ => "context";
+
+ public override int GetHashCode()
+ => base.GetHashCode();
+
+ public override bool Equals(object? obj)
+ => Equals(obj as InvocationContextParameter);
+
+ public bool Equals(InvocationContextParameter? other)
+ {
+ if (other is null) return false;
+ return base.Equals(other);
+ }
+ }
+}
diff --git a/src/System.CommandLine.CommandGenerator/Parameters/OptionParameter.cs b/src/System.CommandLine.CommandGenerator/Parameters/OptionParameter.cs
new file mode 100644
index 0000000000..ebc29c67b2
--- /dev/null
+++ b/src/System.CommandLine.CommandGenerator/Parameters/OptionParameter.cs
@@ -0,0 +1,28 @@
+using Microsoft.CodeAnalysis;
+
+namespace System.CommandLine.CommandGenerator.Parameters
+{
+
+ internal class OptionParameter : PropertyParameter, IEquatable
+ {
+ public OptionParameter(string localName, INamedTypeSymbol type, ITypeSymbol valueType)
+ : base(localName, type, valueType)
+ {
+ }
+
+ public override string GetValueFromContext()
+ => $"context.ParseResult.ValueForOption({LocalName})";
+
+ public override int GetHashCode()
+ => base.GetHashCode();
+
+ public override bool Equals(object? obj)
+ => Equals(obj as OptionParameter);
+
+ public bool Equals(OptionParameter? other)
+ {
+ if (other is null) return false;
+ return base.Equals(other);
+ }
+ }
+}
diff --git a/src/System.CommandLine.CommandGenerator/Parameters/Parameter.cs b/src/System.CommandLine.CommandGenerator/Parameters/Parameter.cs
new file mode 100644
index 0000000000..4f716f8eb6
--- /dev/null
+++ b/src/System.CommandLine.CommandGenerator/Parameters/Parameter.cs
@@ -0,0 +1,46 @@
+using Microsoft.CodeAnalysis;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+
+namespace System.CommandLine.CommandGenerator.Parameters
+{
+ internal abstract class Parameter : IEquatable
+ {
+ protected static SymbolEqualityComparer SymbolComparer { get; } = SymbolEqualityComparer.Default;
+
+ public ITypeSymbol ValueType { get; }
+
+ protected Parameter(ITypeSymbol valueType)
+ {
+ ValueType = valueType;
+ }
+
+ public abstract string GetValueFromContext();
+
+ public virtual string GetPropertyDeclaration() => "";
+ public virtual string GetPropertyAssignment() => "";
+ public virtual (string Type, string Name) GetMethodParameter() => ("", "");
+
+ public override int GetHashCode()
+ {
+ return SymbolComparer.GetHashCode(ValueType);
+ }
+
+ protected static int HashCode([DisallowNull] T value)
+ => EqualityComparer.Default.GetHashCode(value);
+
+ public override bool Equals(object? obj)
+ {
+ return base.Equals(obj as Parameter);
+ }
+
+ public bool Equals(Parameter? other)
+ {
+ if (other is null) return false;
+ return SymbolComparer.Equals(ValueType, other.ValueType);
+ }
+
+ protected static bool Equals(T first, T second)
+ => EqualityComparer.Default.Equals(first, second);
+ }
+}
diff --git a/src/System.CommandLine.CommandGenerator/Parameters/ParseResultParameter.cs b/src/System.CommandLine.CommandGenerator/Parameters/ParseResultParameter.cs
new file mode 100644
index 0000000000..b8e793fc35
--- /dev/null
+++ b/src/System.CommandLine.CommandGenerator/Parameters/ParseResultParameter.cs
@@ -0,0 +1,27 @@
+using Microsoft.CodeAnalysis;
+
+namespace System.CommandLine.CommandGenerator.Parameters
+{
+ internal class ParseResultParameter : Parameter, IEquatable
+ {
+ public ParseResultParameter(ITypeSymbol parseResultType)
+ : base(parseResultType)
+ {
+ }
+
+ public override string GetValueFromContext()
+ => "context.ParseResult";
+
+ public override int GetHashCode()
+ => base.GetHashCode();
+
+ public override bool Equals(object? obj)
+ => Equals(obj as ParseResultParameter);
+
+ public bool Equals(ParseResultParameter? other)
+ {
+ if (other is null) return false;
+ return base.Equals(other);
+ }
+ }
+}
diff --git a/src/System.CommandLine.CommandGenerator/Parameters/PropertyParameter.cs b/src/System.CommandLine.CommandGenerator/Parameters/PropertyParameter.cs
new file mode 100644
index 0000000000..26fac46c28
--- /dev/null
+++ b/src/System.CommandLine.CommandGenerator/Parameters/PropertyParameter.cs
@@ -0,0 +1,46 @@
+using Microsoft.CodeAnalysis;
+
+namespace System.CommandLine.CommandGenerator.Parameters
+{
+ internal abstract class PropertyParameter : Parameter, IEquatable
+ {
+ protected PropertyParameter(string localName, ITypeSymbol type, ITypeSymbol valueType)
+ : base(valueType)
+ {
+ LocalName = localName;
+ Type = type;
+ }
+
+ public ITypeSymbol Type { get; }
+
+ public string LocalName { get; }
+
+ public string ParameterName => LocalName.ToLowerInvariant();
+
+ public override string GetPropertyDeclaration()
+ => $"private {Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)} {LocalName} {{ get; }}";
+
+ public override string GetPropertyAssignment()
+ => $"{LocalName} = {ParameterName};";
+
+ public override (string Type, string Name) GetMethodParameter()
+ => (Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), ParameterName);
+
+ public override int GetHashCode()
+ {
+ return base.GetHashCode() * -1521134295 +
+ SymbolComparer.GetHashCode(Type) * -1521134295 +
+ HashCode(LocalName);
+ }
+
+ public override bool Equals(object? obj)
+ => Equals(obj as PropertyParameter);
+
+ public bool Equals(PropertyParameter? other)
+ {
+ return base.Equals(other) &&
+ SymbolComparer.Equals(Type, other.Type) &&
+ Equals(LocalName, other.LocalName);
+ }
+ }
+}
diff --git a/src/System.CommandLine.CommandGenerator/Parameters/RawParameter.cs b/src/System.CommandLine.CommandGenerator/Parameters/RawParameter.cs
new file mode 100644
index 0000000000..09b4878cfe
--- /dev/null
+++ b/src/System.CommandLine.CommandGenerator/Parameters/RawParameter.cs
@@ -0,0 +1,27 @@
+using Microsoft.CodeAnalysis;
+
+namespace System.CommandLine.CommandGenerator.Parameters
+{
+ internal class RawParameter : PropertyParameter, IEquatable
+ {
+ public RawParameter(string localName, ITypeSymbol valueType)
+ : base(localName, valueType, valueType)
+ {
+ }
+
+ public override string GetValueFromContext()
+ => LocalName;
+
+ public override int GetHashCode()
+ => base.GetHashCode();
+
+ public override bool Equals(object? obj)
+ => Equals(obj as RawParameter);
+
+ public bool Equals(RawParameter? other)
+ {
+ if (other is null) return false;
+ return base.Equals(other);
+ }
+ }
+}
diff --git a/src/System.CommandLine.CommandGenerator/SyntaxReceiver.cs b/src/System.CommandLine.CommandGenerator/SyntaxReceiver.cs
new file mode 100644
index 0000000000..88e88cbe23
--- /dev/null
+++ b/src/System.CommandLine.CommandGenerator/SyntaxReceiver.cs
@@ -0,0 +1,237 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System.Collections.Generic;
+using System.CommandLine.CommandGenerator.Invocations;
+using System.CommandLine.CommandGenerator.Parameters;
+using System.Linq;
+
+namespace System.CommandLine.CommandGenerator
+{
+
+ internal class SyntaxReceiver : ISyntaxContextReceiver
+ {
+ public HashSet Invocations { get; } = new();
+
+ public void OnVisitSyntaxNode(GeneratorSyntaxContext context)
+ {
+ if (context.Node is InvocationExpressionSyntax invocationExpression &&
+ invocationExpression.Expression is MemberAccessExpressionSyntax memberAccess &&
+ memberAccess.Name.Identifier.Text == "Generate" &&
+ context.SemanticModel.GetSymbolInfo(invocationExpression) is { } invocationSymbol &&
+ invocationSymbol.Symbol is IMethodSymbol invokeMethodSymbol &&
+ invokeMethodSymbol.ReceiverType?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == "global::System.CommandLine.Invocation.CommandHandlerGenerator" &&
+ (invokeMethodSymbol.TypeArguments.Length == 1 || invokeMethodSymbol.TypeArguments.Length == 2))
+ {
+ SymbolEqualityComparer symbolEqualityComparer = SymbolEqualityComparer.Default;
+ WellKnownTypes wellKnonwTypes = new(context.SemanticModel.Compilation, symbolEqualityComparer);
+
+ IReadOnlyList delegateParameters = Array.Empty();
+ //Check for model binding condition
+ if (invokeMethodSymbol.TypeArguments[0] is INamedTypeSymbol namedDelegateType &&
+ namedDelegateType.TypeArguments.Length > 0)
+ {
+ if (namedDelegateType.DelegateInvokeMethod?.ReturnsVoid == false)
+ {
+ delegateParameters = namedDelegateType.TypeArguments
+ .Take(namedDelegateType.TypeArguments.Length - 1).Cast().ToList();
+ }
+ else
+ {
+ delegateParameters = namedDelegateType.TypeArguments.Cast().ToList();
+ }
+ }
+
+ IReadOnlyList symbols = invocationExpression.ArgumentList.Arguments
+ .Skip(1)
+ .Select(x => context.SemanticModel.GetSymbolInfo(x.Expression).Symbol)
+ .ToList();
+ if (symbols.Any(x => x is null)) return;
+ IReadOnlyList givenParameters = GetParameters(symbols!);
+ if (invokeMethodSymbol.TypeArguments.Length == 2)
+ {
+ var rawParameter = (RawParameter)givenParameters[0];
+ var factoryParameter = new FactoryParameter(rawParameter, invokeMethodSymbol.Parameters[1].Type);
+ givenParameters = new Parameter[] { factoryParameter }.Concat(givenParameters.Skip(1)).ToList();
+ }
+
+ ITypeSymbol delegateType = invokeMethodSymbol.TypeArguments[0];
+ ReturnPattern returnPattern = GetReturnPattern(delegateType, context.SemanticModel.Compilation);
+
+
+ if (IsMatch(delegateParameters, givenParameters, wellKnonwTypes))
+ {
+ if (invokeMethodSymbol.TypeArguments.Length == 2)
+ {
+ var invocation = new FactoryModelBindingInvocation(delegateType, returnPattern);
+ foreach (var parameter in PopulateParameters(delegateParameters, givenParameters, wellKnonwTypes))
+ {
+ invocation.Parameters.Add(parameter);
+ }
+ Invocations.Add(invocation);
+ }
+ else
+ {
+ var invocation = new DelegateInvocation(delegateType, returnPattern, 1);
+ foreach (var parameter in PopulateParameters(delegateParameters, givenParameters, wellKnonwTypes))
+ {
+ invocation.Parameters.Add(parameter);
+ }
+ Invocations.Add(invocation);
+ }
+ }
+ else if (delegateParameters[0] is INamedTypeSymbol modelType)
+ {
+ foreach (var ctor in modelType.Constructors.OrderByDescending(x => x.Parameters.Length))
+ {
+ var targetTypes =
+ ctor.Parameters.Select(x => x.Type)
+ .Concat(delegateParameters.Skip(1))
+ .ToList();
+ if (IsMatch(targetTypes, givenParameters, wellKnonwTypes))
+ {
+ var invocation = new ConstructorModelBindingInvocation(ctor, returnPattern, delegateType);
+ foreach (var parameter in PopulateParameters(targetTypes, givenParameters, wellKnonwTypes))
+ {
+ invocation.Parameters.Add(parameter);
+ }
+ Invocations.Add(invocation);
+ break;
+ }
+ }
+ }
+
+ static bool IsMatch(
+ IReadOnlyList targetSymbols,
+ IReadOnlyList providedSymbols,
+ WellKnownTypes knownTypes)
+ {
+ SymbolEqualityComparer symbolEqualityComparer = SymbolEqualityComparer.Default;
+ int j = 0;
+ for (int i = 0; i < targetSymbols.Count; i++)
+ {
+ if (j < providedSymbols.Count &&
+ symbolEqualityComparer.Equals(providedSymbols[j].ValueType, targetSymbols[i]))
+ {
+ j++;
+ //TODO: Handle the case where there are more provided symbols than needed
+ }
+ else if (!knownTypes.Contains(targetSymbols[i]))
+ {
+ return false;
+ }
+ }
+ return j == providedSymbols.Count;
+ }
+ }
+ }
+
+ private static IReadOnlyList PopulateParameters(
+ IReadOnlyList symbols,
+ IReadOnlyList givenParameters,
+ WellKnownTypes knownTypes)
+ {
+ List parameters = new(givenParameters);
+ for (int i = 0; i < symbols.Count; i++)
+ {
+ if (knownTypes.TryGet(symbols[i], out Parameter? parameter))
+ {
+ parameters.Insert(i, parameter!);
+ }
+ }
+ return parameters;
+ }
+
+ private static IReadOnlyList GetParameters(IEnumerable symbols)
+ {
+ List parameters = new();
+ int parameterIndex = 1;
+ foreach (Microsoft.CodeAnalysis.ISymbol symbol in symbols)
+ {
+ parameters.Add(GetParameter(symbol, $"Param{parameterIndex}"));
+ parameterIndex++;
+ }
+ return parameters;
+ }
+
+ private static Parameter GetParameter(Microsoft.CodeAnalysis.ISymbol argumentSymbol, string localName)
+ {
+ return argumentSymbol switch
+ {
+ ILocalSymbol local => FromTypeSymbol(local.Type),
+ INamedTypeSymbol namedType => FromNamedTypeSymbol(namedType),
+ IMethodSymbol methodSymbol => FromTypeSymbol(methodSymbol.ReturnType),
+ _ => throw new NotImplementedException($"Cannot convert from '{argumentSymbol?.Kind}' {argumentSymbol?.ToDisplayString()}")
+ };
+
+ Parameter FromNamedTypeSymbol(INamedTypeSymbol namedTypeSymbol)
+ {
+ if (namedTypeSymbol.TypeArguments.Length > 0)
+ {
+ if (namedTypeSymbol.Name == "Option")
+ {
+ return new OptionParameter(localName, namedTypeSymbol, namedTypeSymbol.TypeArguments[0]);
+ }
+ else if (namedTypeSymbol.Name == "Argument")
+ {
+ return new ArgumentParameter(localName, namedTypeSymbol, namedTypeSymbol.TypeArguments[0]);
+ }
+ }
+ return new RawParameter(localName, namedTypeSymbol);
+ }
+
+ Parameter FromTypeSymbol(ITypeSymbol typeSymbol)
+ {
+ return typeSymbol switch
+ {
+ INamedTypeSymbol namedType => FromNamedTypeSymbol(namedType),
+ _ => throw new NotImplementedException($"Cannot convert from type symbol '{typeSymbol?.Kind}' {typeSymbol?.ToDisplayString()}")
+ };
+ }
+ }
+
+ private static ReturnPattern GetReturnPattern(ITypeSymbol delegateType, Compilation compilation)
+ {
+ ITypeSymbol? returnType = null;
+ if (delegateType is INamedTypeSymbol namedSymbol &&
+ namedSymbol.DelegateInvokeMethod is { } delegateInvokeMethod &&
+ !delegateInvokeMethod.ReturnsVoid)
+ {
+ returnType = delegateInvokeMethod.ReturnType;
+ }
+
+ if (returnType is null)
+ {
+ return ReturnPattern.InvocationContextExitCode;
+ }
+
+ SymbolEqualityComparer symbolEqualityComparer = SymbolEqualityComparer.Default;
+
+ INamedTypeSymbol intType = compilation.GetSpecialType(SpecialType.System_Int32);
+ if (symbolEqualityComparer.Equals(returnType, intType))
+ {
+ return ReturnPattern.FunctionReturnValue;
+ }
+
+ //TODO: what about toher awaiatables?
+ INamedTypeSymbol taskType = compilation.GetTypeByMetadataName("System.Threading.Tasks.Task")
+ ?? throw new InvalidOperationException("Failed to find Task");
+ if (symbolEqualityComparer.Equals(returnType, taskType))
+ {
+ return ReturnPattern.AwaitFunction;
+ }
+
+ INamedTypeSymbol taskOfTType = compilation.GetTypeByMetadataName("System.Threading.Tasks.Task`1")
+ ?? throw new InvalidOperationException("Failed to find Task");
+
+ if (returnType is INamedTypeSymbol namedReturnType &&
+ namedReturnType.TypeArguments.Length == 1 &&
+ symbolEqualityComparer.Equals(namedReturnType.TypeArguments[0], intType) &&
+ symbolEqualityComparer.Equals(namedReturnType.ConstructUnboundGenericType(), taskOfTType.ConstructUnboundGenericType()))
+ {
+ return ReturnPattern.AwaitFunctionReturnValue;
+ }
+ //TODO: Should this be an error?
+ return ReturnPattern.InvocationContextExitCode;
+ }
+ }
+}
diff --git a/src/System.CommandLine.CommandGenerator/System.CommandLine.CommandGenerator.csproj b/src/System.CommandLine.CommandGenerator/System.CommandLine.CommandGenerator.csproj
new file mode 100644
index 0000000000..d419906e9a
--- /dev/null
+++ b/src/System.CommandLine.CommandGenerator/System.CommandLine.CommandGenerator.csproj
@@ -0,0 +1,30 @@
+
+
+
+ netstandard2.0
+ enable
+ 9.0
+ true
+ false
+ $(NoWarn);NU5128
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/System.CommandLine.CommandGenerator/System.CommandLine.Invocation.Generated/CommandHandler.cs b/src/System.CommandLine.CommandGenerator/System.CommandLine.Invocation.Generated/CommandHandler.cs
new file mode 100644
index 0000000000..e743bce5ec
--- /dev/null
+++ b/src/System.CommandLine.CommandGenerator/System.CommandLine.Invocation.Generated/CommandHandler.cs
@@ -0,0 +1,7 @@
+namespace System.CommandLine.Invocation.Generated
+{
+ public static partial class CommandHandler
+ {
+ public static CommandHandlerGenerator Generator { get; } = null!;
+ }
+}
diff --git a/src/System.CommandLine.CommandGenerator/System.CommandLine.Invocation.Generated/CommandHandlerGenerator.cs b/src/System.CommandLine.CommandGenerator/System.CommandLine.Invocation.Generated/CommandHandlerGenerator.cs
new file mode 100644
index 0000000000..0f654ee09b
--- /dev/null
+++ b/src/System.CommandLine.CommandGenerator/System.CommandLine.Invocation.Generated/CommandHandlerGenerator.cs
@@ -0,0 +1,10 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace System.CommandLine.Invocation
+{
+ public sealed class CommandHandlerGenerator
+ {
+ private CommandHandlerGenerator() { }
+ }
+}
\ No newline at end of file
diff --git a/src/System.CommandLine.CommandGenerator/System.CommandLine.Invocation.Generated/CommandHandlerGeneratorExtensions.cs b/src/System.CommandLine.CommandGenerator/System.CommandLine.Invocation.Generated/CommandHandlerGeneratorExtensions.cs
new file mode 100644
index 0000000000..8d0c310d73
--- /dev/null
+++ b/src/System.CommandLine.CommandGenerator/System.CommandLine.Invocation.Generated/CommandHandlerGeneratorExtensions.cs
@@ -0,0 +1,24 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace System.CommandLine.Invocation
+{
+ public static class CommandHandlerGeneratorExtensions
+ {
+ public static ICommandHandler Generate(this CommandHandlerGenerator handler,
+ TDelegate @delegate, params ISymbol[] symbols)
+ where TDelegate : Delegate
+ {
+ //TODO: Better exception/message
+ throw new InvalidOperationException("Should never get here....");
+ }
+
+ public static ICommandHandler Generate(this CommandHandlerGenerator handler,
+ TDelegate @delegate, Func modelBuilder)
+ where TDelegate : Delegate
+ {
+ //TODO: Better exception/message
+ throw new InvalidOperationException("Should never get here....");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/System.CommandLine.CommandGenerator/WellKnownTypes.cs b/src/System.CommandLine.CommandGenerator/WellKnownTypes.cs
new file mode 100644
index 0000000000..2289f31113
--- /dev/null
+++ b/src/System.CommandLine.CommandGenerator/WellKnownTypes.cs
@@ -0,0 +1,63 @@
+using Microsoft.CodeAnalysis;
+using System.Collections.Generic;
+using System.CommandLine.CommandGenerator.Parameters;
+
+namespace System.CommandLine.CommandGenerator
+{
+ internal class WellKnownTypes
+ {
+ public INamedTypeSymbol Console { get; }
+ public INamedTypeSymbol ParseResult { get; }
+ public INamedTypeSymbol InvocationContext { get; }
+ public INamedTypeSymbol HelpBuilder { get; }
+ public INamedTypeSymbol BindingContext { get; }
+ public IEqualityComparer Comparer { get; }
+
+ public WellKnownTypes(Compilation compilation, IEqualityComparer comparer)
+ {
+ Console = GetType("System.CommandLine.IConsole");
+ ParseResult = GetType("System.CommandLine.Parsing.ParseResult");
+ InvocationContext = GetType("System.CommandLine.Invocation.InvocationContext");
+ HelpBuilder = GetType("System.CommandLine.Help.IHelpBuilder");
+ BindingContext = GetType("System.CommandLine.Binding.BindingContext");
+
+ INamedTypeSymbol GetType(string typeName)
+ => compilation.GetTypeByMetadataName(typeName)
+ ?? throw new InvalidOperationException($"Could not find well known type '{typeName}'");
+ Comparer = comparer;
+ }
+
+ internal bool Contains(Microsoft.CodeAnalysis.ISymbol symbol) => TryGet(symbol, out _);
+
+ internal bool TryGet(Microsoft.CodeAnalysis.ISymbol symbol, out Parameter? parameter)
+ {
+ if (Comparer.Equals(Console, symbol))
+ {
+ parameter = new ConsoleParameter(Console);
+ return true;
+ }
+ if (Comparer.Equals(InvocationContext, symbol))
+ {
+ parameter = new InvocationContextParameter(InvocationContext);
+ return true;
+ }
+ if (Comparer.Equals(ParseResult, symbol))
+ {
+ parameter = new ParseResultParameter(ParseResult);
+ return true;
+ }
+ if (Comparer.Equals(HelpBuilder, symbol))
+ {
+ parameter = new HelpBuilderParameter(HelpBuilder);
+ return true;
+ }
+ if (Comparer.Equals(BindingContext, symbol))
+ {
+ parameter = new BindingContextParameter(BindingContext);
+ return true;
+ }
+ parameter = null;
+ return false;
+ }
+ }
+}
diff --git a/src/System.CommandLine.Tests/Invocation/CommandHandlerTests.cs b/src/System.CommandLine.Tests/Invocation/CommandHandlerTests.cs
index 546d84eeb0..09b1a47d8e 100644
--- a/src/System.CommandLine.Tests/Invocation/CommandHandlerTests.cs
+++ b/src/System.CommandLine.Tests/Invocation/CommandHandlerTests.cs
@@ -12,7 +12,7 @@
namespace System.CommandLine.Tests.Invocation
{
- public class CommandHandlerTests
+ public partial class CommandHandlerTests
{
private readonly TestConsole _console = new();
@@ -322,7 +322,6 @@ public async Task Method_parameters_of_type_InvocationContext_receive_the_curren
boundContext.ParseResult.GetValueForOption(option).Should().Be(123);
}
-
private class ExecuteTestClass
{
public string boundName = default;
@@ -398,7 +397,6 @@ void Execute(string name, int age)
boundAge.Should().Be(425);
}
-
[Fact]
public async Task Method_parameters_on_the_invoked_method_can_be_bound_to_hyphenated_argument_names()
{
diff --git a/src/System.CommandLine.Tests/Invocation/GeneratedCommandHandlerTests.cs b/src/System.CommandLine.Tests/Invocation/GeneratedCommandHandlerTests.cs
new file mode 100644
index 0000000000..957f3f6d1a
--- /dev/null
+++ b/src/System.CommandLine.Tests/Invocation/GeneratedCommandHandlerTests.cs
@@ -0,0 +1,274 @@
+using FluentAssertions;
+using System.CommandLine.Binding;
+using System.CommandLine.Help;
+using System.CommandLine.Invocation;
+using System.CommandLine.Parsing;
+using System.Threading.Tasks;
+using Xunit;
+using CommandHandler = System.CommandLine.Invocation.Generated.CommandHandler;
+
+#nullable enable
+namespace System.CommandLine.Tests.Invocation
+{
+ public partial class CommandHandlerTests
+ {
+ [Fact]
+ public async Task Can_generate_handler_for_void_returning_method()
+ {
+ string? boundName = default;
+ int boundAge = default;
+ IConsole? boundConsole = null;
+
+ void Execute(string fullnameOrNickname, IConsole console, int age)
+ {
+ boundName = fullnameOrNickname;
+ boundConsole = console;
+ boundAge = age;
+ }
+
+ var command = new Command("command");
+ var nameArgument = new Argument();
+ command.AddArgument(nameArgument);
+ var ageOption = new Option("--age");
+ command.AddOption(ageOption);
+
+ command.Handler = CommandHandler.Generator.Generate>
+ (Execute, nameArgument, ageOption);
+
+ await command.InvokeAsync("command Gandalf --age 425", _console);
+
+ boundName.Should().Be("Gandalf");
+ boundAge.Should().Be(425);
+ boundConsole.Should().NotBeNull();
+ }
+
+ [Fact]
+ public async Task Can_generate_handler_for_method_with_model()
+ {
+ string? boundName = default;
+ int boundAge = default;
+ IConsole? boundConsole = null;
+
+ void Execute(Character character, IConsole console)
+ {
+ boundName = character.FullName;
+ boundConsole = console;
+ boundAge = character.Age;
+ }
+
+ var command = new Command("command");
+ var nameOption = new Option("--name");
+ command.AddOption(nameOption);
+ var ageOption = new Option("--age");
+ command.AddOption(ageOption);
+
+ command.Handler = CommandHandler.Generator.Generate>
+ (Execute, nameOption, ageOption);
+
+ await command.InvokeAsync("command --age 425 --name Gandalf", _console);
+
+ boundName.Should().Be("Gandalf");
+ boundAge.Should().Be(425);
+ boundConsole.Should().NotBeNull();
+ }
+
+ [Fact]
+ public async Task Can_generate_handler_for_method_with_model_property_binding()
+ {
+ string? boundName = default;
+ int boundAge = default;
+ IConsole? boundConsole = null;
+
+ void Execute(Character character, IConsole console)
+ {
+ boundName = character.FullName;
+ boundConsole = console;
+ boundAge = character.Age;
+ }
+
+ var command = new Command("command");
+ var nameOption = new Option("--name");
+ command.AddOption(nameOption);
+ var ageOption = new Option("--age");
+ command.AddOption(ageOption);
+
+ command.Handler = CommandHandler.Generator.Generate, Character>
+ (Execute, context => new Character()
+ {
+ FullName = context.ParseResult.ValueForOption(nameOption),
+ Age = context.ParseResult.ValueForOption(ageOption),
+ });
+
+ await command.InvokeAsync("command --age 425 --name Gandalf", _console);
+
+ boundName.Should().Be("Gandalf");
+ boundAge.Should().Be(425);
+ boundConsole.Should().NotBeNull();
+ }
+
+ [Fact]
+ public async Task Can_generate_handler_for_int_returning_method()
+ {
+ int Execute(int first, int second)
+ {
+ return first + second;
+ }
+
+ var command = new Command("add");
+ var firstArgument = new Argument("first");
+ command.AddArgument(firstArgument);
+ var secondArgument = new Argument("second");
+ command.AddArgument(secondArgument);
+
+ command.Handler = CommandHandler.Generator.Generate>
+ (Execute, firstArgument, secondArgument);
+
+ int result = await command.InvokeAsync("add 1 2", _console);
+
+ result.Should().Be(3);
+ }
+
+ [Fact]
+ public async Task Can_generate_handler_with_well_know_parameters_types()
+ {
+ InvocationContext? boundInvocationContext = null;
+ IConsole? boundConsole = null;
+ ParseResult? boundParseResult = null;
+ IHelpBuilder? boundHelpBuilder = null;
+ BindingContext? boundBindingContext = null;
+
+ void Execute(
+ InvocationContext invocationContext,
+ IConsole console,
+ ParseResult parseResult,
+ IHelpBuilder helpBuilder,
+ BindingContext bindingContext)
+ {
+ boundInvocationContext = invocationContext;
+ boundConsole = console;
+ boundParseResult = parseResult;
+ boundHelpBuilder = helpBuilder;
+ boundBindingContext = bindingContext;
+ }
+
+ var command = new Command("command");
+
+ command.Handler = CommandHandler.Generator
+ .Generate>(Execute);
+
+ await command.InvokeAsync("command", _console);
+
+ boundInvocationContext.Should().NotBeNull();
+ boundConsole.Should().Be(_console);
+ boundParseResult.Should().NotBeNull();
+ boundHelpBuilder.Should().NotBeNull();
+ boundBindingContext.Should().NotBeNull();
+ }
+
+ [Fact]
+ public async Task Can_generate_handler_for_async_method()
+ {
+ string? boundName = default;
+ int boundAge = default;
+ IConsole? boundConsole = null;
+
+ async Task ExecuteAsync(string fullnameOrNickname, IConsole console, int age)
+ {
+ //Just long enough to make sure the taks is be awaited
+ await Task.Delay(100);
+ boundName = fullnameOrNickname;
+ boundConsole = console;
+ boundAge = age;
+ }
+
+ var command = new Command("command");
+ var nameArgument = new Argument();
+ command.AddArgument(nameArgument);
+ var ageOption = new Option("--age");
+ command.AddOption(ageOption);
+
+ command.Handler = CommandHandler.Generator.Generate>
+ (ExecuteAsync, nameArgument, ageOption);
+
+ await command.InvokeAsync("command Gandalf --age 425", _console);
+
+ boundName.Should().Be("Gandalf");
+ boundAge.Should().Be(425);
+ boundConsole.Should().NotBeNull();
+ }
+
+ [Fact]
+ public async Task Can_generate_handler_for_async_task_of_int_returning_method()
+ {
+ async Task Execute(int first, int second)
+ {
+ await Task.Delay(100);
+ return first + second;
+ }
+
+ var command = new Command("add");
+ var firstArgument = new Argument("first");
+ command.AddArgument(firstArgument);
+ var secondArgument = new Argument("second");
+ command.AddArgument(secondArgument);
+
+ command.Handler = CommandHandler.Generator.Generate>>
+ (Execute, firstArgument, secondArgument);
+
+ int result = await command.InvokeAsync("add 1 2", _console);
+
+ result.Should().Be(3);
+ }
+
+ [Fact]
+ public async Task Can_generate_handler_for_multiple_commands_with_the_same_signature()
+ {
+ string firstValue = "";
+ void Execute1(string value)
+ {
+ firstValue = value;
+ }
+ string secondValue = "";
+ void Execute2(string value)
+ {
+ secondValue = value;
+ }
+
+
+ var command1 = new Command("first");
+ var argument1 = new Argument("first-value");
+ command1.AddArgument(argument1);
+ command1.Handler = CommandHandler.Generator.Generate>
+ (Execute1, argument1);
+
+ var command2 = new Command("second");
+ var argument2 = new Argument("second-value");
+ command2.AddArgument(argument2);
+ command2.Handler = CommandHandler.Generator.Generate>
+ (Execute2, argument2);
+
+ await command1.InvokeAsync("first v1", _console);
+ await command2.InvokeAsync("second v2", _console);
+
+ firstValue.Should().Be("v1");
+ secondValue.Should().Be("v2");
+ }
+
+ public class Character
+ {
+ public Character(string? fullName, int age)
+ {
+ FullName = fullName;
+ Age = age;
+ }
+
+ public Character()
+ { }
+
+ public string? FullName { get; set; }
+ public int Age { get; set; }
+ }
+
+ }
+}
+#nullable restore
diff --git a/src/System.CommandLine.Tests/System.CommandLine.Tests.csproj b/src/System.CommandLine.Tests/System.CommandLine.Tests.csproj
index 09c2a2c444..ce812f484a 100644
--- a/src/System.CommandLine.Tests/System.CommandLine.Tests.csproj
+++ b/src/System.CommandLine.Tests/System.CommandLine.Tests.csproj
@@ -4,6 +4,7 @@
$(TargetFrameworks);net462
9
false
+ true
DEBUG;TRACE
@@ -16,6 +17,7 @@
+
diff --git a/src/System.CommandLine/Binding/MethodInfoHandlerDescriptor.cs b/src/System.CommandLine/Binding/MethodInfoHandlerDescriptor.cs
index 4b9511415b..72783b737f 100644
--- a/src/System.CommandLine/Binding/MethodInfoHandlerDescriptor.cs
+++ b/src/System.CommandLine/Binding/MethodInfoHandlerDescriptor.cs
@@ -24,10 +24,19 @@ public MethodInfoHandlerDescriptor(
public override ICommandHandler GetCommandHandler()
{
- return new ModelBindingCommandHandler(
- _handlerMethodInfo,
- this,
- _invocationTarget);
+ if (_invocationTarget is null)
+ {
+ return new ModelBindingCommandHandler(
+ _handlerMethodInfo,
+ this);
+ }
+ else
+ {
+ return new ModelBindingCommandHandler(
+ _handlerMethodInfo,
+ this,
+ _invocationTarget);
+ }
}
public override ModelDescriptor Parent => ModelDescriptor.FromType(_handlerMethodInfo.DeclaringType);
diff --git a/src/System.CommandLine/Invocation/InvocationContext.cs b/src/System.CommandLine/Invocation/InvocationContext.cs
index 59dc94683b..c078b29885 100644
--- a/src/System.CommandLine/Invocation/InvocationContext.cs
+++ b/src/System.CommandLine/Invocation/InvocationContext.cs
@@ -26,10 +26,10 @@ public InvocationContext(
public IConsole Console => BindingContext.Console;
- public Parser Parser => BindingContext.ParseResult.Parser;
-
public IHelpBuilder HelpBuilder => Parser.Configuration.HelpBuilderFactory(BindingContext);
+ public Parser Parser => BindingContext.ParseResult.Parser;
+
public Resources Resources => Parser.Configuration.Resources;
public ParseResult ParseResult
diff --git a/src/System.CommandLine/Parsing/ParseResult.cs b/src/System.CommandLine/Parsing/ParseResult.cs
index ab4e93a2b0..a637e002d3 100644
--- a/src/System.CommandLine/Parsing/ParseResult.cs
+++ b/src/System.CommandLine/Parsing/ParseResult.cs
@@ -190,10 +190,12 @@ public T GetValueForOption(Option option)
[return: MaybeNull]
public T GetValueForOption(IOption option)
{
- if (FindResultFor(option) is { } result &&
- result.GetValueOrDefault() is { } t)
+ if (FindResultFor(option) is { } result)
{
- return t;
+ if (result.GetValueOrDefault() is { } t)
+ {
+ return t;
+ }
}
return (T)Binder.GetDefaultValue(option.Argument.ValueType)!;
diff --git a/src/System.CommandLine/System.CommandLine.csproj b/src/System.CommandLine/System.CommandLine.csproj
index 85f28afc49..125e9c7828 100644
--- a/src/System.CommandLine/System.CommandLine.csproj
+++ b/src/System.CommandLine/System.CommandLine.csproj
@@ -1,4 +1,4 @@
-
+
true
@@ -6,6 +6,7 @@
netstandard2.0
9
enable
+
This package includes a powerful command line parser and other tools for building command line applications, including:
* Shell-agnostic support for command line completions