File: CommandLineUtilities.cs
Web Access
Project: src\src\vstest\src\Microsoft.TestPlatform.Utilities\Microsoft.TestPlatform.Utilities.csproj (Microsoft.TestPlatform.Utilities)
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.VisualStudio.TestPlatform.ObjectModel;

namespace Microsoft.VisualStudio.TestPlatform.Utilities;

public static class CommandLineUtilities
{
    public static bool SplitCommandLineIntoArguments(string args, out string[] arguments)
    {
        bool hadError = false;
        var argArray = new List<string>();
        var currentArg = new StringBuilder();
        bool inQuotes = false;
        int index = 0;

        try
        {
            while (index < args.Length)
            {
                // Skip whitespace.
                while (char.IsWhiteSpace(args[index]))
                {
                    index++;
                }

                // # - comment to end of line
                if (args[index] == '#')
                {
                    index++;
                    while (index < args.Length && args[index] != '\n')
                    {
                        index++;
                    }

                    // We are done processing comment move to next statement.
                    continue;
                }

                // Read argument until next whitespace (not in quotes).
                do
                {
                    if (args[index] == '\\')
                    {
                        // Move to next char.
                        index++;

                        // If this was the last char then output the slash.
                        if (index == args.Length)
                        {
                            currentArg.Append('\\');

                            index++;
                            continue;
                        }
                        else
                        {
                            // If the char after '\' is also a '\', output the second '\' and skip over to the next char.
                            if (args[index] == '\\')
                            {
                                currentArg.Append('\\');

                                // We processed the escaped \, move to next char.
                                index++;
                                continue;
                            }

                            // If the char after '\' is a '"', output '"' and skip over to the next char.
                            if (index <= args.Length && args[index] == '"')
                            {
                                currentArg.Append('"');

                                // We processed the escaped " move to next char.
                                index++;
                                continue;
                            }

                            // If the char after '\' is anything else, output the slash. And continue processing the next char.
                            if (index <= args.Length)
                            {
                                currentArg.Append('\\');

                                // Don't skip to the next char. We outputted the \ because it was not escaping \ or ". Let the next character to be processed by the loop.
                                // index++;
                                continue;
                            }
                        }
                    }
                    // Unescaped quote enters and leaves quoted mode.
                    else if (args[index] == '"')
                    {
                        inQuotes = !inQuotes;
                        index++;
                    }
                    else
                    {
                        // Collect all other characters.
                        currentArg.Append(args[index]);
                        index++;
                    }
                } while (!char.IsWhiteSpace(args[index]) || inQuotes);
                argArray.Add(currentArg.ToString());
                currentArg.Clear();
            }
        }
        catch (IndexOutOfRangeException)
        {
            // got EOF
            if (inQuotes)
            {
                EqtTrace.Verbose("Executor.Execute: Exiting with exit code of {0}", 1);
                EqtTrace.Error("Error: Unbalanced '\"' in command line argument file");
                hadError = true;
            }
            else if (currentArg.Length > 0)
            {
                // valid argument can be terminated by EOF
                argArray.Add(currentArg.ToString());
            }
        }

        arguments = argArray.ToArray();
        return hadError;
    }
}