Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Engine/Commands/InvokeScriptAnalyzerCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ public string[] IncludeRule
private string[] includeRule;

/// <summary>
/// IncludeRule: Array of the severity types to be enabled.
/// Severity levels of the diagnostic records to include.
/// </summary>
[ValidateSet("Warning", "Error", "Information", "ParseError", IgnoreCase = true)]
[Parameter(Mandatory = false)]
Expand Down
49 changes: 15 additions & 34 deletions Engine/ScriptAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1485,7 +1485,7 @@ public List<DiagnosticRecord> AnalyzeScriptDefinition(string scriptDefinition, o
var relevantParseErrors = RemoveTypeNotFoundParseErrors(errors, out List<DiagnosticRecord> diagnosticRecords);

// Add parse errors first if requested!
if ( relevantParseErrors != null && (severity == null || severity.Contains("ParseError", StringComparer.OrdinalIgnoreCase)))
if ( relevantParseErrors != null )
{
var results = new List<DiagnosticRecord>();
foreach ( ParseError parseError in relevantParseErrors )
Expand All @@ -1505,7 +1505,7 @@ public List<DiagnosticRecord> AnalyzeScriptDefinition(string scriptDefinition, o

// now, analyze the script definition
diagnosticRecords.AddRange(this.AnalyzeSyntaxTree(scriptAst, scriptTokens, null, skipVariableAnalysis, emitSuppressionErrors));
return diagnosticRecords;
return FilterDiagnosticsBySeverity(diagnosticRecords).ToList();
}

/// <summary>
Expand Down Expand Up @@ -1876,7 +1876,7 @@ private IEnumerable<DiagnosticRecord> AnalyzeFile(string filePath)
// no parsing can really be done, but there are other rules to run (specifically encoding).
if ( s_aboutHelpRegex.IsMatch(Path.GetFileName(filePath)) )
{
return diagnosticRecords.Concat(this.AnalyzeSyntaxTree(scriptAst, scriptTokens, filePath));
return FilterDiagnosticsBySeverity(diagnosticRecords.Concat(this.AnalyzeSyntaxTree(scriptAst, scriptTokens, filePath)));
}

// Process script
Expand All @@ -1899,7 +1899,7 @@ private IEnumerable<DiagnosticRecord> AnalyzeFile(string filePath)
IEnumerable<ParseError> relevantParseErrors = RemoveTypeNotFoundParseErrors(errors, out diagnosticRecords);

// First, add all parse errors if they've been requested
if ( relevantParseErrors != null && (severity == null || severity.Contains("ParseError", StringComparer.OrdinalIgnoreCase)))
if ( relevantParseErrors != null )
{
List<DiagnosticRecord> results = new List<DiagnosticRecord>();
foreach ( ParseError parseError in relevantParseErrors )
Expand All @@ -1916,7 +1916,7 @@ private IEnumerable<DiagnosticRecord> AnalyzeFile(string filePath)
diagnosticRecords.AddRange(results);
}

return diagnosticRecords.Concat(this.AnalyzeSyntaxTree(scriptAst, scriptTokens, filePath));
return FilterDiagnosticsBySeverity(diagnosticRecords.Concat(this.AnalyzeSyntaxTree(scriptAst, scriptTokens, filePath)));
}

private bool IsModuleNotFoundError(ParseError error)
Expand All @@ -1925,42 +1925,26 @@ private bool IsModuleNotFoundError(ParseError error)
&& error.ErrorId.Equals("ModuleNotFoundDuringParse", StringComparison.OrdinalIgnoreCase);
}

private bool IsSeverityAllowed(IEnumerable<uint> allowedSeverities, IRule rule)
private bool IsDiagnosticSeverityAllowed(DiagnosticRecord diagnostic)
{
return severity == null
|| (allowedSeverities != null
&& rule != null
&& HasGetSeverity(rule)
&& allowedSeverities.Contains((uint)rule.GetSeverity()));
|| (diagnostic != null
&& severity.Contains(diagnostic.Severity.ToString(), StringComparer.OrdinalIgnoreCase));
}

private static bool IsRuleEnabled(IRule rule)
{
var configurableRule = rule as ConfigurableRule;
return configurableRule == null || configurableRule.Enable;
}

IEnumerable<uint> GetAllowedSeveritiesInInt()
{
return severity != null
? severity.Select(item => (uint)Enum.Parse(typeof(DiagnosticSeverity), item, true))
: null;
}

bool HasMethod<T>(T obj, string methodName)
private IEnumerable<DiagnosticRecord> FilterDiagnosticsBySeverity(IEnumerable<DiagnosticRecord> diagnostics)
{
var type = obj.GetType();
return type.GetMethod(methodName) != null;
return severity == null ? diagnostics : diagnostics.Where(IsDiagnosticSeverityAllowed);
}

