Skip to content

Commit 48e1d42

Browse files
committed
Enhance suppression error handling in ScriptAnalyzer and add tests for unapplied suppression errors
1 parent a143b9f commit 48e1d42

3 files changed

Lines changed: 80 additions & 16 deletions

File tree

Engine/ScriptAnalyzer.cs

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1450,7 +1450,7 @@ public IEnumerable<DiagnosticRecord> AnalyzeAndFixPath(string path, Func<string,
14501450
/// <param name="scriptTokens">Parsed tokens of <paramref name="scriptDefinition"/.></param>
14511451
/// <param name="skipVariableAnalysis">Whether variable analysis can be skipped (applicable if rules do not use variable analysis APIs).</param>
14521452
/// <returns></returns>
1453-
public List<DiagnosticRecord> AnalyzeScriptDefinition(string scriptDefinition, out ScriptBlockAst scriptAst, out Token[] scriptTokens, bool skipVariableAnalysis = false)
1453+
public List<DiagnosticRecord> AnalyzeScriptDefinition(string scriptDefinition, out ScriptBlockAst scriptAst, out Token[] scriptTokens, bool skipVariableAnalysis = false, bool emitSuppressionErrors = true)
14541454
{
14551455
scriptAst = null;
14561456
scriptTokens = null;
@@ -1490,7 +1490,7 @@ public List<DiagnosticRecord> AnalyzeScriptDefinition(string scriptDefinition, o
14901490
}
14911491

14921492
// now, analyze the script definition
1493-
diagnosticRecords.AddRange(this.AnalyzeSyntaxTree(scriptAst, scriptTokens, null, skipVariableAnalysis));
1493+
diagnosticRecords.AddRange(this.AnalyzeSyntaxTree(scriptAst, scriptTokens, null, skipVariableAnalysis, emitSuppressionErrors));
14941494
return diagnosticRecords;
14951495
}
14961496

@@ -1549,11 +1549,11 @@ public EditableText Fix(EditableText text, Range range, bool skipParsing, out Ra
15491549
IEnumerable<DiagnosticRecord> records;
15501550
if (skipParsing && previousUnusedCorrections == 0)
15511551
{
1552-
records = AnalyzeSyntaxTree(scriptAst, scriptTokens, String.Empty, skipVariableAnalysis);
1552+
records = AnalyzeSyntaxTree(scriptAst, scriptTokens, String.Empty, skipVariableAnalysis, emitSuppressionErrors: false);
15531553
}
15541554
else
15551555
{
1556-
records = AnalyzeScriptDefinition(text.ToString(), out scriptAst, out scriptTokens, skipVariableAnalysis);
1556+
records = AnalyzeScriptDefinition(text.ToString(), out scriptAst, out scriptTokens, skipVariableAnalysis, emitSuppressionErrors: false);
15571557
}
15581558
var corrections = records
15591559
.Select(r => r.SuggestedCorrections)
@@ -1986,17 +1986,21 @@ bool IsRuleAllowed(IRule rule)
19861986
private Tuple<List<SuppressedRecord>, List<DiagnosticRecord>> SuppressRule(
19871987
string ruleName,
19881988
Dictionary<string, List<RuleSuppression>> ruleSuppressions,
1989-
List<DiagnosticRecord> ruleDiagnosticRecords)
1989+
List<DiagnosticRecord> ruleDiagnosticRecords,
1990+
bool emitSuppressionErrors = true)
19901991
{
19911992
List<ErrorRecord> suppressRuleErrors;
19921993
var records = Helper.Instance.SuppressRule(
19931994
ruleName,
19941995
ruleSuppressions,
19951996
ruleDiagnosticRecords,
19961997
out suppressRuleErrors);
1997-
foreach (var error in suppressRuleErrors)
1998+
if (emitSuppressionErrors)
19981999
{
1999-
this.outputWriter.WriteError(error);
2000+
foreach (var error in suppressRuleErrors)
2001+
{
2002+
this.outputWriter.WriteError(error);
2003+
}
20002004
}
20012005
return records;
20022006
}
@@ -2014,13 +2018,15 @@ private Tuple<List<SuppressedRecord>, List<DiagnosticRecord>> SuppressRule(
20142018
/// <returns>Returns a tuple of suppressed and diagnostic records</returns>
20152019
private Tuple<List<SuppressedRecord>, List<DiagnosticRecord>> SuppressRule(
20162020
Dictionary<string, List<RuleSuppression>> ruleSuppressions,
2017-
DiagnosticRecord ruleDiagnosticRecord
2021+
DiagnosticRecord ruleDiagnosticRecord,
2022+
bool emitSuppressionErrors = true
20182023
)
20192024
{
20202025
return SuppressRule(
20212026
ruleDiagnosticRecord.RuleName,
20222027
ruleSuppressions,
2023-
new List<DiagnosticRecord> { ruleDiagnosticRecord });
2028+
new List<DiagnosticRecord> { ruleDiagnosticRecord },
2029+
emitSuppressionErrors);
20242030
}
20252031

20262032
/// <summary>
@@ -2037,7 +2043,8 @@ public IEnumerable<DiagnosticRecord> AnalyzeSyntaxTree(
20372043
ScriptBlockAst scriptAst,
20382044
Token[] scriptTokens,
20392045
string filePath,
2040-
bool skipVariableAnalysis = false)
2046+
bool skipVariableAnalysis = false,
2047+
bool emitSuppressionErrors = true)
20412048
{
20422049
Dictionary<string, List<RuleSuppression>> ruleSuppressions = new Dictionary<string,List<RuleSuppression>>();
20432050
ConcurrentBag<DiagnosticRecord> diagnostics = new ConcurrentBag<DiagnosticRecord>();
@@ -2117,7 +2124,10 @@ public IEnumerable<DiagnosticRecord> AnalyzeSyntaxTree(
21172124
ruleSuppressions,
21182125
ruleRecords,
21192126
out suppressRuleErrors);
2120-
result.AddRange(suppressRuleErrors);
2127+
if (emitSuppressionErrors)
2128+
{
2129+
result.AddRange(suppressRuleErrors);
2130+
}
21212131
foreach (var record in records.Item2)
21222132
{
21232133
diagnostics.Add(record);
@@ -2177,7 +2187,7 @@ public IEnumerable<DiagnosticRecord> AnalyzeSyntaxTree(
21772187
try
21782188
{
21792189
var ruleRecords = tokenRule.AnalyzeTokens(scriptTokens, filePath).ToList();
2180-
var records = SuppressRule(tokenRule.GetName(), ruleSuppressions, ruleRecords);
2190+
var records = SuppressRule(tokenRule.GetName(), ruleSuppressions, ruleRecords, emitSuppressionErrors);
21812191
foreach (var record in records.Item2)
21822192
{
21832193
diagnostics.Add(record);
@@ -2215,7 +2225,7 @@ public IEnumerable<DiagnosticRecord> AnalyzeSyntaxTree(
22152225
try
22162226
{
22172227
var ruleRecords = dscResourceRule.AnalyzeDSCClass(scriptAst, filePath).ToList();
2218-
var records = SuppressRule(dscResourceRule.GetName(), ruleSuppressions, ruleRecords);
2228+
var records = SuppressRule(dscResourceRule.GetName(), ruleSuppressions, ruleRecords, emitSuppressionErrors);
22192229
foreach (var record in records.Item2)
22202230
{
22212231
diagnostics.Add(record);
@@ -2234,7 +2244,7 @@ public IEnumerable<DiagnosticRecord> AnalyzeSyntaxTree(
22342244
}
22352245

22362246
// Check if the supplied artifact is indeed part of the DSC resource
2237-
if (!filePathIsNullOrWhiteSpace && Helper.Instance.IsDscResourceModule(filePath))
2247+
else if (!filePathIsNullOrWhiteSpace && Helper.Instance.IsDscResourceModule(filePath))
22382248
{
22392249
// Run all DSC Rules
22402250
foreach (IDSCResourceRule dscResourceRule in this.DSCResourceRules)
@@ -2248,7 +2258,7 @@ public IEnumerable<DiagnosticRecord> AnalyzeSyntaxTree(
22482258
try
22492259
{
22502260
var ruleRecords = dscResourceRule.AnalyzeDSCResource(scriptAst, filePath).ToList();
2251-
var records = SuppressRule(dscResourceRule.GetName(), ruleSuppressions, ruleRecords);
2261+
var records = SuppressRule(dscResourceRule.GetName(), ruleSuppressions, ruleRecords, emitSuppressionErrors);
22522262
foreach (var record in records.Item2)
22532263
{
22542264
diagnostics.Add(record);
@@ -2297,7 +2307,7 @@ public IEnumerable<DiagnosticRecord> AnalyzeSyntaxTree(
22972307

22982308
foreach (var ruleRecord in this.GetExternalRecord(scriptAst, scriptTokens, exRules.ToArray(), filePath))
22992309
{
2300-
var records = SuppressRule(ruleSuppressions, ruleRecord);
2310+
var records = SuppressRule(ruleSuppressions, ruleRecord, emitSuppressionErrors);
23012311
foreach (var record in records.Item2)
23022312
{
23032313
diagnostics.Add(record);

Tests/Engine/RuleSuppression.tests.ps1

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,30 @@ function MyFunc
244244
$suppErr.TargetObject.RuleSuppressionID | Should -BeExactly "banana"
245245
}
246246
}
247+
248+
It "Issues one unapplied suppression error when -Fix reanalyzes a file" {
249+
$scriptPath = Join-Path $TestDrive 'SuppressionFix.ps1'
250+
$script = @(
251+
'function Test-Function1 {'
252+
" [System.Diagnostics.CodeAnalysis.SuppressMessage('PSAvoidUsingWriteHost','NonExistentID123')]"
253+
' param() ; Write-Host ''x'''
254+
'}'
255+
) -join "`n"
256+
257+
[System.IO.File]::WriteAllText($scriptPath, $script + "`n")
258+
259+
$diagnostics = Invoke-ScriptAnalyzer `
260+
-Path $scriptPath `
261+
-Fix `
262+
-ErrorVariable fixErr `
263+
-ErrorAction SilentlyContinue
264+
265+
$diagnostics | Should -HaveCount 1
266+
$diagnostics[0].RuleName | Should -BeExactly 'PSAvoidUsingWriteHost'
267+
$fixErr | Should -HaveCount 1
268+
$fixErr[0].TargetObject.RuleName | Should -BeExactly 'PSAvoidUsingWriteHost'
269+
$fixErr[0].TargetObject.RuleSuppressionID | Should -BeExactly 'NonExistentID123'
270+
}
247271
}
248272

249273
Context "RuleSuppressionID with named arguments" {

Tests/Rules/UseDSCResourceFunctions.tests.ps1

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,34 @@ Describe "StandardDSCFunctionsInClass" {
4646
$noClassViolations.Count | Should -Be 0
4747
}
4848
}
49+
50+
Context "When a class-based DSC resource is also in DSC resource module layout" {
51+
It "does not duplicate the unapplied suppression error" {
52+
$resourceRoot = Join-Path $TestDrive 'DSCResources'
53+
$resourceDir = Join-Path $resourceRoot 'MyRes'
54+
$resourcePath = Join-Path $resourceDir 'MyRes.psm1'
55+
$schemaPath = Join-Path $resourceDir 'MyRes.schema.mof'
56+
57+
New-Item -ItemType Directory -Path $resourceDir -Force | Out-Null
58+
[System.IO.File]::WriteAllText($resourcePath, @'
59+
[System.Diagnostics.CodeAnalysis.SuppressMessage('PSDSCStandardDSCFunctionsInResource', 'BadDscId', Scope='Class', Target='MyRes')]
60+
[DscResource()]
61+
class MyRes {
62+
[DscProperty(Key)] [string] $Name
63+
[MyRes] Get() { return $this }
64+
}
65+
'@.TrimStart() + "`n")
66+
Set-Content -Path $schemaPath -Value ''
67+
68+
Invoke-ScriptAnalyzer `
69+
-Path $resourcePath `
70+
-ErrorVariable dscErr `
71+
-ErrorAction SilentlyContinue |
72+
Out-Null
73+
74+
$dscErr | Should -HaveCount 1
75+
$dscErr[0].TargetObject.RuleName | Should -BeExactly $violationName
76+
$dscErr[0].TargetObject.RuleSuppressionID | Should -BeExactly 'BadDscId'
77+
}
78+
}
4979
}

0 commit comments

Comments
 (0)