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).
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
mainata143b9f(v1.25.0 post-release).Bug 1 —
-Fixemits the unapplied-suppression error twiceRepro
Output:
Root cause
AnalyzeAndFixPath(Engine/ScriptAnalyzer.cs:1421) callsFix()— which internally loopsAnalyzeScriptDefinitionand emits the unapplied-suppression error on each iteration (Helper.cs:1347) — and then callsAnalyzeFile(), which emits it again. Each call rebuildsruleSuppressionsfrom scratch. With formatter rules that need more than oneFixpass, the count grows by one per extra iteration.Bug 2 — Class-based DSC resource in module layout duplicates all DSC diagnostics
Repro
Layout:
MyRes.psm1(LF endings):PSDSCStandardDSCFunctionsInResourcediagnostics 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 inDSCResources/<name>/<name>.psm1next to a.schema.mof.Each DSC rule's
SuppressRule()runs twice against the sameruleSuppressionsdict, so both the rule diagnostics and the unapplied-suppression error get duplicated.Suggested fixes
Fix()iterations and the finalAnalyzeFile()pass — or only emit them from the terminal analysis call, not from intermediateFixpasses.