Private/Split-QAFrameworkAttributeArgument.ps1

function Split-QAFrameworkAttributeArgument {
    <#
    .SYNOPSIS
        Splits a C# attribute argument list on top-level commas.
    .DESCRIPTION
        Respects string literals (including escaped quotes and verbatim strings) and nested
        parentheses/brackets/braces so that arguments such as
        new[] { "a", "b" } or "text, with comma" are not split incorrectly.
    .PARAMETER ArgumentText
        The raw text between the attribute's outer parentheses.
    .OUTPUTS
        String[] of trimmed argument expressions. Empty array when there are no arguments.
    #>

    [CmdletBinding()]
    [OutputType([string[]])]
    param(
        [Parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [AllowNull()]
        [string]$ArgumentText
    )

    if ([string]::IsNullOrWhiteSpace($ArgumentText)) {
        return @()
    }

    $arguments = [System.Collections.Generic.List[string]]::new()
    $current = [System.Text.StringBuilder]::new()
    $depth = 0
    $inString = $false
    $inChar = $false
    $isVerbatim = $false
    $index = 0

    while ($index -lt $ArgumentText.Length) {
        $char = $ArgumentText[$index]

        if ($inString) {
            [void]$current.Append($char)

            if ($isVerbatim) {
                # In a verbatim string "" is an escaped quote.
                if ($char -eq '"') {
                    if (($index + 1) -lt $ArgumentText.Length -and $ArgumentText[$index + 1] -eq '"') {
                        [void]$current.Append($ArgumentText[$index + 1])
                        $index += 2
                        continue
                    }
                    $inString = $false
                    $isVerbatim = $false
                }
            }
            elseif ($char -eq '\') {
                # Escape sequence: consume the next character verbatim.
                if (($index + 1) -lt $ArgumentText.Length) {
                    [void]$current.Append($ArgumentText[$index + 1])
                    $index += 2
                    continue
                }
            }
            elseif ($char -eq '"') {
                $inString = $false
            }

            $index++
            continue
        }

        if ($inChar) {
            [void]$current.Append($char)
            if ($char -eq '\' -and ($index + 1) -lt $ArgumentText.Length) {
                [void]$current.Append($ArgumentText[$index + 1])
                $index += 2
                continue
            }
            if ($char -eq "'") { $inChar = $false }
            $index++
            continue
        }

        switch ($char) {
            '"' {
                $inString = $true
                # Detect the @"..." verbatim form.
                $isVerbatim = ($current.Length -gt 0 -and $current[$current.Length - 1] -eq '@')
                [void]$current.Append($char)
            }
            "'" {
                $inChar = $true
                [void]$current.Append($char)
            }
            '(' { $depth++; [void]$current.Append($char) }
            '[' { $depth++; [void]$current.Append($char) }
            '{' { $depth++; [void]$current.Append($char) }
            ')' { $depth--; [void]$current.Append($char) }
            ']' { $depth--; [void]$current.Append($char) }
            '}' { $depth--; [void]$current.Append($char) }
            ',' {
                if ($depth -eq 0) {
                    $arguments.Add($current.ToString().Trim())
                    [void]$current.Clear()
                }
                else {
                    [void]$current.Append($char)
                }
            }
            default { [void]$current.Append($char) }
        }

        $index++
    }

    if ($current.ToString().Trim() -ne '') {
        $arguments.Add($current.ToString().Trim())
    }

    return [string[]]$arguments.ToArray()
}