@@ -56,28 +56,28 @@ Describe "RuleSuppressionWithoutScope" {
5656
5757 It " Suppresses rule with extent created using ScriptExtent constructor" {
5858 Invoke-ScriptAnalyzer `
59- - ScriptDefinition $ruleSuppressionAvoidUsernameAndPassword `
60- - IncludeRule " PSAvoidUsingUserNameAndPassWordParams" `
61- - OutVariable ruleViolations `
62- - SuppressedOnly
59+ - ScriptDefinition $ruleSuppressionAvoidUsernameAndPassword `
60+ - IncludeRule " PSAvoidUsingUserNameAndPassWordParams" `
61+ - OutVariable ruleViolations `
62+ - SuppressedOnly
6363 $ruleViolations.Count | Should - Be 1
64- }
64+ }
6565 }
6666
6767 Context " Script" {
6868 It " Does not raise violations" {
69- $suppression = $violations | Where-Object {$_.RuleName -eq " PSProvideCommentHelp" }
69+ $suppression = $violations | Where-Object { $_.RuleName -eq " PSProvideCommentHelp" }
7070 $suppression.Count | Should - Be 0
71- $suppression = $violationsUsingScriptDefinition | Where-Object {$_.RuleName -eq " PSProvideCommentHelp" }
71+ $suppression = $violationsUsingScriptDefinition | Where-Object { $_.RuleName -eq " PSProvideCommentHelp" }
7272 $suppression.Count | Should - Be 0
7373 }
7474 }
7575
7676 Context " RuleSuppressionID" {
7777 It " Only suppress violations for that ID" {
78- $suppression = $violations | Where-Object {$_.RuleName -eq " PSAvoidDefaultValueForMandatoryParameter" }
78+ $suppression = $violations | Where-Object { $_.RuleName -eq " PSAvoidDefaultValueForMandatoryParameter" }
7979 $suppression.Count | Should - Be 1
80- $suppression = $violationsUsingScriptDefinition | Where-Object {$_.RuleName -eq " PSAvoidDefaultValueForMandatoryParameter" }
80+ $suppression = $violationsUsingScriptDefinition | Where-Object { $_.RuleName -eq " PSAvoidDefaultValueForMandatoryParameter" }
8181 $suppression.Count | Should - Be 1
8282 }
8383
@@ -93,10 +93,10 @@ function SuppressPwdParam()
9393}
9494'@
9595 Invoke-ScriptAnalyzer `
96- - ScriptDefinition $ruleSuppressionIdAvoidPlainTextPassword `
97- - IncludeRule " PSAvoidUsingPlainTextForPassword" `
98- - OutVariable ruleViolations `
99- - SuppressedOnly
96+ - ScriptDefinition $ruleSuppressionIdAvoidPlainTextPassword `
97+ - IncludeRule " PSAvoidUsingPlainTextForPassword" `
98+ - OutVariable ruleViolations `
99+ - SuppressedOnly
100100 $ruleViolations.Count | Should - Be 1
101101 }
102102
@@ -246,8 +246,165 @@ function MyFunc
246246 }
247247 }
248248
249+ Context " RuleSuppressionID with named arguments" {
250+ It " Should work with named argument syntax" {
251+ $scriptWithNamedArgs = @'
252+ function SuppressPasswordParam()
253+ {
254+ [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute(RuleName="PSAvoidUsingPlainTextForPassword", RuleSuppressionId="password1")]
255+ param(
256+ [string] $password1,
257+ [string] $password2
258+ )
259+ }
260+ '@
261+
262+ $diagnostics = Invoke-ScriptAnalyzer `
263+ - ScriptDefinition $scriptWithNamedArgs `
264+ - IncludeRule " PSAvoidUsingPlainTextForPassword"
265+ $suppressions = Invoke-ScriptAnalyzer `
266+ - ScriptDefinition $scriptWithNamedArgs `
267+ - IncludeRule " PSAvoidUsingPlainTextForPassword" `
268+ - SuppressedOnly
269+
270+ # There should be one unsuppressed diagnostic (password2) and one suppressed diagnostic (password1)
271+ $diagnostics | Should - HaveCount 1
272+ $diagnostics [0 ].RuleName | Should - BeExactly " PSAvoidUsingPlainTextForPassword"
273+ $diagnostics [0 ].RuleSuppressionID | Should - BeExactly " password2"
274+
275+ $suppressions | Should - HaveCount 1
276+ $suppressions [0 ].RuleName | Should - BeExactly " PSAvoidUsingPlainTextForPassword"
277+ $suppressions [0 ].RuleSuppressionID | Should - BeExactly " password1"
278+ }
279+
280+ It " Should work with mixed positional and named argument syntax" {
281+ $scriptWithMixedArgs = @'
282+ function SuppressPasswordParam()
283+ {
284+ [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", Scope="Function")]
285+ param(
286+ [string] $password1,
287+ [string] $password2
288+ )
289+ }
290+ '@
291+
292+ $diagnostics = Invoke-ScriptAnalyzer `
293+ - ScriptDefinition $scriptWithMixedArgs `
294+ - IncludeRule " PSAvoidUsingPlainTextForPassword"
295+
296+ # All violations should be suppressed since there's no RuleSuppressionID filtering
297+ $diagnostics | Should - HaveCount 0
298+ }
299+
300+ It " Should work with custom rule from issue #1686 comment" {
301+ # This test recreates the exact scenario from GitHub issue 1686 comment
302+ # with a custom rule that populates RuleSuppressionID for targeted suppression
303+
304+ # Custom rule module that creates violations with specific RuleSuppressionIDs
305+ $customRuleScript = @'
306+ function Measure-AvoidFooBarCommand {
307+ [CmdletBinding()]
308+ [OutputType([Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]
309+ param(
310+ [Parameter(Mandatory)]
311+ [ValidateNotNullOrEmpty()]
312+ [System.Management.Automation.Language.ScriptBlockAst]
313+ $ScriptBlockAst
314+ )
315+
316+ $results = @()
317+
318+ # Find all command expressions
319+ $commandAsts = $ScriptBlockAst.FindAll({
320+ param($node)
321+ $node -is [System.Management.Automation.Language.CommandAst]
322+ }, $true)
323+
324+ foreach ($commandAst in $commandAsts) {
325+ $commandName = $commandAst.GetCommandName()
326+ if ($commandName -match '^(Get-FooBar|Set-FooBar)$') {
327+ # Create a diagnostic with the command name as RuleSuppressionID
328+ $result = [Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord]::new(
329+ "Avoid using $commandName command",
330+ $commandAst.Extent,
331+ 'Measure-AvoidFooBarCommand',
332+ 'Warning',
333+ $null,
334+ $commandName # This becomes the RuleSuppressionID
335+ )
336+ $results += $result
337+ }
338+ }
339+
340+ return $results
341+ }
342+
343+ Export-ModuleMember -Function Measure-AvoidFooBarCommand
344+ '@
345+
346+ # Script that uses the custom rule with targeted suppression
347+ $scriptWithCustomRuleSuppression = @'
348+ [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('Measure-AvoidFooBarCommand', RuleSuppressionId = 'Get-FooBar', Scope = 'Function', Target = 'Allow-GetFooBar')]
349+ param()
350+
351+ function Test-BadCommands {
352+ Get-FooBar # Line 6 - Should NOT be suppressed (wrong function)
353+ Set-FooBar # Line 7 - Should NOT be suppressed (different RuleSuppressionID)
354+ }
355+
356+ function Allow-GetFooBar {
357+ Get-FooBar # Line 11 - Should be suppressed (matches RuleSuppressionId and Target)
358+ Set-FooBar # Line 12 - Should NOT be suppressed (different RuleSuppressionID)
359+ }
360+ '@
361+
362+ # Save custom rule to temporary file
363+ $customRuleFile = [System.IO.Path ]::GetTempFileName()
364+ $customRuleModuleFile = [System.IO.Path ]::ChangeExtension($customRuleFile , ' .psm1' )
365+ Set-Content - Path $customRuleModuleFile - Value $customRuleScript
366+
367+ try
368+ {
369+ # Check suppressed violations - this is the key test for our fix
370+ $suppressions = Invoke-ScriptAnalyzer `
371+ - ScriptDefinition $scriptWithCustomRuleSuppression `
372+ - CustomRulePath $customRuleModuleFile `
373+ - SuppressedOnly `
374+ - ErrorAction SilentlyContinue
375+
376+ # The core functionality: RuleSuppressionID with named arguments should work for custom rules
377+ # We should have at least one suppressed Get-FooBar violation
378+ $suppressions | Should -Not - BeNullOrEmpty - Because " RuleSuppressionID named arguments should work for custom rules"
379+
380+ $getFooBarSuppressions = $suppressions | Where-Object { $_.RuleSuppressionID -eq ' Get-FooBar' }
381+ $getFooBarSuppressions | Should -Not - BeNullOrEmpty - Because " Get-FooBar should be suppressed based on RuleSuppressionID"
382+
383+ # Verify the suppression occurred in the right function (Allow-GetFooBar)
384+ $getFooBarSuppressions | Should -Not - BeNullOrEmpty
385+ $getFooBarSuppressions [0 ].RuleName | Should - BeExactly ' Measure-AvoidFooBarCommand'
386+
387+ # Get unsuppressed violations to verify selective suppression
388+ $diagnostics = Invoke-ScriptAnalyzer `
389+ - ScriptDefinition $scriptWithCustomRuleSuppression `
390+ - CustomRulePath $customRuleModuleFile `
391+ - ErrorAction SilentlyContinue
392+
393+ # Should still have violations for Set-FooBar (different RuleSuppressionID) and Get-FooBar in wrong function
394+ $setFooBarViolations = $diagnostics | Where-Object { $_.RuleSuppressionID -eq ' Set-FooBar' }
395+ $setFooBarViolations | Should -Not - BeNullOrEmpty - Because " Set-FooBar should not be suppressed (different RuleSuppressionID)"
396+
397+ }
398+ finally
399+ {
400+ Remove-Item - Path $customRuleModuleFile - ErrorAction SilentlyContinue
401+ Remove-Item - Path $customRuleFile - ErrorAction SilentlyContinue
402+ }
403+ }
404+ }
405+
249406 Context " Rule suppression within DSC Configuration definition" {
250- It " Suppresses rule" - skip :($IsLinux -or $IsMacOS -or ($PSVersionTable.PSVersion.Major -lt 5 )) {
407+ It " Suppresses rule" - Skip :($IsLinux -or $IsMacOS -or ($PSVersionTable.PSVersion.Major -lt 5 )) {
251408 $suppressedRule = Invoke-ScriptAnalyzer - ScriptDefinition $ruleSuppressionInConfiguration - SuppressedOnly
252409 $suppressedRule.Count | Should - Be 1
253410 }
@@ -281,9 +438,9 @@ function MyFunc
281438Describe " RuleSuppressionWithScope" {
282439 Context " FunctionScope" {
283440 It " Does not raise violations" {
284- $suppression = $violations | Where-Object {$_.RuleName -eq " PSAvoidUsingPositionalParameters" }
441+ $suppression = $violations | Where-Object { $_.RuleName -eq " PSAvoidUsingPositionalParameters" }
285442 $suppression.Count | Should - Be 0
286- $suppression = $violationsUsingScriptDefinition | Where-Object {$_.RuleName -eq " PSAvoidUsingPositionalParameters" }
443+ $suppression = $violationsUsingScriptDefinition | Where-Object { $_.RuleName -eq " PSAvoidUsingPositionalParameters" }
287444 $suppression.Count | Should - Be 0
288445 }
289446 }
@@ -353,4 +510,4 @@ Describe "RuleSuppressionWithScope" {
353510 $suppressed.Count | Should - Be 1
354511 }
355512 }
356- }
513+ }
0 commit comments