Skip to content

Commit 99db1e3

Browse files
Fix FirstArgumentIsRootCommand falsely matching option values (#2789)
Path.GetFileName(args[0]) extracts the last path segment, which falsely matches command names embedded in option values. For example, "/p:Key=something/dotnet" yields "dotnet" from GetFileName, matching a command named "dotnet". This causes the tokenizer to consume args[0] as the root command instead of parsing it as an option. Replace the Path.GetFileName heuristic with an exact match against the command name. The only other case (args[0] == RootCommand.ExecutablePath) is already handled by a separate check. Co-authored-by: Jon Sequeira <jonsequeira@gmail.com>
1 parent 857b4c3 commit 99db1e3

2 files changed

Lines changed: 35 additions & 11 deletions

File tree

src/System.CommandLine.Tests/ParserTests.RootCommandAndArg0.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,39 @@ public void When_parsing_an_unsplit_string_then_a_renamed_RootCommand_can_be_omi
101101
result3.RootCommandResult.Command.Should().BeSameAs(result1.RootCommandResult.Command);
102102
}
103103

104+
[Fact]
105+
public void When_parsing_a_string_array_option_values_containing_command_name_after_slash_are_not_treated_as_root_command()
106+
{
107+
// Path.GetFileName("/p:Key=something/myapp") returns "myapp",
108+
// which should not be matched as the root command name.
109+
var option = new Option<string>("--property", "/p") { Arity = ArgumentArity.ExactlyOne };
110+
var command = new Command("myapp");
111+
command.Options.Add(option);
112+
113+
var result = command.Parse(Split("/p:Key=something/myapp"));
114+
115+
result.Errors.Should().BeEmpty();
116+
result.GetResult(option).Should().NotBeNull();
117+
}
118+
119+
[Theory]
120+
[InlineData("/p:DockerImage=registry.example.com/project/dotnet")]
121+
[InlineData("-p:DockerImage=registry.example.com/project/dotnet")]
122+
[InlineData("--property:DockerImage=registry.example.com/project/dotnet")]
123+
public void When_parsing_a_string_array_option_values_ending_with_slash_command_name_are_preserved(string arg)
124+
{
125+
// Regression test: Path.GetFileName extracts the last segment after '/',
126+
// so option values like ".../dotnet" falsely matched a command named "dotnet".
127+
var option = new Option<string>("--property", "/p", "-p") { Arity = ArgumentArity.ExactlyOne };
128+
var command = new Command("dotnet");
129+
command.Options.Add(option);
130+
131+
var result = command.Parse(Split(arg));
132+
133+
result.Errors.Should().BeEmpty();
134+
result.GetResult(option).Should().NotBeNull();
135+
}
136+
104137
string[] Split(string value) => CommandLineParser.SplitCommandLine(value).ToArray();
105138
}
106139
}

src/System.CommandLine/Parsing/StringExtensions.cs

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -275,18 +275,9 @@ private static bool FirstArgumentIsRootCommand(IReadOnlyList<string> args, Comma
275275
return true;
276276
}
277277

278-
try
278+
if (rootCommand.EqualsNameOrAlias(args[0]))
279279
{
280-
var potentialRootCommand = Path.GetFileName(args[0]);
281-
282-
if (rootCommand.EqualsNameOrAlias(potentialRootCommand))
283-
{
284-
return true;
285-
}
286-
}
287-
catch (ArgumentException)
288-
{
289-
// possible exception for illegal characters in path on .NET Framework
280+
return true;
290281
}
291282
}
292283

0 commit comments

Comments
 (0)