diff --git a/eng/source-build-patches/0002-Use-package-version-properties-for-reference-version.patch b/eng/source-build-patches/0002-Use-package-version-properties-for-reference-version.patch
index 4744fa2791..212b26b6af 100644
--- a/eng/source-build-patches/0002-Use-package-version-properties-for-reference-version.patch
+++ b/eng/source-build-patches/0002-Use-package-version-properties-for-reference-version.patch
@@ -27,21 +27,3 @@ index 3048701..16a0eb7 100644
+ 4.5.4
+
-diff --git a/src/System.CommandLine/System.CommandLine.csproj b/src/System.CommandLine/System.CommandLine.csproj
-index 1ae8dd3..aaa2c4a 100644
---- a/src/System.CommandLine/System.CommandLine.csproj
-+++ b/src/System.CommandLine/System.CommandLine.csproj
-@@ -19,8 +19,8 @@
-
-
-
--
--
-+
-+
-
-
-
---
-2.18.0
-
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 733c404a70..9de8b2b8e5 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
@@ -16,20 +16,6 @@ Includes a code fix for ref nullability with the new framework.
src/System.CommandLine/System.CommandLine.csproj | 7 +------
6 files changed, 7 insertions(+), 12 deletions(-)
-diff --git a/src/System.CommandLine.Generator/System.CommandLine.Generator.csproj b/src/System.CommandLine.Generator/System.CommandLine.Generator.csproj
-index d419906e..8644194a 100644
---- a/src/System.CommandLine.Generator/System.CommandLine.Generator.csproj
-+++ b/src/System.CommandLine.Generator/System.CommandLine.Generator.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..0e49d818 100644
--- a/src/System.CommandLine.DragonFruit/System.CommandLine.DragonFruit.csproj
@@ -100,8 +86,8 @@ index 125e9c78..3a10c14d 100644
-
--
--
+-
+-
-
-
diff --git a/repack.ps1 b/repack.ps1
new file mode 100644
index 0000000000..9297374748
--- /dev/null
+++ b/repack.ps1
@@ -0,0 +1,11 @@
+
+# clean up the previously-cached NuGet packages
+Remove-Item -Recurse ~\.nuget\packages\System.CommandLine* -Force
+
+# build and pack dotnet-interactive
+dotnet clean
+dotnet pack /p:PackageVersion=2.0.0-dev
+
+# copy the dotnet-interactive packages to the temp directory
+Get-ChildItem -Recurse -Filter *.nupkg | Copy-Item -Destination c:\temp -Force
+
diff --git a/src/System.CommandLine.Generator.Tests/GeneratedCommandHandlerTests.cs b/src/System.CommandLine.Generator.Tests/GeneratedCommandHandlerTests.cs
index 6db98b2830..3b4b886cd4 100644
--- a/src/System.CommandLine.Generator.Tests/GeneratedCommandHandlerTests.cs
+++ b/src/System.CommandLine.Generator.Tests/GeneratedCommandHandlerTests.cs
@@ -9,9 +9,7 @@
using System.Threading.Tasks;
using FluentAssertions;
using Xunit;
-using static System.CommandLine.Invocation.CommandHandlerGenerator;
-#nullable enable
namespace System.CommandLine.Generator.Tests
{
public class CommandHandlerTests
@@ -32,13 +30,16 @@ void Execute(string fullnameOrNickname, IConsole console, int age)
boundAge = age;
}
- var command = new Command("command");
var nameArgument = new Argument();
- command.AddArgument(nameArgument);
var ageOption = new Option("--age");
- command.AddOption(ageOption);
- command.Handler = GeneratedHandler.Create>
+ var command = new Command("command")
+ {
+ nameArgument,
+ ageOption
+ };
+
+ command.SetHandler>
(Execute, nameArgument, ageOption);
await command.InvokeAsync("command Gandalf --age 425", _console);
@@ -46,32 +47,33 @@ void Execute(string fullnameOrNickname, IConsole console, int age)
boundName.Should().Be("Gandalf");
boundAge.Should().Be(425);
boundConsole.Should().NotBeNull();
- }
-
+ }
+
[Fact]
- public async Task Can_generate_handler_for_method_with_model()
+ public async Task Can_generate_handler_for_void_returning_delegate()
{
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 nameArgument = new Argument();
var ageOption = new Option("--age");
- command.AddOption(ageOption);
- command.Handler = GeneratedHandler.Create>
- (Execute, nameOption, ageOption);
+ var command = new Command("command")
+ {
+ nameArgument,
+ ageOption
+ };
- await command.InvokeAsync("command --age 425 --name Gandalf", _console);
+ command.SetHandler>
+ ((fullnameOrNickname, console, age) =>
+ {
+ boundName = fullnameOrNickname;
+ boundConsole = console;
+ boundAge = age;
+ }, nameArgument, ageOption);
+
+ await command.InvokeAsync("command Gandalf --age 425", _console);
boundName.Should().Be("Gandalf");
boundAge.Should().Be(425);
@@ -79,7 +81,7 @@ void Execute(Character character, IConsole console)
}
[Fact]
- public async Task Can_generate_handler_for_method_with_model_property_binding()
+ public async Task Can_generate_handler_for_method_with_model()
{
string? boundName = default;
int boundAge = default;
@@ -98,12 +100,7 @@ void Execute(Character character, IConsole console)
var ageOption = new Option("--age");
command.AddOption(ageOption);
- command.Handler = GeneratedHandler.Create, Character>
- (Execute, context => new Character
- {
- FullName = context.ParseResult.ValueForOption(nameOption),
- Age = context.ParseResult.ValueForOption(ageOption),
- });
+ command.SetHandler>(Execute, nameOption, ageOption);
await command.InvokeAsync("command --age 425 --name Gandalf", _console);
@@ -126,8 +123,7 @@ int Execute(int first, int second)
var secondArgument = new Argument("second");
command.AddArgument(secondArgument);
- command.Handler = GeneratedHandler.Create>
- (Execute, firstArgument, secondArgument);
+ command.SetHandler>(Execute, firstArgument, secondArgument);
int result = await command.InvokeAsync("add 1 2", _console);
@@ -145,7 +141,7 @@ public async Task Can_generate_handler_with_well_know_parameters_types()
void Execute(
InvocationContext invocationContext,
- IConsole console,
+ IConsole console,
ParseResult parseResult,
IHelpBuilder helpBuilder,
BindingContext bindingContext)
@@ -159,7 +155,7 @@ void Execute(
var command = new Command("command");
- command.Handler = GeneratedHandler.Create>(Execute);
+ command.SetHandler>(Execute);
await command.InvokeAsync("command", _console);
@@ -179,20 +175,22 @@ public async Task Can_generate_handler_for_async_method()
async Task ExecuteAsync(string fullnameOrNickname, IConsole console, int age)
{
- //Just long enough to make sure the taks is be awaited
- await Task.Delay(100);
+ await Task.Yield();
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 = GeneratedHandler.Create>
+ var command = new Command("command")
+ {
+ nameArgument,
+ ageOption
+ };
+
+ command.SetHandler>
(ExecuteAsync, nameArgument, ageOption);
await command.InvokeAsync("command Gandalf --age 425", _console);
@@ -207,50 +205,52 @@ public async Task Can_generate_handler_for_async_task_of_int_returning_method()
{
async Task Execute(int first, int second)
{
- await Task.Delay(100);
+ await Task.Yield();
return first + second;
}
- var command = new Command("add");
var firstArgument = new Argument("first");
- command.AddArgument(firstArgument);
var secondArgument = new Argument("second");
- command.AddArgument(secondArgument);
+ var command = new Command("add")
+ {
+ firstArgument,
+ secondArgument
+ };
- command.Handler = GeneratedHandler.Create>>
+ command.SetHandler>>
(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 = GeneratedHandler.Create>
- (Execute1, argument1);
+ command1.SetHandler>(Execute1, argument1);
var command2 = new Command("second");
var argument2 = new Argument("second-value");
command2.AddArgument(argument2);
- command2.Handler = GeneratedHandler.Create>
- (Execute2, argument2);
+ command2.SetHandler>(Execute2, argument2);
await command1.InvokeAsync("first v1", _console);
await command2.InvokeAsync("second v2", _console);
@@ -268,12 +268,11 @@ public Character(string? fullName, int age)
}
public Character()
- { }
+ {
+ }
public string? FullName { get; set; }
public int Age { get; set; }
}
-
}
}
-#nullable restore
diff --git a/src/System.CommandLine.Generator.Tests/System.CommandLine.Generator.Tests.csproj b/src/System.CommandLine.Generator.Tests/System.CommandLine.Generator.Tests.csproj
index a28060ab35..25e7d2f169 100644
--- a/src/System.CommandLine.Generator.Tests/System.CommandLine.Generator.Tests.csproj
+++ b/src/System.CommandLine.Generator.Tests/System.CommandLine.Generator.Tests.csproj
@@ -5,6 +5,7 @@
9
true
true
+ enable
diff --git a/src/System.CommandLine.Generator/CommandHandlerGenerator.cs b/src/System.CommandLine.Generator/CommandHandlerGenerator.cs
deleted file mode 100644
index 24c59f36b2..0000000000
--- a/src/System.CommandLine.Generator/CommandHandlerGenerator.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-// 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()
- {
- }
-
- public static CommandHandlerGenerator GeneratedHandler { get; } = null!;
- }
-}
\ No newline at end of file
diff --git a/src/System.CommandLine.Generator/CommandHandlerGeneratorExtensions.cs b/src/System.CommandLine.Generator/CommandHandlerGeneratorExtensions.cs
deleted file mode 100644
index a6fd9dd257..0000000000
--- a/src/System.CommandLine.Generator/CommandHandlerGeneratorExtensions.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-// 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 Create(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 Create(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.Generator/CommandHandlerSourceGenerator.cs b/src/System.CommandLine.Generator/CommandHandlerSourceGenerator.cs
index 064c16e896..57fc272943 100644
--- a/src/System.CommandLine.Generator/CommandHandlerSourceGenerator.cs
+++ b/src/System.CommandLine.Generator/CommandHandlerSourceGenerator.cs
@@ -1,7 +1,6 @@
// 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.
-using System.CommandLine.Invocation;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
@@ -17,19 +16,29 @@ public void Execute(GeneratorExecutionContext context)
{
SyntaxReceiver rx = (SyntaxReceiver)context.SyntaxContextReceiver!;
+ if (rx.Invocations.Count == 0)
+ {
+ return;
+ }
+
StringBuilder builder = new();
builder.Append(
$@"// 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.
+using System.ComponentModel;
using System.CommandLine.Binding;
using System.Reflection;
using System.Threading.Tasks;
using System.CommandLine.Invocation;
-namespace {typeof(CommandHandlerGeneratorExtensions).Namespace}
+#pragma warning disable
+
+namespace System.CommandLine
{{
- public static partial class CommandHandlerGeneratorExtensions_Generated
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ internal static class GeneratedCommandHandlers
{{
");
int handlerCount = 1;
@@ -43,8 +52,8 @@ public static partial class CommandHandlerGeneratorExtensions_Generated
builder.Append(
@$"
- public static {ICommandHandlerType} {nameof(CommandHandlerGeneratorExtensions.Create)}<{string.Join(", ", Enumerable.Range(1, invocation.NumberOfGenerericParameters).Select(x => $@"T{x}"))}>(
- this {nameof(CommandHandlerGenerator)} handler,");
+ public static void SetHandler<{string.Join(", ", Enumerable.Range(1, invocation.NumberOfGenerericParameters).Select(x => $@"T{x}"))}>(
+ this Command command,");
builder.Append($@"
{invocation.DelegateType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)} method");
@@ -62,7 +71,7 @@ public static partial class CommandHandlerGeneratorExtensions_Generated
builder.Append(@"
{");
builder.Append($@"
- return new GeneratedHandler_{handlerCount}(method");
+ command.Handler = new GeneratedHandler_{handlerCount}(method");
if (methodParameters.Length > 0)
{
@@ -138,6 +147,8 @@ public async Task InvokeAsync(InvocationContext context)
public void Initialize(GeneratorInitializationContext context)
{
+ // Debugger.Launch();
+
context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
}
}
diff --git a/src/System.CommandLine.Generator/Invocations/FactoryModelBindingInvocation.cs b/src/System.CommandLine.Generator/Invocations/FactoryModelBindingInvocation.cs
deleted file mode 100644
index db5b7380b9..0000000000
--- a/src/System.CommandLine.Generator/Invocations/FactoryModelBindingInvocation.cs
+++ /dev/null
@@ -1,78 +0,0 @@
-using Microsoft.CodeAnalysis;
-using System.CommandLine.Generator.Parameters;
-using System.Linq;
-using System.Text;
-
-namespace System.CommandLine.Generator.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.Append(@"
- return await Task.FromResult(context.ExitCode);");
- break;
- case ReturnPattern.FunctionReturnValue:
- builder.Append(@"
- return await Task.FromResult(rv);");
- break;
- case ReturnPattern.AwaitFunction:
- builder.Append(@"
- await rv;");
- builder.Append(@"
- return context.ExitCode;");
- break;
- case ReturnPattern.AwaitFunctionReturnValue:
- builder.Append(@"
- 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.Generator/Parameters/FactoryParameter.cs b/src/System.CommandLine.Generator/Parameters/FactoryParameter.cs
deleted file mode 100644
index 9e11bb8a73..0000000000
--- a/src/System.CommandLine.Generator/Parameters/FactoryParameter.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-using Microsoft.CodeAnalysis;
-
-namespace System.CommandLine.Generator.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.Generator/SyntaxReceiver.cs b/src/System.CommandLine.Generator/SyntaxReceiver.cs
index 32aa11605c..3f35373e41 100644
--- a/src/System.CommandLine.Generator/SyntaxReceiver.cs
+++ b/src/System.CommandLine.Generator/SyntaxReceiver.cs
@@ -1,144 +1,146 @@
// 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.
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Generic;
using System.CommandLine.Generator.Invocations;
using System.CommandLine.Generator.Parameters;
-using System.CommandLine.Invocation;
using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace System.CommandLine.Generator
{
internal class SyntaxReceiver : ISyntaxContextReceiver
{
+ private static readonly string _nameOfExtensionMethodAnchorType = "global::System.CommandLine.Command";
+
public HashSet Invocations { get; } = new();
public void OnVisitSyntaxNode(GeneratorSyntaxContext context)
{
- if (context.Node is InvocationExpressionSyntax { Expression: MemberAccessExpressionSyntax memberAccess } invocationExpression &&
- memberAccess.Name.Identifier.Text == nameof(CommandHandlerGeneratorExtensions.Create) &&
- context.SemanticModel.GetSymbolInfo(invocationExpression) is { Symbol: IMethodSymbol invokeMethodSymbol } &&
- invokeMethodSymbol.ReceiverType?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) ==
- $"global::{typeof(CommandHandlerGenerator).Namespace}.{nameof(CommandHandlerGenerator)}" &&
- invokeMethodSymbol.TypeArguments.Length is 1 or 2)
+ if (context.Node is not InvocationExpressionSyntax { Expression: MemberAccessExpressionSyntax memberAccess } invocationExpression)
{
- SymbolEqualityComparer symbolEqualityComparer = SymbolEqualityComparer.Default;
- WellKnownTypes wellKnownTypes = new(context.SemanticModel.Compilation, symbolEqualityComparer);
+ return;
+ }
- var delegateParameters = Array.Empty();
+ if (memberAccess.Name.Identifier.Text != "SetHandler")
+ {
+ return;
+ }
- //Check for model binding condition
- if (invokeMethodSymbol.TypeArguments[0] is INamedTypeSymbol { TypeArguments: { Length: > 0 } } namedDelegateType)
- {
- if (namedDelegateType.DelegateInvokeMethod?.ReturnsVoid == false)
- {
- delegateParameters = namedDelegateType.TypeArguments
- .Take(namedDelegateType.TypeArguments.Length - 1)
- .Cast()
- .ToArray();
- }
- else
- {
- delegateParameters = namedDelegateType.TypeArguments
- .Cast()
- .ToArray();
- }
- }
+ if (context.SemanticModel.GetSymbolInfo(invocationExpression) is not { Symbol: IMethodSymbol invokeMethodSymbol })
+ {
+ return;
+ }
- var symbols = invocationExpression.ArgumentList.Arguments
- .Skip(1)
- .Select(x => context.SemanticModel.GetSymbolInfo(x.Expression).Symbol)
- .ToArray();
+ if (invokeMethodSymbol.ReceiverType?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) != _nameOfExtensionMethodAnchorType)
+ {
+ return;
+ }
+
+ if (invokeMethodSymbol.TypeArguments.Length is not 1)
+ {
+ return;
+ }
+
+ SymbolEqualityComparer symbolEqualityComparer = SymbolEqualityComparer.Default;
+ WellKnownTypes wellKnownTypes = new(context.SemanticModel.Compilation, symbolEqualityComparer);
+
+ var delegateParameters = Array.Empty();
- if (symbols.Any(x => x is null))
+ //Check for model binding condition
+ if (invokeMethodSymbol.TypeArguments[0] is INamedTypeSymbol { TypeArguments: { Length: > 0 } } namedDelegateType)
+ {
+ if (namedDelegateType.DelegateInvokeMethod?.ReturnsVoid == false)
+ {
+ delegateParameters = namedDelegateType.TypeArguments
+ .Take(namedDelegateType.TypeArguments.Length - 1)
+ .Cast()
+ .ToArray();
+ }
+ else
{
- return;
+ delegateParameters = namedDelegateType.TypeArguments
+ .Cast()
+ .ToArray();
}
+ }
+
+ var symbols = invocationExpression.ArgumentList
+ .Arguments
+ .Skip(1)
+ .Select(x => context.SemanticModel.GetSymbolInfo(x.Expression).Symbol)
+ .ToArray();
+
+ if (symbols.Any(x => x is null))
+ {
+ return;
+ }
- IReadOnlyList givenParameters = GetParameters(symbols!);
+ IReadOnlyList givenParameters = GetParameters(symbols!);
+ ITypeSymbol delegateType = invokeMethodSymbol.TypeArguments[0];
+ ReturnPattern returnPattern = GetReturnPattern(delegateType, context.SemanticModel.Compilation);
- if (invokeMethodSymbol.TypeArguments.Length == 2)
+ if (IsMatch(delegateParameters, givenParameters, wellKnownTypes))
+ {
+
+ var invocation = new DelegateInvocation(delegateType, returnPattern, 1);
+ foreach (var parameter in PopulateParameters(delegateParameters, givenParameters, wellKnownTypes))
{
- var rawParameter = (RawParameter)givenParameters[0];
- var factoryParameter = new FactoryParameter(rawParameter, invokeMethodSymbol.Parameters[1].Type);
- givenParameters = new Parameter[] { factoryParameter }.Concat(givenParameters.Skip(1)).ToList();
+ invocation.Parameters.Add(parameter);
}
- ITypeSymbol delegateType = invokeMethodSymbol.TypeArguments[0];
- ReturnPattern returnPattern = GetReturnPattern(delegateType, context.SemanticModel.Compilation);
-
-
- if (IsMatch(delegateParameters, givenParameters, wellKnownTypes))
+ Invocations.Add(invocation);
+ }
+ else if (delegateParameters[0] is INamedTypeSymbol modelType)
+ {
+ foreach (var ctor in modelType.Constructors.OrderByDescending(x => x.Parameters.Length))
{
- if (invokeMethodSymbol.TypeArguments.Length == 2)
- {
- var invocation = new FactoryModelBindingInvocation(delegateType, returnPattern);
- foreach (var parameter in PopulateParameters(delegateParameters, givenParameters, wellKnownTypes))
- {
- invocation.Parameters.Add(parameter);
- }
- Invocations.Add(invocation);
- }
- else
+ var targetTypes =
+ ctor.Parameters.Select(x => x.Type)
+ .Concat(delegateParameters.Skip(1))
+ .ToArray();
+ if (IsMatch(targetTypes, givenParameters, wellKnownTypes))
{
- var invocation = new DelegateInvocation(delegateType, returnPattern, 1);
- foreach (var parameter in PopulateParameters(delegateParameters, givenParameters, wellKnownTypes))
+ var invocation = new ConstructorModelBindingInvocation(ctor, returnPattern, delegateType);
+ foreach (var parameter in PopulateParameters(targetTypes, givenParameters, wellKnownTypes))
{
invocation.Parameters.Add(parameter);
}
+
Invocations.Add(invocation);
+ break;
}
}
- else if (delegateParameters[0] is INamedTypeSymbol modelType)
+ }
+
+ static bool IsMatch(
+ IReadOnlyList targetSymbols,
+ IReadOnlyList providedSymbols,
+ WellKnownTypes knownTypes)
+ {
+ SymbolEqualityComparer symbolEqualityComparer = SymbolEqualityComparer.Default;
+ int j = 0;
+ for (int i = 0; i < targetSymbols.Count; i++)
{
- foreach (var ctor in modelType.Constructors.OrderByDescending(x => x.Parameters.Length))
+ if (j < providedSymbols.Count &&
+ symbolEqualityComparer.Equals(providedSymbols[j].ValueType, targetSymbols[i]))
{
- var targetTypes =
- ctor.Parameters.Select(x => x.Type)
- .Concat(delegateParameters.Skip(1))
- .ToList();
- if (IsMatch(targetTypes, givenParameters, wellKnownTypes))
- {
- var invocation = new ConstructorModelBindingInvocation(ctor, returnPattern, delegateType);
- foreach (var parameter in PopulateParameters(targetTypes, givenParameters, wellKnownTypes))
- {
- invocation.Parameters.Add(parameter);
- }
- Invocations.Add(invocation);
- break;
- }
+ j++;
+ //TODO: Handle the case where there are more provided symbols than needed
}
- }
-
- static bool IsMatch(
- IReadOnlyList targetSymbols,
- IReadOnlyList providedSymbols,
- WellKnownTypes knownTypes)
- {
- SymbolEqualityComparer symbolEqualityComparer = SymbolEqualityComparer.Default;
- int j = 0;
- for (int i = 0; i < targetSymbols.Count; i++)
+ else if (!knownTypes.Contains(targetSymbols[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 false;
}
- return j == providedSymbols.Count;
}
+
+ return j == providedSymbols.Count;
}
}
private static IReadOnlyList PopulateParameters(
- IReadOnlyList symbols,
+ IReadOnlyList symbols,
IReadOnlyList givenParameters,
WellKnownTypes knownTypes)
{
@@ -150,22 +152,24 @@ private static IReadOnlyList PopulateParameters(
parameters.Insert(i, parameter!);
}
}
+
return parameters;
}
- private static IReadOnlyList GetParameters(IEnumerable symbols)
+ private static IReadOnlyList GetParameters(IEnumerable symbols)
{
List parameters = new();
int parameterIndex = 1;
- foreach (Microsoft.CodeAnalysis.ISymbol symbol in symbols)
+ foreach (ISymbol symbol in symbols)
{
parameters.Add(GetParameter(symbol, $"Param{parameterIndex}"));
parameterIndex++;
}
+
return parameters;
}
- private static Parameter GetParameter(Microsoft.CodeAnalysis.ISymbol argumentSymbol, string localName)
+ private static Parameter GetParameter(ISymbol argumentSymbol, string localName)
{
return argumentSymbol switch
{
@@ -179,15 +183,17 @@ Parameter FromNamedTypeSymbol(INamedTypeSymbol namedTypeSymbol)
{
if (namedTypeSymbol.TypeArguments.Length > 0)
{
- if (namedTypeSymbol.Name == nameof(Option))
+ if (namedTypeSymbol.Name == "Option")
{
return new OptionParameter(localName, namedTypeSymbol, namedTypeSymbol.TypeArguments[0]);
}
- else if (namedTypeSymbol.Name == nameof(Argument))
+
+ if (namedTypeSymbol.Name == "Argument")
{
return new ArgumentParameter(localName, namedTypeSymbol, namedTypeSymbol.TypeArguments[0]);
}
}
+
return new RawParameter(localName, namedTypeSymbol);
}
@@ -204,9 +210,7 @@ Parameter FromTypeSymbol(ITypeSymbol typeSymbol)
private static ReturnPattern GetReturnPattern(ITypeSymbol delegateType, Compilation compilation)
{
ITypeSymbol? returnType = null;
- if (delegateType is INamedTypeSymbol namedSymbol &&
- namedSymbol.DelegateInvokeMethod is { } delegateInvokeMethod &&
- !delegateInvokeMethod.ReturnsVoid)
+ if (delegateType is INamedTypeSymbol { DelegateInvokeMethod: { ReturnsVoid: false } delegateInvokeMethod })
{
returnType = delegateInvokeMethod.ReturnType;
}
@@ -217,23 +221,23 @@ namedSymbol.DelegateInvokeMethod is { } delegateInvokeMethod &&
}
SymbolEqualityComparer symbolEqualityComparer = SymbolEqualityComparer.Default;
-
+
INamedTypeSymbol intType = compilation.GetSpecialType(SpecialType.System_Int32);
if (symbolEqualityComparer.Equals(returnType, intType))
{
return ReturnPattern.FunctionReturnValue;
}
- //TODO: what about toher awaiatables?
+ //TODO: what about other awaitables?
INamedTypeSymbol taskType = compilation.GetTypeByMetadataName("System.Threading.Tasks.Task")
- ?? throw new InvalidOperationException("Failed to find 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");
+ ?? throw new InvalidOperationException("Failed to find Task");
if (returnType is INamedTypeSymbol { TypeArguments: { Length: 1 } } namedReturnType && symbolEqualityComparer.Equals(namedReturnType.TypeArguments[0], intType) &&
symbolEqualityComparer.Equals(namedReturnType.ConstructUnboundGenericType(), taskOfTType.ConstructUnboundGenericType()))
@@ -245,4 +249,4 @@ namedSymbol.DelegateInvokeMethod is { } delegateInvokeMethod &&
return ReturnPattern.InvocationContextExitCode;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/System.CommandLine.Generator/System.CommandLine.Generator.csproj b/src/System.CommandLine.Generator/System.CommandLine.Generator.csproj
index b25c3ed5b1..6ffa8a90fa 100644
--- a/src/System.CommandLine.Generator/System.CommandLine.Generator.csproj
+++ b/src/System.CommandLine.Generator/System.CommandLine.Generator.csproj
@@ -1,4 +1,4 @@
-
+
netstandard2.0
@@ -7,7 +7,6 @@
true
false
$(NoWarn);NU5128
- false
@@ -24,8 +23,4 @@
-
-
-
-
diff --git a/src/System.CommandLine.Generator/WellKnownTypes.cs b/src/System.CommandLine.Generator/WellKnownTypes.cs
index 38b07997d6..bfddbee3ab 100644
--- a/src/System.CommandLine.Generator/WellKnownTypes.cs
+++ b/src/System.CommandLine.Generator/WellKnownTypes.cs
@@ -1,9 +1,9 @@
// 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.
-using Microsoft.CodeAnalysis;
using System.Collections.Generic;
using System.CommandLine.Generator.Parameters;
+using Microsoft.CodeAnalysis;
namespace System.CommandLine.Generator
{
@@ -14,9 +14,9 @@ internal class WellKnownTypes
public INamedTypeSymbol InvocationContext { get; }
public INamedTypeSymbol HelpBuilder { get; }
public INamedTypeSymbol BindingContext { get; }
- public IEqualityComparer Comparer { get; }
+ public IEqualityComparer Comparer { get; }
- public WellKnownTypes(Compilation compilation, IEqualityComparer comparer)
+ public WellKnownTypes(Compilation compilation, IEqualityComparer comparer)
{
Console = GetType("System.CommandLine.IConsole");
ParseResult = GetType("System.CommandLine.Parsing.ParseResult");
@@ -26,41 +26,47 @@ public WellKnownTypes(Compilation compilation, IEqualityComparer compilation.GetTypeByMetadataName(typeName)
- ?? throw new InvalidOperationException($"Could not find well known type '{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 Contains(ISymbol symbol) => TryGet(symbol, out _);
- internal bool TryGet(Microsoft.CodeAnalysis.ISymbol symbol, out Parameter? parameter)
+ internal bool TryGet(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;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/System.CommandLine/CommandExtensions.cs b/src/System.CommandLine/CommandExtensions.cs
index 74f43fc8f6..fb10d15a17 100644
--- a/src/System.CommandLine/CommandExtensions.cs
+++ b/src/System.CommandLine/CommandExtensions.cs
@@ -65,5 +65,16 @@ public static ParseResult Parse(
string commandLine,
IReadOnlyCollection? delimiters = null) =>
new Parser(command).Parse(commandLine);
+
+ private const string _messageForWhenGeneratorIsNotInUse =
+ "This overload should not be called. You should reference the System.CommandLine.Generator package which will generate a more specific overload for your delegate.";
+
+ public static void SetHandler(
+ this Command command,
+ TDelegate @delegate,
+ params ISymbol[] symbols)
+ {
+ throw new InvalidOperationException(_messageForWhenGeneratorIsNotInUse);
+ }
}
}
diff --git a/src/System.CommandLine/ConsoleExtensions.cs b/src/System.CommandLine/ConsoleExtensions.cs
new file mode 100644
index 0000000000..63c115cd57
--- /dev/null
+++ b/src/System.CommandLine/ConsoleExtensions.cs
@@ -0,0 +1,19 @@
+// 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.
+
+using System.CommandLine.IO;
+
+namespace System.CommandLine
+{
+ ///
+ /// Provides extension methods for .
+ ///
+ public static class ConsoleExtensions
+ {
+ public static void Write(this IConsole console, string value) =>
+ console.Out.Write(value);
+
+ public static void WriteLine(this IConsole console, string value) =>
+ console.Out.WriteLine(value);
+ }
+}
\ No newline at end of file