Public/Set-RuleSuppression.ps1
using namespace Microsoft.PowerShell.EditorServices using namespace Microsoft.PowerShell.EditorServices.Extensions using namespace System.Collections.Generic using namespace System.Management.Automation.Language function Set-RuleSuppression { <# .EXTERNALHELP EditorServicesCommandSuite-help.xml #> [EditorCommand(DisplayName='Suppress Closest Analyzer Rule Violation')] [CmdletBinding()] param( [Parameter(Position=0, ValueFromPipeline)] [ValidateNotNullOrEmpty()] [System.Management.Automation.Language.Ast[]] $Ast = (Find-Ast -AtCursor) ) begin { function GetAttributeTarget { param( [System.Management.Automation.Language.Ast] $SubjectAst ) if (-not $Ast) { return } $splat = @{ Ast = $SubjectAst First = $true } # Attribute can go right on top of variable expressions. if ($SubjectAst.VariablePath -or (Find-Ast @splat -Ancestor { $_.VariablePath })) { return $SubjectAst } $splat.FilterScript = { $PSItem.ParamBlock } # This isn't a variable expression so we need to find the closest param block. if ($scriptBlockAst = Find-Ast @splat -Ancestor) { return $scriptBlockAst.ParamBlock } # No param block anywhere in it's ancestry so try to find a physically close param block if ($scriptBlockAst = Find-Ast @splat -Before) { return $scriptBlockAst.ParamBlock } # Check if part of a method definition in a class. $splat.FilterScript = { $PSItem -is [FunctionMemberAst] } if ($methodAst = Find-Ast @splat -Ancestor) { return $methodAst } # Check if part of a class period. $splat.FilterScript = { $PSItem -is [TypeDefinitionAst] } if ($classAst = Find-Ast @splat -Ancestor) { return $classAst} # Give up and just create it above the original ast. return $SubjectAst } $astList = [List[Ast]]::new() } process { if ($Ast) { $astList.AddRange($Ast) } } end { $context = $psEditor.GetEditorContext() $scriptFile = $context.CurrentFile.GetType(). GetField('scriptFile', 60). GetValue($context.CurrentFile) $markers = Invoke-ScriptAnalyzer -Path $Context.CurrentFile.Path $extentsToSuppress = [List[psobject]]::new() foreach ($aAst in $astList) { # Get the closest valid ast that can be assigned an attribute. $target = GetAttributeTarget $aAst foreach ($marker in $markers) { $isWithinMarker = $aAst.Extent.StartOffset -ge $marker.Extent.StartOffset -and $aAst.Extent.EndOffset -le $marker.Extent.EndOffset if (-not $isWithinMarker) { continue } # FilePosition gives us some nice methods for indent aware navigation. $position = [FilePosition]::new($scriptFile, $target.Extent.StartLineNumber, 1) # GetLineStart puts us at the first non-whitespace character, which we use to get indent level. $indentOffset = ' ' * ($position.GetLineStart().Column - 1) $string = '{0}{1}[System.Diagnostics.CodeAnalysis.SuppressMessage(''{2}'', '''')]' -f [Environment]::NewLine, $indentOffset, $marker.RuleName # AddOffset is line/column based, and will throw if you try to move to a column where # there is no text. $newPosition = $position.AddOffset(-1, ($position.Column - 1) * -1).GetLineEnd() # HACK: Temporary workaround until https://github.com/PowerShell/PowerShellEditorServices/pull/541 # $extent = $position.AddOffset(-1, ($position.Column - 1) * -1).GetLineEnd() | # ConvertTo-ScriptExtent $extent = [Microsoft.PowerShell.EditorServices.FullScriptExtent]::new( $psEditor.GetEditorContext().CurrentFile, [Microsoft.PowerShell.EditorServices.BufferRange]::new( $newPosition.Line, $newPosition.Column, $newPosition.Line, $newPosition.Column)) $extentsToSuppress.Add([PSCustomObject]@{ Extent = $extent Expression = $string }) } } # Need to pass extents all at once to Set-ScriptExtent for position tracking. $extentsToSuppress | Group-Object -Property Expression | ForEach-Object { $PSItem.Group.Extent | Set-ScriptExtent -Text $PSItem.Name } } } |