Private/Get-QAFrameworkAttribute.ps1

function Get-QAFrameworkAttribute {
    <#
    .SYNOPSIS
        Extracts individual attributes from a C# attribute block.
    .DESCRIPTION
        Scans a block of attribute text and returns every attribute it contains. Handles
        grouped attributes ([A, B("x")]), attribute targets ([method: Foo]), multi-line
        argument lists and string literals containing brackets or commas.
    .PARAMETER Text
        The attribute block text.
    .OUTPUTS
        PSCustomObject with Name and ArgumentText properties, one per attribute.
    #>

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

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

    $index = 0
    while ($index -lt $Text.Length) {
        if ($Text[$index] -ne '[') {
            $index++
            continue
        }

        # Find the matching closing bracket for this attribute group.
        $depth = 0
        $inString = $false
        $isVerbatim = $false
        $inChar = $false
        $scan = $index
        $closeIndex = -1

        while ($scan -lt $Text.Length) {
            $char = $Text[$scan]

            if ($inString) {
                if ($isVerbatim) {
                    if ($char -eq '"') {
                        if (($scan + 1) -lt $Text.Length -and $Text[$scan + 1] -eq '"') { $scan += 2; continue }
                        $inString = $false; $isVerbatim = $false
                    }
                }
                elseif ($char -eq '\') { $scan += 2; continue }
                elseif ($char -eq '"') { $inString = $false }
                $scan++
                continue
            }

            if ($inChar) {
                if ($char -eq '\') { $scan += 2; continue }
                if ($char -eq "'") { $inChar = $false }
                $scan++
                continue
            }

            switch ($char) {
                '"' {
                    $inString = $true
                    $isVerbatim = ($scan -gt 0 -and $Text[$scan - 1] -eq '@')
                }
                "'" { $inChar = $true }
                '[' { $depth++ }
                ']' { $depth-- }
            }

            if ($depth -eq 0 -and $char -eq ']') {
                $closeIndex = $scan
                break
            }

            $scan++
        }

        if ($closeIndex -lt 0) { break }

        $inner = $Text.Substring($index + 1, $closeIndex - $index - 1)

        # Strip an attribute target prefix such as "method:" or "assembly:".
        if ($inner -match '(?s)^\s*(?:assembly|module|field|event|method|param|property|return|type)\s*:\s*(.+)$') {
            $inner = $Matches[1]
        }

        foreach ($entry in (Split-QAFrameworkAttributeArgument -ArgumentText $inner)) {
            if ($entry -match '(?s)^\s*([A-Za-z_][\w\.]*)\s*\((.*)\)\s*$') {
                $name = ($Matches[1] -split '\.')[-1] -replace 'Attribute$', ''
                [pscustomobject]@{ Name = $name; ArgumentText = $Matches[2] }
            }
            elseif ($entry -match '(?s)^\s*([A-Za-z_][\w\.]*)\s*$') {
                $name = ($Matches[1] -split '\.')[-1] -replace 'Attribute$', ''
                [pscustomobject]@{ Name = $name; ArgumentText = '' }
            }
        }

        $index = $closeIndex + 1
    }
}