Skip to content

Commit 1b85be5

Browse files
authored
When TreatUnmatchedTokensAsErrors is set to false, there should be no errors reported for unmatched tokens (#2208)
* add failing tests * fix: don't report errors for unmatched tokens for commands with TreatUnmatchedTokensAsErrors set to false root command TreatUnmatchedTokensAsErrors set to false has precedence over subcommands * adjust Diagram_directive_writes_parse_diagram test: the error for unmatched tokens is now being reported for the inner most command, as long as TreatUnmatchedTokensAsErrors is true
1 parent 3252cad commit 1b85be5

File tree

5 files changed

+78
-8
lines changed

5 files changed

+78
-8
lines changed

src/System.CommandLine.Tests/ParseDirectiveTests.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,17 @@ public DiagramDirectiveTests(ITestOutputHelper output)
1818
this.output = output;
1919
}
2020

21-
[Fact]
22-
public async Task Diagram_directive_writes_parse_diagram()
21+
[Theory]
22+
[InlineData(true)]
23+
[InlineData(false)]
24+
public async Task Diagram_directive_writes_parse_diagram(bool treatUnmatchedTokensAsErrors)
2325
{
2426
var rootCommand = new CliRootCommand();
2527
var subcommand = new CliCommand("subcommand");
2628
rootCommand.Subcommands.Add(subcommand);
2729
var option = new CliOption<int>("-c", "--count");
2830
subcommand.Options.Add(option);
31+
subcommand.TreatUnmatchedTokensAsErrors = treatUnmatchedTokensAsErrors;
2932

3033
CliConfiguration config = new(rootCommand)
3134
{
@@ -39,10 +42,14 @@ public async Task Diagram_directive_writes_parse_diagram()
3942

4043
await result.InvokeAsync();
4144

45+
string expected = treatUnmatchedTokensAsErrors
46+
? $"[ {CliRootCommand.ExecutableName} ![ subcommand [ -c <34> ] ] ] ???--> --nonexistent wat" + Environment.NewLine
47+
: $"[ {CliRootCommand.ExecutableName} [ subcommand [ -c <34> ] ] ] ???--> --nonexistent wat" + Environment.NewLine;
48+
4249
config.Output
4350
.ToString()
4451
.Should()
45-
.Be($"![ {CliRootCommand.ExecutableName} [ subcommand [ -c <34> ] ] ] ???--> --nonexistent wat" + Environment.NewLine);
52+
.Be(expected);
4653
}
4754

4855
[Fact]

src/System.CommandLine.Tests/ParserTests.cs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1332,6 +1332,59 @@ public void When_a_command_line_has_unmatched_tokens_they_are_not_applied_to_sub
13321332
result.UnmatchedTokens.Should().BeEquivalentTo("unmatched-token");
13331333
}
13341334