bool HasGetSeverity<T>(T obj)
private static bool IsRuleEnabled(IRule rule)
{
return HasMethod<T>(obj, "GetSeverity");
var configurableRule = rule as ConfigurableRule;
return configurableRule == null || configurableRule.Enable;
}

bool IsRuleAllowed(IRule rule)
{
IEnumerable<uint> allowedSeverities = GetAllowedSeveritiesInInt();
bool includeRegexMatch = false;
bool excludeRegexMatch = false;
foreach (Regex include in includeRegexList)
Expand All @@ -1981,12 +1965,8 @@ bool IsRuleAllowed(IRule rule)
}
}

bool helpRule = String.Equals(rule.GetName(), "PSUseUTF8EncodingForHelpFile", StringComparison.OrdinalIgnoreCase);
bool includeSeverity = IsSeverityAllowed(allowedSeverities, rule);

return (includeRule == null || includeRegexMatch)
&& (excludeRule == null || !excludeRegexMatch)
&& IsSeverityAllowed(allowedSeverities, rule)
&& IsRuleEnabled(rule);
}

Expand Down Expand Up @@ -2358,13 +2338,14 @@ public IEnumerable<DiagnosticRecord> AnalyzeSyntaxTree(
// Need to reverse the concurrentbag to ensure that results are sorted in the increasing order of line numbers
IEnumerable<DiagnosticRecord> diagnosticsList = diagnostics.Reverse();

return _suppressionPreference switch
IEnumerable<DiagnosticRecord> selectedDiagnostics = _suppressionPreference switch
{
SuppressionPreference.SuppressedOnly => suppressed.OfType<DiagnosticRecord>(),
SuppressionPreference.Omit => diagnosticsList,
SuppressionPreference.Include => diagnosticsList.Concat(suppressed.OfType<DiagnosticRecord>()),
_ => throw new ArgumentException($"SuppressionPreference has invalid value '{_suppressionPreference}'"),
};
return FilterDiagnosticsBySeverity(selectedDiagnostics);
Comment thread
bergmeister marked this conversation as resolved.
}
}
}
54 changes: 54 additions & 0 deletions Tests/Engine/CustomizedRule.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,60 @@ Describe "Test importing correct customized rules" {
$customizedRulePath.Count | Should -Be 0
}

It "filters custom rule diagnostics by diagnostic severity" {
$customRuleContent = @'
function ReportError {
param (
[System.Management.Automation.Language.CommandAst]$ast
)

if ($ast.GetCommandName() -eq 'Invoke-Something') {
New-Object -Typename 'Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord' `
-ArgumentList 'This is an error', $ast.Extent, 'ReportError', Error, $ast.Extent.File, 'custom-id', $null
}
}
'@
$customRuleScriptPath = Join-Path $TestDrive 'SeverityRule.psm1'
Set-Content -Path $customRuleScriptPath -Value $customRuleContent
(Get-ScriptAnalyzerRule -CustomRulePath $customRuleScriptPath).Severity | Should -Be 'Warning'

$errors = Invoke-ScriptAnalyzer -ScriptDefinition 'Invoke-Something' -CustomRulePath $customRuleScriptPath -Severity Error
$errors.Count | Should -Be 1
$errors[0].Severity | Should -Be 'Error'

$warnings = Invoke-ScriptAnalyzer -ScriptDefinition 'Invoke-Something' -CustomRulePath $customRuleScriptPath -Severity Warning
$warnings.Count | Should -Be 0

$script = @'
function Suppressed {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('ReportError', '', Justification = 'test justification')]
param()
Invoke-Something
}
Invoke-Something
'@
foreach ($mode in 'SuppressedOnly', 'IncludeSuppressed') {
$parameters = @{
ScriptDefinition = $script
CustomRulePath = $customRuleScriptPath
ErrorAction = 'Stop'
$mode = $true
}

$errors = @(Invoke-ScriptAnalyzer @parameters -Severity Error)
$errors.Count | Should -Be $(if ($mode -eq 'SuppressedOnly') { 1 } else { 2 })
$suppressed = @($errors | Where-Object IsSuppressed)
$suppressed.Count | Should -Be 1
$suppressed[0].Severity | Should -Be 'Error'
$suppressed[0].RuleSuppressionID | Should -BeExactly 'custom-id'
$suppressed[0].Suppression | Should -HaveCount 1
$suppressed[0].Suppression[0].Justification | Should -BeExactly 'test justification'

$warnings = @(Invoke-ScriptAnalyzer @parameters -Severity Warning)
$warnings.Count | Should -Be 0
}
}

It "can suppress custom rule with rule name expression '<RuleNameExpression>'" -TestCases @(
@{RuleNameExpression = '$MyInvocation.MyCommand.Name'; RuleName = 'WarningAboutDoSomething' }
@{RuleNameExpression = '$MyInvocation.InvocationName'; RuleName = 'MyCustomRule\WarningAboutDoSomething' }
Expand Down
Loading