Skip to content

Duplicate "Cannot find any DiagnosticRecord with the Rule Suppression ID" errors with -Fix and class-based DSC resources #2177

@tablackburn

Description

@tablackburn

Splitting this out of #2155 per @bergmeister's request. The OP's original reproducer on that issue doesn't reproduce on current main (confirmed by @liamjpeters and myself), but investigating it turned up two real code paths that emit the unapplied-suppression error more than once. Filing here for clean history.

Verified on main at a143b9f (v1.25.0 post-release).

Bug 1 — -Fix emits the unapplied-suppression error twice

Repro

# Written with normalized LF endings — Fix's formatter throws on mixed CRLF/LF,
# which can hide this repro if you paste a here-string at the prompt.
$content = @(
    'function Test-Function1 {'
    "    [System.Diagnostics.CodeAnalysis.SuppressMessage('PSAvoidUsingWriteHost','NonExistentID123')]"
    '    param() ; Write-Host ''x'''
    '}'
) -join "`n"
[System.IO.File]::WriteAllText("$PWD/poc.ps1", $content + "`n")

Invoke-ScriptAnalyzer -Path ./poc.ps1 -Fix -ErrorVariable e *> $null
$e.Count   # Actual: 2 — Expected: 1

Output:

Suppression Message Attribute error at line 2 in script definition : Cannot find any DiagnosticRecord with the Rule Suppression ID NonExistentID123.
Suppression Message Attribute error at line 2 in poc.ps1 : Cannot find any DiagnosticRecord with the Rule Suppression ID NonExistentID123.

Root cause

AnalyzeAndFixPath (Engine/ScriptAnalyzer.cs:1421) calls Fix() — which internally loops AnalyzeScriptDefinition and emits the unapplied-suppression error on each iteration (Helper.cs:1347) — and then calls AnalyzeFile(), which emits it again. Each call rebuilds ruleSuppressions from scratch. With formatter rules that need more than one Fix pass, the count grows by one per extra iteration.

Bug 2 — Class-based DSC resource in module layout duplicates all DSC diagnostics

Repro

Layout:

./DSCResources/MyRes/MyRes.psm1
./DSCResources/MyRes/MyRes.schema.mof   # empty file is fine

MyRes.psm1 (LF endings):

[System.Diagnostics.CodeAnalysis.SuppressMessage('PSDSCStandardDSCFunctionsInResource', 'BadDscId')]
[DscResource()]
class MyRes {
    [DscProperty(Key)] [string] $Name
    [MyRes] Get() { return $this }
}
Invoke-ScriptAnalyzer -Path ./DSCResources/MyRes/MyRes.psm1 -ErrorVariable e *> $null
$e.Count   # Actual: 2 identical errors — Expected: 1

PSDSCStandardDSCFunctionsInResource diagnostics are also duplicated in the output (5 copies in my run instead of the expected set).

Root cause

In AnalyzeSyntaxTree (Engine/ScriptAnalyzer.cs), both branches in the DSC region fire for this file:

  • IsDscResourceClassBased(scriptAst) check at line 2204 — matches because the AST contains [DscResource()].
  • IsDscResourceModule(filePath) check at line 2237 — matches because the file sits in DSCResources/<name>/<name>.psm1 next to a .schema.mof.

Each DSC rule's SuppressRule() runs twice against the same ruleSuppressions dict, so both the rule diagnostics and the unapplied-suppression error get duplicated.

Suggested fixes

  • Bug 1: de-dupe the unapplied-suppression errors across Fix() iterations and the final AnalyzeFile() pass — or only emit them from the terminal analysis call, not from intermediate Fix passes.
  • Bug 2: make the two DSC branches mutually exclusive (skip the module-layout branch when the class-based branch already handled the AST).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions