PowerShell / PSScriptAnalyzer

Download ScriptAnalyzer from PowerShellGallery
https://www.powershellgallery.com/packages/PSScriptAnalyzer/
MIT License
1.85k stars 373 forks source link

Unable to suppress rules in an advanced function when there is no Param block #1930

Closed fourpastmidnight closed 1 year ago

fourpastmidnight commented 1 year ago

Overview

I was able to successfully suppress the violation of PSUseSingularNouns for two functions within a script using the SuppressMessageAttribute. But a third function will not suppress even with the presence of the SuppressMessageAttribute.

Steps to reproduce

  1. Use a PSScriptAnalyzer configuration file with the following contents:

    @{
        Severity = @('Warning', 'Error')
        IncludeRules = @('PSUseSingularNouns')
    }
  2. Use the following contents for a script file to be analyzed with PSScriptAnalyzer.

    # SUCCESS: The rule flags this, but there's explicit code looking for the word 'data' and such a
    # violation is suppressed. However, without the SuppressMessageAttribute, this function is flagged by
    # PSScriptAnalyzer v1.21.0
    function Get-QueueMessageMetadata {
        [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
        [CmdletBinding()]
        Param ( <# Doesn't matter #> )
        Begin { }
    }
    
    # SUCCESS: The rule flags this, but the SuppressMessageAttribute successfully suppresses a diagnostic
    # from being returned.
    function Start-EmptyQueuesAndStopServices {
        [CmdletBinding()]
        [Diagnostics.CodeAanalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
        Param ( <# Doesn't matter #> )
        Begin { }
    }
    
    # ALWAYS FLAGS: I tried four variations of using SuppressMessageAttribute and it WILL NOT suppress!
    #[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Target='Get-QueuesWithMessages')]
    #[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns, '', Target='Get-QueuesWithMessages', Scope='Function')]
    #[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns, '', Target='Get-QueuesWithMessages', Scope='Script')]
    function Get-QueuesWithMesasges {
        #[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Target='Get-QueuesWithMessages')]
        [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
        [CmdletBinding()]
        Begin { }
    }
  3. PSScriptAnalyzer returns a violation of the PSUseSingularNouns rule for the last function in the script file above even though a suppression attribute exists. And of course, I tried several variations with no success.

It took me quite a while to spot this since I'm so familiar with the code I'm working on. But notice that the last function DOES NOT declare a Param ( ) block! Once I added an empty Param ( ) block, no more violations were reported.

I do not believe this is expected behavior because unless your function requires parameters (or simply, doesn't name them), none of my resources say that the Param ( ) block is required for advanced functions. Additionally, it looks like PowerShell may be parsing the SuppressMessageAttribute as decorating the Begin { } block instead of being a "decorator of" the enclosing function.

As an aside, it has always bothered me how attributes for functions are placed inside the function declaration, instead of directly above it as it's done in C#. So, if anything, it looks like a PowerShell parser ambiguity here. Does the attribute decorate the enclosing function, or the block within? Adding a Param ( ) block to the function, it would seem, resolves the parser ambiguity. I even tried putting the [CmdletBinding()] attribute declaration below the SuppressMessageAttribute declaration, but the unexpected behavior remains.

Now that I think about this, this issue probably does not belong on the PSScriptAnalyzer repo, but with the PowerShell repo, proper. Let me know if I should post this issue on that repository instead, as it's most likely that the PSScriptAnalyzer team can do anything to PSScriptAnalyzer to "fix" this.

Expected behavior

I expect that if my function does not need parameters, the Param ( ) block can be omitted and that the SuppressMessageAttribute would still be associated with the function as it is for all other PowerShell functions so that the PSUseSingularNouns rule is suppressed on the affected function.

Actual behavior

PSScriptAnalyzer does not suppress this rule and emits a diagnostic record for the affected function. I suspect the attribute is not "decorating" the right block during parsing--being applied to the Begin { } block rather than the enclosing function.

Environment data

> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      7.4.0-preview.3
PSEdition                      Core
GitCommitId                    7.4.0-preview.3
OS                             Microsoft Windows 10.0.19042
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

> (Get-Module -ListAvailable PSScriptAnalyzer).Version | ForEach-Object { $_.ToString() }

1.21.0
SydneyhSmith commented 1 year ago

Thanks @fourpastmidnight for taking the time to spend this issue, and for providing such detailed information. PowerShell (rather than PSSA) does the parsing, and applies the attribute accordingly, because there is no param statement what you are seeing is the PowerShell expected behavior. Unfortunately this is not something we can solve for right now on the PSSA level-- PowerShell would need to solve this. The workaround is to use an empty param block (as you did). Thanks!

fourpastmidnight commented 1 year ago

@SydneyhSmith Thanks for confirming that this is a PowerShell parsing "issue" (at least, a parsing ambiguity with respect to how attributes are applied in PowerShell).