Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
88 changes: 32 additions & 56 deletions src/Platform/Microsoft.Testing.Platform/CommandLine/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ internal static class CommandLineParser
/// Options parser support:
/// * Only - and -- prefix for options https://learn.microsoft.com/dotnet/standard/commandline/syntax#options
/// * Multiple option arguments https://learn.microsoft.com/dotnet/standard/commandline/syntax#multiple-arguments
/// * Use a space, '=', or ':' as the delimiter between an option name and its argument
/// * Use '=' or ':' as the delimiter between an option name and its argument. See https://learn.microsoft.com/dotnet/standard/commandline/syntax#option-argument-delimiters
/// * escape with \
/// * surrounding with ""
/// * surrounding with ''
Expand All @@ -39,97 +39,73 @@ private static CommandLineParseResult Parse(List<string> args, IEnvironment envi
List<string> errors = [];

string? currentOption = null;
string? currentArg = null;
string? toolName = null;
List<string> currentOptionArguments = [];
bool isFirstRealArgument = true;
for (int i = 0; i < args.Count; i++)
{
if (args[i].StartsWith('@') && ResponseFileHelper.TryReadResponseFile(args[i].Substring(1), errors, out string[]? newArguments))
string? currentArg = args[i];

if (currentArg.StartsWith('@') && ResponseFileHelper.TryReadResponseFile(currentArg.Substring(1), errors, out string[]? newArguments))
{
args.InsertRange(i + 1, newArguments);
continue;
}

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

while (!argumentHandled)
isFirstRealArgument = false;

// we accept as start for options -- and - all the rest are arguments to the previous option
if ((currentArg.Length > 1 && currentArg[0].Equals('-') && !currentArg[1].Equals('-')) ||
(currentArg.Length > 2 && currentArg[0].Equals('-') && currentArg[1].Equals('-') && !currentArg[2].Equals('-')))
{
if (currentArg is null)
if (currentOption is not null)
{
errors.Add(string.Format(CultureInfo.InvariantCulture, PlatformResources.CommandLineParserUnexpectedNullArgument, i));
break;
options.Add(new(currentOption, [.. currentOptionArguments]));
currentOptionArguments.Clear();
}

// we accept as start for options -- and - all the rest are arguments to the previous option
if ((args[i].Length > 1 && currentArg[0].Equals('-') && !currentArg[1].Equals('-')) ||
(args[i].Length > 2 && currentArg[0].Equals('-') && currentArg[1].Equals('-') && !currentArg[2].Equals('-')))
ParseOptionAndSeparators(currentArg, out currentOption, out currentArg);
}

if (currentArg is not null)
{
if (currentOption is null)
{
if (currentOption is null)
{
ParseOptionAndSeparators(args[i], out currentOption, out currentArg);
argumentHandled = currentArg is null;
}
else
{
options.Add(new(currentOption, [.. currentOptionArguments]));
currentOptionArguments.Clear();
ParseOptionAndSeparators(args[i], out currentOption, out currentArg);
argumentHandled = true;
}
errors.Add(string.Format(CultureInfo.InvariantCulture, PlatformResources.CommandLineParserUnexpectedArgument, args[i]));
}
else
{
// If it's the first argument and it doesn't start with - then it's the tool name
if (i == 0 && !args[0][0].Equals('-'))
{
toolName = currentArg;
}
else if (currentOption is null)
if (TryUnescape(currentArg.Trim(), currentOption, environment, out string? unescapedArg, out string? error))
{
errors.Add(string.Format(CultureInfo.InvariantCulture, PlatformResources.CommandLineParserUnexpectedArgument, args[i]));
currentOptionArguments.Add(unescapedArg);
}
else
{
if (TryUnescape(currentArg.Trim(), currentOption, environment, out string? unescapedArg, out string? error))
{
currentOptionArguments.Add(unescapedArg);
}
else
{
errors.Add(error);
}

currentArg = null;
errors.Add(error);
}

argumentHandled = true;
}
}
}

if (currentOption is not null)
{
if (currentArg is not null)
{
if (TryUnescape(currentArg.Trim(), currentOption, environment, out string? unescapedArg, out string? error))
{
currentOptionArguments.Add(unescapedArg);
}
else
{
errors.Add(error);
}
}

options.Add(new(currentOption, [.. currentOptionArguments]));
}

return new CommandLineParseResult(toolName, options, errors);

static void ParseOptionAndSeparators(string arg, out string? currentOption, out string? currentArg)
{
(currentOption, currentArg) = arg.IndexOfAny([':', '=', ' ']) switch
(currentOption, currentArg) = arg.IndexOfAny([':', '=']) switch
{
-1 => (arg, null),
var delimiterIndex => (arg[..delimiterIndex], arg[(delimiterIndex + 1)..]),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema

<!--
Microsoft ResX Schema
Version 2.0

The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.

Example:

... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
Expand All @@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>

There are any number of "resheader" rows that contain simple
There are any number of "resheader" rows that contain simple
name/value pairs.

Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.

The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:

Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.

mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.

mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.

mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
Expand Down Expand Up @@ -319,9 +319,6 @@
<data name="CommandLineParserUnexpectedArgument" xml:space="preserve">
<value>Unexpected argument {0}</value>
</data>
<data name="CommandLineParserUnexpectedNullArgument" xml:space="preserve">
<value>Unexpected null argument at index {0}</value>
</data>
<data name="CommandLineParserUnexpectedSingleQuoteInArgument" xml:space="preserve">
<value>Unexpected single quote in argument: {0}</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,6 @@
<target state="translated">Neočekávaný argument {0}</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedNullArgument">
<source>Unexpected null argument at index {0}</source>
<target state="translated">Neočekávaný argument null v indexu {0}</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedSingleQuoteInArgument">
<source>Unexpected single quote in argument: {0}</source>
<target state="translated">Neočekávaná jednoduchá uvozovka v argumentu: {0}</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,6 @@
<target state="translated">Unerwartetes Argument {0}</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedNullArgument">
<source>Unexpected null argument at index {0}</source>
<target state="translated">Unerwartetes NULL-Argument bei Index {0}</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedSingleQuoteInArgument">
<source>Unexpected single quote in argument: {0}</source>
<target state="translated">Unerwartetes einfaches Anführungszeichen im Argument: {0}</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,6 @@
<target state="translated">Argumento inesperado {0}</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedNullArgument">
<source>Unexpected null argument at index {0}</source>
<target state="translated">Argumento nulo inesperado en el índice {0}</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedSingleQuoteInArgument">
<source>Unexpected single quote in argument: {0}</source>
<target state="translated">Comilla simple inesperada en el argumento: {0}</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,6 @@
<target state="translated">Arguments inattendue {0}</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedNullArgument">
<source>Unexpected null argument at index {0}</source>
<target state="translated">Argument null inattendu à l’index de recherche{0}</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedSingleQuoteInArgument">
<source>Unexpected single quote in argument: {0}</source>
<target state="translated">Guillemet simple inattendu dans l’argument : {0}</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,6 @@
<target state="translated">Argomento imprevisto {0}</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedNullArgument">
<source>Unexpected null argument at index {0}</source>
<target state="translated">Argomento Null imprevisto in corrispondenza dell'indice {0}</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedSingleQuoteInArgument">
<source>Unexpected single quote in argument: {0}</source>
<target state="translated">Virgolette singole impreviste nell'argomento: {0}</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,6 @@
<target state="translated">予期しない引数 {0}</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedNullArgument">
<source>Unexpected null argument at index {0}</source>
<target state="translated">インデックス {0} に予期しない null 引数があります</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedSingleQuoteInArgument">
<source>Unexpected single quote in argument: {0}</source>
<target state="translated">引数に予期しない単一引用符が含まれています: {0}</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,6 @@
<target state="translated">예기치 않은 인수 {0}</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedNullArgument">
<source>Unexpected null argument at index {0}</source>
<target state="translated">인덱스 {0}에 예기치 않은 null 인수가 있습니다.</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedSingleQuoteInArgument">
<source>Unexpected single quote in argument: {0}</source>
<target state="translated">인수 {0}에 예기치 않은 작은따옴표가 있습니다.</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,6 @@
<target state="translated">Nieoczekiwany argument {0}</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedNullArgument">
<source>Unexpected null argument at index {0}</source>
<target state="translated">Nieoczekiwany argument o wartości null przy indeksie {0}</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedSingleQuoteInArgument">
<source>Unexpected single quote in argument: {0}</source>
<target state="translated">Nieoczekiwany pojedynczy cudzysłów w argumencie: {0}</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,6 @@
<target state="translated">Argumento inesperado {0}</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedNullArgument">
<source>Unexpected null argument at index {0}</source>
<target state="translated">Argumento nulo inesperado no índice {0}</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedSingleQuoteInArgument">
<source>Unexpected single quote in argument: {0}</source>
<target state="translated">Aspas simples inesperadas no argumento: {0}</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,6 @@
<target state="translated">Неожиданный аргумент {0}</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedNullArgument">
<source>Unexpected null argument at index {0}</source>
<target state="translated">Неожиданный аргумент, имеющий значение NULL, с индексом {0}</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedSingleQuoteInArgument">
<source>Unexpected single quote in argument: {0}</source>
<target state="translated">Неожиданная одинарная кавычка в аргументе: {0}</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,6 @@
<target state="translated">{0} bağımsız değişkeni beklenmiyordu</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedNullArgument">
<source>Unexpected null argument at index {0}</source>
<target state="translated">{0} dizinindeki null bağımsız değişken beklenmiyordu</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedSingleQuoteInArgument">
<source>Unexpected single quote in argument: {0}</source>
<target state="translated">{0} bağımsız değişkeninde tek alıntı beklenmiyor</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,6 @@
<target state="translated">意外的参数 {0}</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedNullArgument">
<source>Unexpected null argument at index {0}</source>
<target state="translated">索引 {0} 处出现意外 null 参数</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedSingleQuoteInArgument">
<source>Unexpected single quote in argument: {0}</source>
<target state="translated">参数中出现意外的单引号: {0}</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,6 @@
<target state="translated">未預期的引數 {0}</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedNullArgument">
<source>Unexpected null argument at index {0}</source>
<target state="translated">索引 {0} 中有未預期的 Null 引數</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedSingleQuoteInArgument">
<source>Unexpected single quote in argument: {0}</source>
<target state="translated">引數 {0} 中有未預期的單引號</target>
Expand Down
Loading
Loading