Skip to content

Commit 446e832

Browse files
authored
make error on missing symbol in SetHandler more helpful (#1558)
* make error on missing symbol in SetHandler more helpful * improve error message
1 parent 5ca753b commit 446e832

File tree

2 files changed

+87
-0
lines changed

2 files changed

+87
-0
lines changed

src/System.CommandLine.Tests/Binding/SetHandlerTests.cs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using System.Threading.Tasks;
1010
using FluentAssertions;
1111
using Xunit;
12+
using static System.Environment;
1213

1314
namespace System.CommandLine.Tests.Binding
1415
{
@@ -160,6 +161,62 @@ public void If_service_is_not_found_then_an_error_results()
160161
exitCode.Should().Be(1);
161162
}
162163

164+
[Fact]
165+
public void If_no_symbol_was_passed_for_binding_then_the_error_message_suggests_a_fix_for_the_first_missing_symbol()
166+
{
167+
var boolOption = new Option<bool>("-o");
168+
var stringArg = new Argument<string>("value");
169+
170+
var subcommand = new Command("TheCommand")
171+
{
172+
boolOption,
173+
stringArg
174+
};
175+
176+
var command = new RootCommand
177+
{
178+
subcommand
179+
};
180+
181+
subcommand.SetHandler((bool boolValue, string stringValue) => { });
182+
183+
var console = new TestConsole();
184+
185+
command.Invoke("TheCommand -o hi", console);
186+
187+
console.Error.ToString().Should()
188+
.Contain(
189+
$"The SetHandler call for command 'TheCommand' is missing an Argument or Option for the parameter at position 0. Did you mean to pass one of these?{NewLine}Option<Boolean> -o");
190+
}
191+
192+
[Fact]
193+
public void If_no_symbol_was_passed_for_binding_subsequent_parameter_then_the_error_message_suggests_a_fix_for_the_first_missing_symbol()
194+
{
195+
var boolOption = new Option<bool>("-o");
196+
var stringArg = new Argument<string>("value");
197+
198+
var subcommand = new Command("TheCommand")
199+
{
200+
boolOption,
201+
stringArg
202+
};
203+
204+
var command = new RootCommand
205+
{
206+
subcommand
207+
};
208+
209+
subcommand.SetHandler((bool boolValue, string stringValue) => { }, boolOption);
210+
211+
var console = new TestConsole();
212+
213+
command.Invoke("TheCommand -o hi", console);
214+
215+
console.Error.ToString().Should()
216+
.Contain(
217+
$"The SetHandler call for command 'TheCommand' is missing an Argument or Option for the parameter at position 1. Did you mean to pass one of these?{NewLine}Argument<String> value");
218+
}
219+
163220
[Fact]
164221
public void Custom_types_can_be_bound()
165222
{

src/System.CommandLine/Handler.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33

44
using System.CommandLine.Binding;
55
using System.CommandLine.Invocation;
6+
using System.CommandLine.Parsing;
7+
using System.Linq;
8+
using static System.Environment;
69

710
namespace System.CommandLine;
811

@@ -34,6 +37,33 @@ public static partial class Handler
3437

3538
if (service is null)
3639
{
40+
var candidates = context.ParseResult
41+
.RootCommandResult
42+
.AllSymbolResults()
43+
.Where(r => r.Symbol.Parents.All(p => p is not Option))
44+
.Select(r => r.Symbol)
45+
.OfType<IValueDescriptor>()
46+
.ToArray();
47+
48+
if (candidates.Any())
49+
{
50+
var candidatesDescription = string.Join(
51+
NewLine,
52+
candidates
53+
.Where(c => typeof(T).IsAssignableFrom(c.ValueType))
54+
.Select(c => c switch
55+
{
56+
Argument<T> argument => $"{nameof(Argument)}<{argument.ValueType.Name}> {argument.Name}",
57+
Argument argument => $"{nameof(Argument)} {argument.Name}",
58+
Option<T> option => $"{nameof(Option)}<{option.ValueType.Name}> {option.Aliases.First()}",
59+
Option option => $"{nameof(Option)} {option.Aliases.First()}",
60+
_ => throw new ArgumentOutOfRangeException(nameof(c))
61+
}));
62+
63+
throw new ArgumentException(
64+
$"The {nameof(SetHandler)} call for command '{context.ParseResult.CommandResult.Command.Name}' is missing an {nameof(Argument)} or {nameof(Option)} for the parameter at position {index}. Did you mean to pass one of these?{NewLine}{candidatesDescription}");
65+
}
66+
3767
throw new ArgumentException($"Service not found for type {typeof(T)}.");
3868
}
3969

0 commit comments

Comments
 (0)