Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixing issue #2392 #2402

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/System.CommandLine.Tests/ParseDiagramTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,23 @@ public void Parse_result_diagram_displays_unmatched_tokens()
.Be("[ command ![ -x <ar> ] ]");
}

// https://github.com/dotnet/command-line-api/issues/2392
[Fact]
public void Parse_diagram_with_option_argument_error_only_one_error_is_returned()
{
var command = new CliCommand("the-command")
{
new CliOption<int[]>("-x") { Arity = new ArgumentArity(2, 3)}
};

ParseResult parseResult = command.Parse("-x 1 -x 2 -x 3 -x 4");
_ = parseResult.Diagram();

parseResult.Errors
.Should()
.ContainSingle();
}

[Fact]
public void Parse_diagram_shows_type_conversion_errors()
{
Expand Down
19 changes: 17 additions & 2 deletions src/System.CommandLine.Tests/ParserTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// 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.Collections.Generic;
Expand Down Expand Up @@ -1560,7 +1560,22 @@ public void When_option_arguments_are_greater_than_maximum_arity_then_an_error_i
.Should()
.Contain(LocalizationResources.UnrecognizedCommandOrArgument("4"));
}


[Fact]
public void When_the_number_of_option_arguments_are_greater_than_maximum_arity_then_an_error_is_returned()
{
var command = new CliCommand("the-command")
{
new CliOption<int[]>("-x") { Arity = new ArgumentArity(2, 3)}
};

command.Parse("-x 1 -x 2 -x 3 -x 4")
.Errors
.Select(e => e.Message)
.Should()
.Contain(LocalizationResources.OptionArgumentsMaximumExceeded("-x", 3, 4));
}

[Fact]
public void Tokens_are_not_split_if_the_part_before_the_delimiter_is_not_an_option()
{
Expand Down
19 changes: 14 additions & 5 deletions src/System.CommandLine/ArgumentArity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,20 @@ internal static bool Validate(ArgumentResult argumentResult, [NotNullWhen(false)
{
if (!optionResult.Option.AllowMultipleArgumentsPerToken)
{
error = ArgumentConversionResult.Failure(
argumentResult,
LocalizationResources.ExpectsOneArgument(optionResult),
ArgumentConversionResultType.FailedTooManyArguments);

if (argumentResult.Argument.Arity.MaximumNumberOfValues > 1)
{
error = ArgumentConversionResult.Failure(
argumentResult,
LocalizationResources.OptionArgumentsMaximumExceeded(optionResult, argumentResult.Argument.Arity.MaximumNumberOfValues),
ArgumentConversionResultType.FailedTooManyArguments);
}
else
{
error = ArgumentConversionResult.Failure(
argumentResult,
LocalizationResources.ExpectsOneArgument(optionResult),
ArgumentConversionResultType.FailedTooManyArguments);
}
return false;
}
}
Expand Down
14 changes: 13 additions & 1 deletion src/System.CommandLine/LocalizationResources.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,23 @@ namespace System.CommandLine
internal static class LocalizationResources
{
/// <summary>
/// Interpolates values into a localized string similar to Command &apos;{0}&apos; expects a single argument but {1} were provided.
/// Interpolates values into a localized string similar to Option &apos;{0}&apos; expects a single argument but {1} were provided.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this used by both options and commands?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was to make the comment match the actual value that is getting returned. I believe the only usage is for options. I suspect this comment was wrong from a copy and paste error when we initially did the localization resources.

/// </summary>
internal static string ExpectsOneArgument(OptionResult optionResult)
=> GetResourceString(Properties.Resources.OptionExpectsOneArgument, GetOptionName(optionResult), optionResult.Tokens.Count);

/// <summary>
/// Interpolates values into a localized string similar to Option &apos;{0}&apos; expects at most {1} arguments but {2} were provided.
/// </summary>
internal static string OptionArgumentsMaximumExceeded(OptionResult optionResult, int maximumOptionArgumentsAllowed)
=> OptionArgumentsMaximumExceeded(GetOptionName(optionResult), maximumOptionArgumentsAllowed, optionResult.Tokens.Count);

/// <summary>
/// Interpolates values into a localized string similar to Option &apos;{0}&apos; expects at most {1} arguments but {2} were provided.
/// </summary>
internal static string OptionArgumentsMaximumExceeded(string optionName, int maximumOptionArgumentsAllowed, int foundTokenCount)
=> GetResourceString(Properties.Resources.OptionArgumentsMaximumExceeded, optionName, maximumOptionArgumentsAllowed, foundTokenCount);

/// <summary>
/// Interpolates values into a localized string similar to Directory does not exist: {0}.
/// </summary>
Expand Down
4 changes: 2 additions & 2 deletions src/System.CommandLine/Parsing/CommandResult.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// 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.Collections.Generic;
Expand Down Expand Up @@ -125,7 +125,7 @@ private void ValidateOptions(bool completeValidation)
// When_there_is_an_arity_error_then_further_errors_are_not_reported
if (!ArgumentArity.Validate(argumentResult, out var error))
{
optionResult.AddError(error.ErrorMessage!);
argumentResult.AddError(error.ErrorMessage!);
continue;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not understand this change. If this is on options, I thought we got the errors from the option. Do we get them from the option's argument?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was one of the key fixes, that was causing the diagram command to insert a duplicate error.

The process looks like this:

  1. During the Parse, this line was invoked and the error was added to the OptionResult.
  2. The AddError method adds the error into the SymbolResultTree, and keys it based on the symbol. However, ArgumentResult.AddError method has a special case for options, where it will use the OptionResult if that is the parent. So in either case the error is added to the SymbolResultTree with the OptionResult as the key. However, in the case of the ArgumentResult.AddError it also sets the _conversionResult. So the change here still sets the error on the OptionResult, but it also causes the _conversionResult field to be set.
  3. When calling the Diagram (and likely this bug exists with other code paths as well), it will call ArgumentResult.GetArgumentConversionResult. That method will call ValidateAndConvert if the _conversionResult is null. Calling ValidateAndConvert will add an error to the result. This was the result of the second error being added.
  4. Because Diagram was being invoked from the ParseResult.ToString() it was being invoked whenever the debugger inspected a ParseResult. This was the reason for the un-usual behavior that you saw.

}

Expand Down
9 changes: 9 additions & 0 deletions src/System.CommandLine/Properties/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/System.CommandLine/Properties/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -225,4 +225,7 @@
<data name="ArgumentConversionCannotParseForOption_Completions" xml:space="preserve">
<value>Cannot parse argument '{0}' for option '{1}' as expected type '{2}'. Did you mean one of the following?{3}</value>
</data>
<data name="OptionArgumentsMaximumExceeded" xml:space="preserve">
<value>Option '{0}' expects at most {1} arguments but {2} were provided.</value>
</data>
</root>
Loading