Private/Get-QAFrameworkClassAttributeBlock.ps1
|
function Get-QAFrameworkClassAttributeBlock { <# .SYNOPSIS Extracts the attribute block that decorates a C# class declaration. .DESCRIPTION Locates the class declaration and walks backwards collecting the contiguous attribute lines above it. Bracket balance is tracked so multi-line attributes (for example a MinVersion spanning several lines) are captured completely. Blank lines and comments between attributes are tolerated. .PARAMETER Path Path to the C# source file. .PARAMETER Content Raw file content. Use instead of -Path when the source is already in memory. .PARAMETER ClassName Optional class name to locate. When omitted the first class declaration wins. .OUTPUTS String containing the attribute block, or an empty string when none is found. #> [CmdletBinding(DefaultParameterSetName = 'Path')] [OutputType([string])] param( [Parameter(Mandatory = $true, ParameterSetName = 'Path')] [string]$Path, [Parameter(Mandatory = $true, ParameterSetName = 'Content')] [AllowEmptyString()] [string]$Content, [string]$ClassName ) if ($PSCmdlet.ParameterSetName -eq 'Path') { if (-not (Test-Path -LiteralPath $Path)) { return '' } $Content = Get-Content -LiteralPath $Path -Raw -ErrorAction Stop } if ([string]::IsNullOrWhiteSpace($Content)) { return '' } $lines = $Content -split '\r?\n' $classPattern = if ($ClassName) { '(^|\s)class\s+' + [regex]::Escape($ClassName) + '(\s|:|<|$)' } else { '(^|\s)class\s+[A-Za-z_]\w*' } $classLineIndex = -1 for ($i = 0; $i -lt $lines.Count; $i++) { $line = $lines[$i] if ($line.TrimStart().StartsWith('//')) { continue } if ($line -match $classPattern) { $classLineIndex = $i break } } if ($classLineIndex -lt 0) { return '' } $collected = [System.Collections.Generic.List[string]]::new() $balance = 0 for ($i = $classLineIndex - 1; $i -ge 0; $i--) { $line = $lines[$i] $trimmed = $line.Trim() if ($balance -eq 0) { # Between attributes: tolerate blank lines and comments, stop at anything else. if ($trimmed -eq '') { continue } if ($trimmed.StartsWith('//') -or $trimmed.StartsWith('*') -or $trimmed.StartsWith('/*')) { continue } if (-not $trimmed.EndsWith(']')) { break } } $collected.Insert(0, $line) # Walking backwards, a ']' opens an attribute and a '[' closes it. foreach ($char in $line.ToCharArray()) { if ($char -eq ']') { $balance++ } elseif ($char -eq '[') { $balance-- } } # A negative balance means we started mid-attribute; treat as balanced. if ($balance -lt 0) { $balance = 0 } } return ($collected -join [Environment]::NewLine) } |