1335+
[Theory]
1336+
[InlineData(true)]
1337+
[InlineData(false)]
1338+
public void When_a_command_line_has_unmatched_tokens_the_parse_result_action_should_depend_on_parsed_command_TreatUnmatchedTokensAsErrors(bool treatUnmatchedTokensAsErrors)
1339+
{
1340+
CliRootCommand rootCommand = new();
1341+
CliCommand subcommand = new("vstest")
1342+
{
1343+
new CliOption<string>("--Platform"),
1344+
new CliOption<string>("--Framework"),
1345+
new CliOption<string[]>("--logger")
1346+
};
1347+
subcommand.TreatUnmatchedTokensAsErrors = treatUnmatchedTokensAsErrors;
1348+
rootCommand.Subcommands.Add(subcommand);
1349+
1350+
var result = rootCommand.Parse("vstest test1.dll test2.dll");
1351+
1352+
result.UnmatchedTokens.Should().BeEquivalentTo("test1.dll", "test2.dll");
1353+
1354+
if (treatUnmatchedTokensAsErrors)
1355+
{
1356+
result.Errors.Should().NotBeEmpty();
1357+
result.Action.Should().NotBeSameAs(result.CommandResult.Command.Action);
1358+
}
1359+
else
1360+
{
1361+
result.Errors.Should().BeEmpty();
1362+
result.Action.Should().BeSameAs(result.CommandResult.Command.Action);
1363+
}
1364+
}
1365+
1366+
[Fact]
1367+
public void RootCommand_TreatUnmatchedTokensAsErrors_set_to_false_has_precedence_over_subcommands()
1368+
{
1369+
CliRootCommand rootCommand = new();
1370+
rootCommand.TreatUnmatchedTokensAsErrors = false;
1371+
CliCommand subcommand = new("vstest")
1372+
{
1373+
new CliOption<string>("--Platform"),
1374+
new CliOption<string>("--Framework"),
1375+
new CliOption<string[]>("--logger")
1376+
};
1377+
subcommand.TreatUnmatchedTokensAsErrors = true; // the default, set to true to make it explicit
1378+
rootCommand.Subcommands.Add(subcommand);
1379+
1380+
var result = rootCommand.Parse("vstest test1.dll test2.dll");
1381+
1382+
result.UnmatchedTokens.Should().BeEquivalentTo("test1.dll", "test2.dll");
1383+
1384+
result.Errors.Should().BeEmpty();
1385+
result.Action.Should().BeSameAs(result.CommandResult.Command.Action);
1386+
}
1387+
13351388
[Fact]
13361389
public void Parse_can_not_be_called_with_null_args()
13371390
{

src/System.CommandLine/Parsing/ArgumentResult.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,18 @@ public void OnlyTake(int numberOfTokens)
107107
nextArgumentIndex++;
108108
}
109109

110+
CommandResult rootCommand = parent;
111+
while (rootCommand.Parent is CommandResult nextLevel)
112+
{
113+
rootCommand = nextLevel;
114+
}
115+
110116
// When_tokens_are_passed_on_by_custom_parser_on_last_argument_then_they_become_unmatched_tokens
111117
while (tokensToPass > 0)
112118
{
113119
CliToken unmatched = _tokens[numberOfTokens];
114120
_tokens.RemoveAt(numberOfTokens);
115-
SymbolResultTree.AddUnmatchedToken(unmatched, parent.Command.TreatUnmatchedTokensAsErrors ? parent : null);
121+
SymbolResultTree.AddUnmatchedToken(unmatched, parent, rootCommand);
116122
--tokensToPass;
117123
}
118124
}

src/System.CommandLine/Parsing/ParseOperation.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -359,8 +359,7 @@ private void AddCurrentTokenToUnmatched()
359359
return;
360360
}
361361

362-
_symbolResultTree.AddUnmatchedToken(CurrentToken,
363-
_rootCommandResult.Command.TreatUnmatchedTokensAsErrors ? _rootCommandResult : null);
362+
_symbolResultTree.AddUnmatchedToken(CurrentToken, _innermostCommandResult, _rootCommandResult);
364363
}
365364

366365
private void Validate()

src/System.CommandLine/Parsing/SymbolResultTree.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,17 @@ internal IEnumerable<SymbolResult> GetChildren(SymbolResult parent)
5555

5656
internal void InsertFirstError(ParseError parseError) => (Errors ??= new()).Insert(0, parseError);
5757

58-
internal void AddUnmatchedToken(CliToken token, CommandResult? commandResult)
58+
internal void AddUnmatchedToken(CliToken token, CommandResult commandResult, CommandResult rootCommandResult)
5959
{
6060
(UnmatchedTokens ??= new()).Add(token);
6161

62-
if (commandResult is not null)
62+
if (commandResult.Command.TreatUnmatchedTokensAsErrors)
6363
{
64+
if (commandResult != rootCommandResult && !rootCommandResult.Command.TreatUnmatchedTokensAsErrors)
65+
{
66+
return;
67+
}
68+
6469
AddError(new ParseError(LocalizationResources.UnrecognizedCommandOrArgument(token.Value), commandResult));
6570
}
6671
}

0 commit comments

Comments
 (0)