Skip to content

Commit 978d8cb

Browse files
authored
Fix MTP command-line parsing (#7044)
1 parent 0dae7ae commit 978d8cb

18 files changed

+126
-158
lines changed

src/Platform/Microsoft.Testing.Platform/CommandLine/Parser.cs

Lines changed: 32 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ internal static class CommandLineParser
1212
/// Options parser support:
1313
/// * Only - and -- prefix for options https://learn.microsoft.com/dotnet/standard/commandline/syntax#options
1414
/// * Multiple option arguments https://learn.microsoft.com/dotnet/standard/commandline/syntax#multiple-arguments
15-
/// * Use a space, '=', or ':' as the delimiter between an option name and its argument
15+
/// * Use '=' or ':' as the delimiter between an option name and its argument. See https://learn.microsoft.com/dotnet/standard/commandline/syntax#option-argument-delimiters
1616
/// * escape with \
1717
/// * surrounding with ""
1818
/// * surrounding with ''
@@ -39,97 +39,73 @@ private static CommandLineParseResult Parse(List<string> args, IEnvironment envi
3939
List<string> errors = [];
4040

4141
string? currentOption = null;
42-
string? currentArg = null;
4342
string? toolName = null;
4443
List<string> currentOptionArguments = [];
44+
bool isFirstRealArgument = true;
4545
for (int i = 0; i < args.Count; i++)
4646
{
47-
if (args[i].StartsWith('@') && ResponseFileHelper.TryReadResponseFile(args[i].Substring(1), errors, out string[]? newArguments))
47+
string? currentArg = args[i];
48+
49+
if (currentArg.StartsWith('@') && ResponseFileHelper.TryReadResponseFile(currentArg.Substring(1), errors, out string[]? newArguments))
4850
{
4951
args.InsertRange(i + 1, newArguments);
5052
continue;
5153
}
5254

53-
bool argumentHandled = false;
54-
currentArg = args[i];
55+
// If it's the first argument and it doesn't start with - then it's the tool name
56+
// TODO: This won't work correctly if the first argument provided is a response file that contains the tool name.
57+
if (isFirstRealArgument && currentArg[0] != '-')
58+
{
59+
toolName = currentArg;
60+
isFirstRealArgument = false;
61+
continue;
62+
}
5563

56-
while (!argumentHandled)
64+
isFirstRealArgument = false;
65+
66+
// we accept as start for options -- and - all the rest are arguments to the previous option
67+
if ((currentArg.Length > 1 && currentArg[0].Equals('-') && !currentArg[1].Equals('-')) ||
68+
(currentArg.Length > 2 && currentArg[0].Equals('-') && currentArg[1].Equals('-') && !currentArg[2].Equals('-')))
5769
{
58-
if (currentArg is null)
70+
if (currentOption is not null)
5971
{
60-
errors.Add(string.Format(CultureInfo.InvariantCulture, PlatformResources.CommandLineParserUnexpectedNullArgument, i));
61-
break;
72+
options.Add(new(currentOption, [.. currentOptionArguments]));
73+
currentOptionArguments.Clear();
6274
}
6375

64-
// we accept as start for options -- and - all the rest are arguments to the previous option
65-
if ((args[i].Length > 1 && currentArg[0].Equals('-') && !currentArg[1].Equals('-')) ||
66-
(args[i].Length > 2 && currentArg[0].Equals('-') && currentArg[1].Equals('-') && !currentArg[2].Equals('-')))
76+
ParseOptionAndSeparators(currentArg, out currentOption, out currentArg);
77+
}
78+
79+
if (currentArg is not null)
80+
{
81+
if (currentOption is null)
6782
{
68-
if (currentOption is null)
69-
{
70-
ParseOptionAndSeparators(args[i], out currentOption, out currentArg);
71-
argumentHandled = currentArg is null;
72-
}
73-
else
74-
{
75-
options.Add(new(currentOption, [.. currentOptionArguments]));
76-
currentOptionArguments.Clear();
77-
ParseOptionAndSeparators(args[i], out currentOption, out currentArg);
78-
argumentHandled = true;
79-
}
83+
errors.Add(string.Format(CultureInfo.InvariantCulture, PlatformResources.CommandLineParserUnexpectedArgument, args[i]));
8084
}
8185
else
8286
{
83-
// If it's the first argument and it doesn't start with - then it's the tool name
84-
if (i == 0 && !args[0][0].Equals('-'))
85-
{
86-
toolName = currentArg;
87-
}
88-
else if (currentOption is null)
87+
if (TryUnescape(currentArg.Trim(), currentOption, environment, out string? unescapedArg, out string? error))
8988
{
90-
errors.Add(string.Format(CultureInfo.InvariantCulture, PlatformResources.CommandLineParserUnexpectedArgument, args[i]));
89+
currentOptionArguments.Add(unescapedArg);
9190
}
9291
else
9392
{
94-
if (TryUnescape(currentArg.Trim(), currentOption, environment, out string? unescapedArg, out string? error))
95-
{
96-
currentOptionArguments.Add(unescapedArg);
97-
}
98-
else
99-
{
100-
errors.Add(error);
101-
}
102-
103-
currentArg = null;
93+
errors.Add(error);
10494
}
105-
106-
argumentHandled = true;
10795
}
10896
}
10997
}
11098

11199
if (currentOption is not null)
112100
{
113-
if (currentArg is not null)
114-
{
115-
if (TryUnescape(currentArg.Trim(), currentOption, environment, out string? unescapedArg, out string? error))
116-
{
117-
currentOptionArguments.Add(unescapedArg);
118-
}
119-
else
120-
{
121-
errors.Add(error);
122-
}
123-
}
124-
125101
options.Add(new(currentOption, [.. currentOptionArguments]));
126102
}
127103

128104
return new CommandLineParseResult(toolName, options, errors);
129105

130106
static void ParseOptionAndSeparators(string arg, out string? currentOption, out string? currentArg)
131107
{
132-
(currentOption, currentArg) = arg.IndexOfAny([':', '=', ' ']) switch
108+
(currentOption, currentArg) = arg.IndexOfAny([':', '=']) switch
133109
{
134110
-1 => (arg, null),
135111
var delimiterIndex => (arg[..delimiterIndex], arg[(delimiterIndex + 1)..]),

src/Platform/Microsoft.Testing.Platform/Resources/PlatformResources.resx

Lines changed: 27 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<root>
3-
<!--
4-
Microsoft ResX Schema
5-
3+
<!--
4+
Microsoft ResX Schema
5+
66
Version 2.0
7-
8-
The primary goals of this format is to allow a simple XML format
9-
that is mostly human readable. The generation and parsing of the
10-
various data types are done through the TypeConverter classes
7+
8+
The primary goals of this format is to allow a simple XML format
9+
that is mostly human readable. The generation and parsing of the
10+
various data types are done through the TypeConverter classes
1111
associated with the data types.
12-
12+
1313
Example:
14-
14+
1515
... ado.net/XML headers & schema ...
1616
<resheader name="resmimetype">text/microsoft-resx</resheader>
1717
<resheader name="version">2.0</resheader>
@@ -26,36 +26,36 @@
2626
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
2727
<comment>This is a comment</comment>
2828
</data>
29-
30-
There are any number of "resheader" rows that contain simple
29+
30+
There are any number of "resheader" rows that contain simple
3131
name/value pairs.
32-
33-
Each data row contains a name, and value. The row also contains a
34-
type or mimetype. Type corresponds to a .NET class that support
35-
text/value conversion through the TypeConverter architecture.
36-
Classes that don't support this are serialized and stored with the
32+
33+
Each data row contains a name, and value. The row also contains a
34+
type or mimetype. Type corresponds to a .NET class that support
35+
text/value conversion through the TypeConverter architecture.
36+
Classes that don't support this are serialized and stored with the
3737
mimetype set.
38-
39-
The mimetype is used for serialized objects, and tells the
40-
ResXResourceReader how to depersist the object. This is currently not
38+
39+
The mimetype is used for serialized objects, and tells the
40+
ResXResourceReader how to depersist the object. This is currently not
4141
extensible. For a given mimetype the value must be set accordingly:
42-
43-
Note - application/x-microsoft.net.object.binary.base64 is the format
44-
that the ResXResourceWriter will generate, however the reader can
42+
43+
Note - application/x-microsoft.net.object.binary.base64 is the format
44+
that the ResXResourceWriter will generate, however the reader can
4545
read any of the formats listed below.
46-
46+
4747
mimetype: application/x-microsoft.net.object.binary.base64
48-
value : The object must be serialized with
48+
value : The object must be serialized with
4949
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
5050
: and then encoded with base64 encoding.
51-
51+
5252
mimetype: application/x-microsoft.net.object.soap.base64
53-
value : The object must be serialized with
53+
value : The object must be serialized with
5454
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
5555
: and then encoded with base64 encoding.
5656
5757
mimetype: application/x-microsoft.net.object.bytearray.base64
58-
value : The object must be serialized into a byte array
58+
value : The object must be serialized into a byte array
5959
: using a System.ComponentModel.TypeConverter
6060
: and then encoded with base64 encoding.
6161
-->
@@ -319,9 +319,6 @@
319319
<data name="CommandLineParserUnexpectedArgument" xml:space="preserve">
320320
<value>Unexpected argument {0}</value>
321321
</data>
322-
<data name="CommandLineParserUnexpectedNullArgument" xml:space="preserve">
323-
<value>Unexpected null argument at index {0}</value>
324-
</data>
325322
<data name="CommandLineParserUnexpectedSingleQuoteInArgument" xml:space="preserve">
326323
<value>Unexpected single quote in argument: {0}</value>
327324
</data>

src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.cs.xlf

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,11 +142,6 @@
142142
<target state="translated">Neočekávaný argument {0}</target>
143143
<note />
144144
</trans-unit>
145-
<trans-unit id="CommandLineParserUnexpectedNullArgument">
146-
<source>Unexpected null argument at index {0}</source>
147-
<target state="translated">Neočekávaný argument null v indexu {0}</target>
148-
<note />
149-
</trans-unit>
150145
<trans-unit id="CommandLineParserUnexpectedSingleQuoteInArgument">
151146
<source>Unexpected single quote in argument: {0}</source>
152147
<target state="translated">Neočekávaná jednoduchá uvozovka v argumentu: {0}</target>

src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.de.xlf

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,11 +142,6 @@
142142
<target state="translated">Unerwartetes Argument {0}</target>
143143
<note />
144144
</trans-unit>
145-
<trans-unit id="CommandLineParserUnexpectedNullArgument">
146-
<source>Unexpected null argument at index {0}</source>
147-
<target state="translated">Unerwartetes NULL-Argument bei Index {0}</target>
148-
<note />
149-
</trans-unit>
150145
<trans-unit id="CommandLineParserUnexpectedSingleQuoteInArgument">
151146
<source>Unexpected single quote in argument: {0}</source>
152147
<target state="translated">Unerwartetes einfaches Anführungszeichen im Argument: {0}</target>

src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.es.xlf

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,11 +142,6 @@
142142
<target state="translated">Argumento inesperado {0}</target>
143143
<note />
144144
</trans-unit>
145-
<trans-unit id="CommandLineParserUnexpectedNullArgument">
146-
<source>Unexpected null argument at index {0}</source>
147-
<target state="translated">Argumento nulo inesperado en el índice {0}</target>
148-
<note />
149-
</trans-unit>
150145
<trans-unit id="CommandLineParserUnexpectedSingleQuoteInArgument">
151146
<source>Unexpected single quote in argument: {0}</source>
152147
<target state="translated">Comilla simple inesperada en el argumento: {0}</target>

src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.fr.xlf

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,11 +142,6 @@
142142
<target state="translated">Arguments inattendue {0}</target>
143143
<note />
144144
</trans-unit>
145-
<trans-unit id="CommandLineParserUnexpectedNullArgument">
146-
<source>Unexpected null argument at index {0}</source>
147-
<target state="translated">Argument null inattendu à l’index de recherche{0}</target>
148-
<note />
149-
</trans-unit>
150145
<trans-unit id="CommandLineParserUnexpectedSingleQuoteInArgument">
151146
<source>Unexpected single quote in argument: {0}</source>
152147
<target state="translated">Guillemet simple inattendu dans l’argument : {0}</target>

src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.it.xlf

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,11 +142,6 @@
142142
<target state="translated">Argomento imprevisto {0}</target>
143143
<note />
144144
</trans-unit>
145-
<trans-unit id="CommandLineParserUnexpectedNullArgument">
146-
<source>Unexpected null argument at index {0}</source>
147-
<target state="translated">Argomento Null imprevisto in corrispondenza dell'indice {0}</target>
148-
<note />
149-
</trans-unit>
150145
<trans-unit id="CommandLineParserUnexpectedSingleQuoteInArgument">
151146
<source>Unexpected single quote in argument: {0}</source>
152147
<target state="translated">Virgolette singole impreviste nell'argomento: {0}</target>

src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ja.xlf

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,11 +142,6 @@
142142
<target state="translated">予期しない引数 {0}</target>
143143
<note />
144144
</trans-unit>
145-
<trans-unit id="CommandLineParserUnexpectedNullArgument">
146-
<source>Unexpected null argument at index {0}</source>
147-
<target state="translated">インデックス {0} に予期しない null 引数があります</target>
148-
<note />
149-
</trans-unit>
150145
<trans-unit id="CommandLineParserUnexpectedSingleQuoteInArgument">
151146
<source>Unexpected single quote in argument: {0}</source>
152147
<target state="translated">引数に予期しない単一引用符が含まれています: {0}</target>

src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ko.xlf

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,11 +142,6 @@
142142
<target state="translated">예기치 않은 인수 {0}</target>
143143
<note />
144144
</trans-unit>
145-
<trans-unit id="CommandLineParserUnexpectedNullArgument">
146-
<source>Unexpected null argument at index {0}</source>
147-
<target state="translated">인덱스 {0}에 예기치 않은 null 인수가 있습니다.</target>
148-
<note />
149-
</trans-unit>
150145
<trans-unit id="CommandLineParserUnexpectedSingleQuoteInArgument">
151146
<source>Unexpected single quote in argument: {0}</source>
152147
<target state="translated">인수 {0}에 예기치 않은 작은따옴표가 있습니다.</target>

src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.pl.xlf

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,11 +142,6 @@
142142
<target state="translated">Nieoczekiwany argument {0}</target>
143143
<note />
144144
</trans-unit>
145-
<trans-unit id="CommandLineParserUnexpectedNullArgument">
146-
<source>Unexpected null argument at index {0}</source>
147-
<target state="translated">Nieoczekiwany argument o wartości null przy indeksie {0}</target>
148-
<note />
149-
</trans-unit>
150145
<trans-unit id="CommandLineParserUnexpectedSingleQuoteInArgument">
151146
<source>Unexpected single quote in argument: {0}</source>
152147
<target state="translated">Nieoczekiwany pojedynczy cudzysłów w argumencie: {0}</target>

0 commit comments

Comments
 (0)