Skip to content

Commit 7c049d9

Browse files
authored
introduce SymbolResult.Errors (#2166)
* introduce `SymbolResult.Errors` This property makes it possible to inspect results of other symbols from within a custom parser. * update API baseline
1 parent ad5aed4 commit 7c049d9

File tree

4 files changed

+88
-4
lines changed

4 files changed

+88
-4
lines changed

src/System.CommandLine.ApiCompatibility.Tests/ApiCompatibilityApprovalTests.System_CommandLine_api_is_not_changed.approved.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ System.CommandLine.Parsing
257257
public SymbolResult SymbolResult { get; }
258258
public System.String ToString()
259259
public abstract class SymbolResult
260+
public System.Collections.Generic.IEnumerable<ParseError> Errors { get; }
260261
public SymbolResult Parent { get; }
261262
public System.Collections.Generic.IReadOnlyList<Token> Tokens { get; }
262263
public System.Void AddError(System.String errorMessage)

src/System.CommandLine.Tests/ArgumentTests.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,52 @@ public void Multiple_arguments_can_have_custom_parsers()
442442
.Contain("UH UH");
443443
}
444444

445+
[Theory]
446+
// The two different examples verify that the relative order of the symbols doesn't matter.
447+
[InlineData("--option-with-error 123 --depends-on-option-with-error")]
448+
[InlineData("--depends-on-option-with-error --option-with-error 123")]
449+
public void Custom_parser_can_check_another_option_result_for_custom_errors(string commandLine)
450+
{
451+
var optionWithError = new CliOption<string>("--option-with-error")
452+
{
453+
CustomParser = r =>
454+
{
455+
r.AddError("one");
456+
return r.Tokens[0].Value;
457+
}
458+
};
459+
460+
var optionThatDependsOnOptionWithError = new CliOption<bool>("--depends-on-option-with-error")
461+
{
462+
CustomParser = result =>
463+
{
464+
if (result.FindResultFor(optionWithError) is { } optionWithErrorResult)
465+
{
466+
var otherOptionError = optionWithErrorResult.Errors.SingleOrDefault()?.Message;
467+
468+
result.AddError(otherOptionError + " " + "two");
469+
}
470+
471+
return false;
472+
}
473+
};
474+
475+
var command = new CliCommand("cmd")
476+
{
477+
optionWithError,
478+
optionThatDependsOnOptionWithError
479+
};
480+
481+
var parseResult = command.Parse(commandLine);
482+
483+
parseResult.Errors
484+
.Single(e => e.SymbolResult is OptionResult optResult &&
485+
optResult.Option == optionThatDependsOnOptionWithError)
486+
.Message
487+
.Should()
488+
.Be("one two");
489+
}
490+
445491
[Fact]
446492
public void When_custom_conversion_fails_then_an_option_does_not_accept_further_arguments()
447493
{

src/System.CommandLine/Parsing/ArgumentResult.cs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ public void OnlyTake(int numberOfTokens)
123123
/// <inheritdoc/>
124124
public override void AddError(string errorMessage)
125125
{
126-
SymbolResultTree.AddError(new ParseError(errorMessage, Parent is OptionResult option ? option : this));
126+
SymbolResultTree.AddError(new ParseError(errorMessage, AppliesToPublicSymbolResult));
127127
_conversionResult = ArgumentConversionResult.Failure(this, errorMessage, ArgumentConversionResultType.Failed);
128128
}
129129

@@ -188,17 +188,29 @@ private ArgumentConversionResult ValidateAndConvert(bool useValidators)
188188
return ArgumentConversionResult.Success(this, value);
189189
}
190190

191-
return ReportErrorIfNeeded(ArgumentConversionResult.ArgumentConversionCannotParse(this, Argument.ValueType, Tokens[0].Value));
191+
return ReportErrorIfNeeded(
192+
ArgumentConversionResult.ArgumentConversionCannotParse(
193+
this,
194+
Argument.ValueType,
195+
Tokens.Count > 0
196+
? Tokens[0].Value
197+
: ""));
192198

193199
ArgumentConversionResult ReportErrorIfNeeded(ArgumentConversionResult result)
194200
{
195201
if (result.Result >= ArgumentConversionResultType.Failed)
196202
{
197-
SymbolResultTree.AddError(new ParseError(result.ErrorMessage!, Parent is OptionResult option ? option : this));
203+
SymbolResultTree.AddError(new ParseError(result.ErrorMessage!, AppliesToPublicSymbolResult));
198204
}
199205

200206
return result;
201207
}
202208
}
209+
210+
/// <summary>
211+
/// Since Option.Argument is an internal implementation detail, this ArgumentResult applies to the OptionResult in public API if the parent is an OptionResult.
212+
/// </summary>
213+
private SymbolResult AppliesToPublicSymbolResult =>
214+
Parent is OptionResult optionResult ? optionResult : this;
203215
}
204216
}

src/System.CommandLine/Parsing/SymbolResult.cs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

44
using System.Collections.Generic;
5-
using System.CommandLine.Binding;
5+
using System.Linq;
66

77
namespace System.CommandLine.Parsing
88
{
@@ -20,6 +20,31 @@ private protected SymbolResult(SymbolResultTree symbolResultTree, SymbolResult?
2020
Parent = parent;
2121
}
2222

23+
/// <summary>
24+
/// The parse errors associated with this symbol result.
25+
/// </summary>
26+
public IEnumerable<ParseError> Errors
27+
{
28+
get
29+
{
30+
var parseErrors = SymbolResultTree.Errors;
31+
32+
if (parseErrors is null)
33+
{
34+
yield break;
35+
}
36+
37+
for (var i = 0; i < parseErrors.Count; i++)
38+
{
39+
var parseError = parseErrors[i];
40+
if (parseError.SymbolResult == this)
41+
{
42+
yield return parseError;
43+
}
44+
}
45+
}
46+
}
47+
2348
/// <summary>
2449
/// The parent symbol result in the parse tree.
2550
/// </summary>

0 commit comments

Comments
 (0)