diff --git a/Engine/Generic/ExternalRule.cs b/Engine/Generic/ExternalRule.cs index 57fe11ac4..55379c3ba 100644 --- a/Engine/Generic/ExternalRule.cs +++ b/Engine/Generic/ExternalRule.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System.Globalization; + namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic { internal class ExternalRule : IExternalRule @@ -20,6 +22,11 @@ public string GetName() return this.name; } + public string GetFullName() + { + return string.Format(CultureInfo.CurrentCulture, "{0}\\{1}", this.GetSourceName(), this.name); + } + public string GetCommonName() { return this.commonName; diff --git a/Engine/Generic/IExternalRule.cs b/Engine/Generic/IExternalRule.cs index 08ac40910..ed18ba548 100644 --- a/Engine/Generic/IExternalRule.cs +++ b/Engine/Generic/IExternalRule.cs @@ -8,6 +8,12 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic /// internal interface IExternalRule : IRule { + /// + /// Adds module name (source name) to handle ducplicate function names in different modules. + /// + /// + string GetFullName(); + /// /// GetParameter: Retrieves AstType parameter /// diff --git a/Engine/Generic/RuleSuppression.cs b/Engine/Generic/RuleSuppression.cs index ac59d43b4..f3bb848ff 100644 --- a/Engine/Generic/RuleSuppression.cs +++ b/Engine/Generic/RuleSuppression.cs @@ -64,7 +64,7 @@ public string RuleName && (ScriptAnalyzer.Instance.TokenRules != null && ScriptAnalyzer.Instance.TokenRules.Count(item => String.Equals(item.GetName(), _ruleName, StringComparison.OrdinalIgnoreCase)) == 0) && (ScriptAnalyzer.Instance.ExternalRules != null - && ScriptAnalyzer.Instance.ExternalRules.Count(item => String.Equals(item.GetName(), _ruleName, StringComparison.OrdinalIgnoreCase)) == 0) + && ScriptAnalyzer.Instance.ExternalRules.Count(item => String.Equals(item.GetFullName(), _ruleName, StringComparison.OrdinalIgnoreCase)) == 0) && (ScriptAnalyzer.Instance.DSCResourceRules != null && ScriptAnalyzer.Instance.DSCResourceRules.Count(item => String.Equals(item.GetName(), _ruleName, StringComparison.OrdinalIgnoreCase)) == 0)) { diff --git a/Engine/Helper.cs b/Engine/Helper.cs index b093d9c0f..35a0a11a1 100644 --- a/Engine/Helper.cs +++ b/Engine/Helper.cs @@ -1419,7 +1419,7 @@ public Tuple, List> SuppressRule( while (startRecord < diagnostics.Count // && diagnostics[startRecord].Extent.StartOffset < ruleSuppression.StartOffset) // && diagnostics[startRecord].Extent.StartLineNumber < ruleSuppression.st) - && offsetArr[startRecord].Item1 < ruleSuppression.StartOffset) + && offsetArr[startRecord] != null && offsetArr[startRecord].Item1 < ruleSuppression.StartOffset) { startRecord += 1; } @@ -1433,7 +1433,7 @@ public Tuple, List> SuppressRule( var curOffset = offsetArr[recordIndex]; //if (record.Extent.EndOffset > ruleSuppression.EndOffset) - if (curOffset.Item2 > ruleSuppression.EndOffset) + if (curOffset != null && curOffset.Item2 > ruleSuppression.EndOffset) { break; } @@ -1489,6 +1489,10 @@ private Tuple[] GetOffsetArray(List diagnostics) for (int k = 0; k < diagnostics.Count; k++) { var ext = diagnostics[k].Extent; + if (ext == null) + { + continue; + } if (ext.StartOffset == 0 && ext.EndOffset == 0) { // check if line and column number correspond to 0 offsets diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index 5b5ccd890..cc8a411a6 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -1206,9 +1206,7 @@ internal IEnumerable GetExternalRecord(Ast ast, Token[] token, // Adds command to run external analyzer rule, like // Measure-CurlyBracket -ScriptBlockAst $ScriptBlockAst - // Adds module name (source name) to handle ducplicate function names in different modules. - string ruleName = string.Format("{0}\\{1}", rule.GetSourceName(), rule.GetName()); - posh.Commands.AddCommand(ruleName); + posh.Commands.AddCommand(rule.GetFullName()); posh.Commands.AddParameter(rule.GetParameter(), token); // Merges results because external analyzer rules may throw exceptions. @@ -1236,9 +1234,7 @@ internal IEnumerable GetExternalRecord(Ast ast, Token[] token, // Adds command to run external analyzer rule, like // Measure-CurlyBracket -ScriptBlockAst $ScriptBlockAst - // Adds module name (source name) to handle ducplicate function names in different modules. - string ruleName = string.Format("{0}\\{1}", rule.GetSourceName(), rule.GetName()); - posh.Commands.AddCommand(ruleName); + posh.Commands.AddCommand(rule.GetFullName()); posh.Commands.AddParameter(rule.GetParameter(), childAst); // Merges results because external analyzer rules may throw exceptions. @@ -2278,7 +2274,7 @@ public IEnumerable AnalyzeSyntaxTree( { if (IsRuleAllowed(exRule)) { - string ruleName = string.Format(CultureInfo.CurrentCulture, "{0}\\{1}", exRule.GetSourceName(), exRule.GetName()); + string ruleName = exRule.GetFullName(); this.outputWriter.WriteVerbose(string.Format(CultureInfo.CurrentCulture, Strings.VerboseRunningMessage, ruleName)); // Ensure that any unhandled errors from Rules are converted to non-terminating errors diff --git a/README.md b/README.md index dc9f5e617..af5750de1 100644 --- a/README.md +++ b/README.md @@ -307,8 +307,6 @@ Suppress violation in `start-bar`, `start-baz` and `start-bam` but not in `start Param() ``` -**Note**: Rule suppression is currently supported only for built-in rules. - **Note**: Parser Errors cannot be suppressed via the `SuppressMessageAttribute` [Back to ToC](#table-of-contents) diff --git a/Tests/Engine/CustomizedRule.tests.ps1 b/Tests/Engine/CustomizedRule.tests.ps1 index 543abc768..7936de266 100644 --- a/Tests/Engine/CustomizedRule.tests.ps1 +++ b/Tests/Engine/CustomizedRule.tests.ps1 @@ -160,6 +160,17 @@ Describe "Test importing correct customized rules" { $violations[0].SuggestedCorrections.Text | Should -Be 'text' $violations[0].SuggestedCorrections.File | Should -Be 'filePath' $violations[0].SuggestedCorrections.Description | Should -Be 'description' + } + + It "can suppress custom rule" { + $script = "[Diagnostics.CodeAnalysis.SuppressMessageAttribute('samplerule\$measure','')]Param()" + $testScriptPath = "TestDrive:\SuppressedCustomRule.ps1" + Set-Content -Path $testScriptPath -Value $script + + $customizedRulePath = Invoke-ScriptAnalyzer -Path $testScriptPath -CustomizedRulePath $directory\samplerule\samplerule.psm1 | + Where-Object { $_.Message -eq $message } + + $customizedRulePath.Count | Should -Be 0 } if (!$testingLibraryUsage) diff --git a/Tests/Engine/samplerule/samplerule.psm1 b/Tests/Engine/samplerule/samplerule.psm1 index 6cf0ea1fd..f740c0ea1 100644 --- a/Tests/Engine/samplerule/samplerule.psm1 +++ b/Tests/Engine/samplerule/samplerule.psm1 @@ -27,13 +27,16 @@ function Measure-RequiresRunAsAdministrator [ValidateNotNullOrEmpty()] [System.Management.Automation.Language.ScriptBlockAst] $testAst - ) + ) + + # The implementation is mocked out for testing purposes only and many properties are deliberately set to null to test if PSSA can cope with it $l=(new-object System.Collections.ObjectModel.Collection["Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.CorrectionExtent"]) $c = (new-object Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.CorrectionExtent 1,2,3,4,'text','filePath','description') $l.Add($c) + $extent = $null $dr = New-Object ` -Typename "Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord" ` - -ArgumentList "This is help",$ast.Extent,$PSCmdlet.MyInvocation.InvocationName,Warning,$null,$null,$l + -ArgumentList "This is help",$extent,$PSCmdlet.MyInvocation.InvocationName,Warning,$null,$null,$l return $dr } Export-ModuleMember -Function Measure* \ No newline at end of file