diff --git a/Directory.Build.props b/Directory.Build.props
index 492000f..e40b48a 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -36,6 +36,6 @@
git
-
+
diff --git a/src/DotNetCampus.CommandLine.Analyzer/Generators/ModelProviding/InterceptorModelProvider.cs b/src/DotNetCampus.CommandLine.Analyzer/Generators/ModelProviding/InterceptorModelProvider.cs
index 05a1be4..022b9d6 100644
--- a/src/DotNetCampus.CommandLine.Analyzer/Generators/ModelProviding/InterceptorModelProvider.cs
+++ b/src/DotNetCampus.CommandLine.Analyzer/Generators/ModelProviding/InterceptorModelProvider.cs
@@ -44,18 +44,31 @@ public static IncrementalValuesProvider SelectMethod
{
return context.SyntaxProvider.CreateSyntaxProvider((node, ct) =>
{
- // 检查 commandLine.As() 方法调用。
+ // 检查 commandLine.Xxx() 或 commandLine.Xxx((T o) => { }) 方法调用。
if (node is InvocationExpressionSyntax
{
Expression: MemberAccessExpressionSyntax
{
- Name: GenericNameSyntax
- {
- TypeArgumentList.Arguments.Count: 1,
- } syntax,
+ Name: var nameSyntax,
},
- } invocationExpressionNode && syntax.Identifier.Text == methodName)
+ } invocationExpressionNode)
{
+ // 支持显式泛型参数(GenericNameSyntax)和隐式推断(SimpleNameSyntax/IdentifierNameSyntax)。
+ var isMatch = nameSyntax switch
+ {
+ GenericNameSyntax
+ {
+ TypeArgumentList.Arguments.Count: 1,
+ } genericName => genericName.Identifier.Text == methodName,
+ not null => nameSyntax.Identifier.Text == methodName,
+ _ => false,
+ };
+
+ if (!isMatch)
+ {
+ return false;
+ }
+
// 再检查方法的参数列表是否是指定类型。
var expectedParameterCount = parameterTypeFullNameRegexes.Length;
var argumentList = invocationExpressionNode.ArgumentList.Arguments;
@@ -97,14 +110,25 @@ public static IncrementalValuesProvider SelectMethod
}
}
- // 获取 commandLine.As() 中的 T。
- var genericTypeNode = ((GenericNameSyntax)((MemberAccessExpressionSyntax)node.Expression).Name).TypeArgumentList.Arguments[0];
- var symbol = ModelExtensions.GetSymbolInfo(c.SemanticModel, genericTypeNode, ct).Symbol as INamedTypeSymbol;
+ // 获取 commandLine.Xxx() 中的 T。
+ // 支持从语法节点获取(显式泛型参数)或从方法符号获取(隐式推断)。
+ var nameSyntax = ((MemberAccessExpressionSyntax)node.Expression).Name;
+ var symbol = nameSyntax switch
+ {
+ // commandLine.Xxx() 显式获取类型符号。
+ GenericNameSyntax genericNameSyntax => ModelExtensions.GetSymbolInfo(c.SemanticModel, genericNameSyntax.TypeArgumentList.Arguments[0], ct)
+ .Symbol as INamedTypeSymbol,
+ // commandLine.Xxx((T o) => { }) 隐式获取类型符号。
+ not null when methodSymbol.TypeArguments.Length == 1 => methodSymbol.TypeArguments[0] as INamedTypeSymbol,
+ _ => null,
+ };
+
var interceptableLocation = c.SemanticModel.GetInterceptableLocation(node, ct);
if (interceptableLocation is null || symbol is null)
{
return null;
}
+
// 获取 [Command("xxx")] 或 [Verb("xxx")] 特性中的 xxx。
var commandAttribute = symbol.GetAttributes().FirstOrDefault(a => a.AttributeClass!.IsAttributeOf())
#pragma warning disable CS0618 // 类型或成员已过时
diff --git a/src/DotNetCampus.CommandLine/CommandRunner.cs b/src/DotNetCampus.CommandLine/CommandRunner.cs
index e5e8437..b14c1c2 100644
--- a/src/DotNetCampus.CommandLine/CommandRunner.cs
+++ b/src/DotNetCampus.CommandLine/CommandRunner.cs
@@ -89,7 +89,7 @@ public Task RunAsync()
{
throw new CommandNameNotFoundException(
string.IsNullOrEmpty(possibleCommandNames)
- ? "No command handler found. Please ensure that at least one command handler is registered by AddHandler()."
+ ? "No command handler found. Please ensure that at least one command handler is registered by AddHandler(), especially a default command handler."
: $"No command handler found for command '{possibleCommandNames}'. Please ensure that the command handler is registered by AddHandler().",
possibleCommandNames);
}
diff --git a/src/DotNetCampus.CommandLine/DotNetCampus.CommandLine.csproj b/src/DotNetCampus.CommandLine/DotNetCampus.CommandLine.csproj
index 3ca002a..078d0d9 100644
--- a/src/DotNetCampus.CommandLine/DotNetCampus.CommandLine.csproj
+++ b/src/DotNetCampus.CommandLine/DotNetCampus.CommandLine.csproj
@@ -47,7 +47,7 @@
-
+
diff --git a/src/DotNetCampus.CommandLine/Package/build/Package.props b/src/DotNetCampus.CommandLine/Package/buildTransitive/Package.props
similarity index 100%
rename from src/DotNetCampus.CommandLine/Package/build/Package.props
rename to src/DotNetCampus.CommandLine/Package/buildTransitive/Package.props
diff --git a/tests/DotNetCampus.CommandLine.Tests/CommandMatching/AddHandlerTests.cs b/tests/DotNetCampus.CommandLine.Tests/CommandMatching/AddHandlerTests.cs
index a39b8b9..91e6a50 100644
--- a/tests/DotNetCampus.CommandLine.Tests/CommandMatching/AddHandlerTests.cs
+++ b/tests/DotNetCampus.CommandLine.Tests/CommandMatching/AddHandlerTests.cs
@@ -206,6 +206,32 @@ public void AddHandler_Mix3(string[] args, string expectedCommand, string expect
Assert.AreEqual(1, exitCode);
}
+ [TestMethod]
+ [DataRow(new[] { "foo" }, nameof(FooOptions), "Foo", TestCommandLineStyle.Flexible, DisplayName = "[Flexible] foo")]
+ [DataRow(new[] { "foo" }, nameof(FooOptions), "Foo", TestCommandLineStyle.DotNet, DisplayName = "[DotNet] foo")]
+ [DataRow(new[] { "foo" }, nameof(FooOptions), "Foo", TestCommandLineStyle.Gnu, DisplayName = "[Gnu] foo")]
+ [DataRow(new[] { "foo" }, nameof(FooOptions), "Foo", TestCommandLineStyle.Windows, DisplayName = "[Windows] foo")]
+ public async Task AddHandler_TypedDelegate(string[] args, string expectedCommand, string expectedValue, TestCommandLineStyle style)
+ {
+ // Arrange
+ string? matched = null;
+ var commandLine = CommandLine.Parse(args, style.ToParsingOptions());
+
+ // Act
+ var result = await commandLine
+ .AddHandler(async (FooOptions o) =>
+ {
+ await Task.Yield();
+ matched = o.Value;
+ })
+ .RunAsync();
+ var matchedTypeName = result.HandledBy!.GetType().Name;
+
+ // Assert
+ Assert.AreEqual(expectedCommand, matchedTypeName);
+ Assert.AreEqual(expectedValue, matched);
+ }
+
// ReSharper disable once RedundantAssignment
private int RunWithExitCode(ref T field, T value)
{