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