Skip to content

Implement GlobalSuppression for ScriptAnalyzer #278

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 16, 2015
Merged
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
21 changes: 14 additions & 7 deletions Engine/Commands/InvokeScriptAnalyzerCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,19 @@ public SwitchParameter SuppressedOnly
}
private bool suppressedOnly;

#endregion Parameters

#region Private Members

Dictionary<string, List<string>> validationResults = new Dictionary<string, List<string>>();
/// <summary>
/// Returns path to the file that contains user profile for ScriptAnalyzer
/// </summary>
[Parameter(Mandatory = false)]
[ValidateNotNull]
public string Profile
{
get { return profile; }
set { profile = value; }
}
private string profile;

#endregion
#endregion Parameters

#region Overrides

Expand All @@ -144,7 +150,8 @@ protected override void BeginProcessing()
this.includeRule,
this.excludeRule,
this.severity,
this.suppressedOnly);
this.suppressedOnly,
this.profile);
}

/// <summary>
Expand Down
154 changes: 149 additions & 5 deletions Engine/ScriptAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ internal void Initialize<TCmdlet>(
string[] includeRuleNames = null,
string[] excludeRuleNames = null,
string[] severity = null,
bool suppressedOnly = false)
bool suppressedOnly = false,
string profile = null)
where TCmdlet : PSCmdlet, IOutputWriter
{
if (cmdlet == null)
Expand All @@ -115,7 +116,8 @@ internal void Initialize<TCmdlet>(
includeRuleNames,
excludeRuleNames,
severity,
suppressedOnly);
suppressedOnly,
profile);
}

/// <summary>
Expand All @@ -128,7 +130,8 @@ public void Initialize(
string[] includeRuleNames = null,
string[] excludeRuleNames = null,
string[] severity = null,
bool suppressedOnly = false)
bool suppressedOnly = false,
string profile = null)
{
if (runspace == null)
{
Expand All @@ -143,7 +146,8 @@ public void Initialize(
includeRuleNames,
excludeRuleNames,
severity,
suppressedOnly);
suppressedOnly,
profile);
}

private void Initialize(
Expand All @@ -154,7 +158,8 @@ private void Initialize(
string[] includeRuleNames,
string[] excludeRuleNames,
string[] severity,
bool suppressedOnly = false)
bool suppressedOnly = false,
string profile = null)
{
if (outputWriter == null)
{
Expand All @@ -178,6 +183,145 @@ private void Initialize(
this.includeRegexList = new List<Regex>();
this.excludeRegexList = new List<Regex>();

if (!String.IsNullOrWhiteSpace(profile))
{
try
{
profile = path.GetResolvedPSPathFromPSPath(profile).First().Path;
}
catch
{
this.outputWriter.WriteError(new ErrorRecord(new FileNotFoundException(),
string.Format(CultureInfo.CurrentCulture, Strings.FileNotFound, profile),
ErrorCategory.InvalidArgument, this));
}

if (File.Exists(profile))
{
Token[] parserTokens = null;
ParseError[] parserErrors = null;
Ast profileAst = Parser.ParseFile(profile, out parserTokens, out parserErrors);
IEnumerable<Ast> hashTableAsts = profileAst.FindAll(item => item is HashtableAst, false);
foreach (HashtableAst hashTableAst in hashTableAsts)
{
foreach (var kvp in hashTableAst.KeyValuePairs)
{
if (!(kvp.Item1 is StringConstantExpressionAst))
{
this.outputWriter.WriteError(new ErrorRecord(new ArgumentException(),
string.Format(CultureInfo.CurrentCulture, Strings.WrongKeyFormat, kvp.Item1.Extent.StartLineNumber, kvp.Item1.Extent.StartColumnNumber, profile),
ErrorCategory.InvalidArgument, this));
continue;
}

// parse the item2 as array
PipelineAst pipeAst = kvp.Item2 as PipelineAst;
List<string> rhsList = new List<string>();
if (pipeAst != null)
{
ExpressionAst pureExp = pipeAst.GetPureExpression();
if (pureExp is StringConstantExpressionAst)
{
rhsList.Add((pureExp as StringConstantExpressionAst).Value);
}
else
{
ArrayLiteralAst arrayLitAst = pureExp as ArrayLiteralAst;
if (arrayLitAst == null && pureExp is ArrayExpressionAst)
{
ArrayExpressionAst arrayExp = pureExp as ArrayExpressionAst;
// Statements property is never null
if (arrayExp.SubExpression != null)
{
StatementAst stateAst = arrayExp.SubExpression.Statements.First();
if (stateAst != null && stateAst is PipelineAst)
{
CommandBaseAst cmdBaseAst = (stateAst as PipelineAst).PipelineElements.First();
if (cmdBaseAst != null && cmdBaseAst is CommandExpressionAst)
{
CommandExpressionAst cmdExpAst = cmdBaseAst as CommandExpressionAst;
if (cmdExpAst.Expression is StringConstantExpressionAst)
{
rhsList.Add((cmdExpAst.Expression as StringConstantExpressionAst).Value);
}
else
{
arrayLitAst = cmdExpAst.Expression as ArrayLiteralAst;
}
}
}
}
}

if (arrayLitAst != null)
{
foreach (var element in arrayLitAst.Elements)
{
if (!(element is StringConstantExpressionAst))
{
this.outputWriter.WriteError(new ErrorRecord(new ArgumentException(),
string.Format(CultureInfo.CurrentCulture, Strings.WrongValueFormat, element.Extent.StartLineNumber, element.Extent.StartColumnNumber, profile),
ErrorCategory.InvalidArgument, this));
continue;
}

rhsList.Add((element as StringConstantExpressionAst).Value);
}
}
}
}

if (rhsList.Count == 0)
{
this.outputWriter.WriteError(new ErrorRecord(new ArgumentException(),
string.Format(CultureInfo.CurrentCulture, Strings.WrongValueFormat, kvp.Item2.Extent.StartLineNumber, kvp.Item2.Extent.StartColumnNumber, profile),
ErrorCategory.InvalidArgument, this));
break;
}

switch ((kvp.Item1 as StringConstantExpressionAst).Value.ToLower())
{
case "severity":
if (this.severity == null)
{
this.severity = rhsList.ToArray();
}
else
{
this.severity = this.severity.Union(rhsList).ToArray();
}
break;
case "includerules":
if (this.includeRule == null)
{
this.includeRule = rhsList.ToArray();
}
else
{
this.includeRule = this.includeRule.Union(rhsList).ToArray();
}
break;
case "excluderules":
if (this.excludeRule == null)
{
this.excludeRule = rhsList.ToArray();
}
else
{
this.excludeRule = this.excludeRule.Union(rhsList).ToArray();
}
break;
default:
this.outputWriter.WriteError(new ErrorRecord(new ArgumentException(),
string.Format(CultureInfo.CurrentCulture, Strings.WrongKey, kvp.Item1.Extent.StartLineNumber, kvp.Item1.Extent.StartColumnNumber, profile),
ErrorCategory.InvalidArgument, this));
break;
}
}
}
}
}

//Check wild card input for the Include/ExcludeRules and create regex match patterns
if (this.includeRule != null)
{
Expand Down
29 changes: 28 additions & 1 deletion Engine/Strings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions Engine/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -192,4 +192,13 @@
<data name="RuleSuppressionIDError" xml:space="preserve">
<value>Cannot find any DiagnosticRecord with the Rule Suppression ID {0}.</value>
</data>
<data name="WrongKey" xml:space="preserve">
<value>{0} is not a valid key in the profile hashtable: line {0} column {1} in file {2}</value>
</data>
<data name="WrongKeyFormat" xml:space="preserve">
<value>Key in the profile hashtable should be a string: line {0} column {1} in file {2}</value>
</data>
<data name="WrongValueFormat" xml:space="preserve">
<value>Value in the profile hashtable should be a string or an array of strings: line {0} column {1} in file {2}</value>
</data>
</root>
7 changes: 7 additions & 0 deletions Tests/Engine/GlobalSuppression.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
$a

gcm "blah"

$a = "//internal"

Get-Alias -ComputerName dfd
49 changes: 49 additions & 0 deletions Tests/Engine/GlobalSuppression.test.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Check if PSScriptAnalyzer is already loaded so we don't
# overwrite a test version of Invoke-ScriptAnalyzer by
# accident
if (!(Get-Module PSScriptAnalyzer) -and !$testingLibraryUsage)
{
Import-Module -Verbose PSScriptAnalyzer
}

$directory = Split-Path -Parent $MyInvocation.MyCommand.Path
$violations = Invoke-ScriptAnalyzer $directory\GlobalSuppression.ps1
$suppression = Invoke-ScriptAnalyzer $directory\GlobalSuppression.ps1 -Profile $directory\Profile.ps1

Describe "GlobalSuppression" {
Context "Exclude Rule" {
It "Raises 1 violation for uninitialized variable and 1 for cmdlet alias" {
$withoutProfile = $violations | Where-Object { $_.RuleName -eq "PSAvoidUsingCmdletAliases" -or $_.RuleName -eq "PSAvoidUninitializedVariable" }
$withoutProfile.Count | Should Be 2
}

It "Does not raise any violations for uninitialized variable and cmdlet alias with profile" {
$withProfile = $suppression | Where-Object { $_.RuleName -eq "PSAvoidUsingCmdletAliases" -or $_.RuleName -eq "PSAvoidUninitializedVariable" }
$withProfile.Count | Should be 0
}
}

Context "Include Rule" {
It "Raises 1 violation for computername hard-coded" {
$withoutProfile = $violations | Where-Object { $_.RuleName -eq "PSAvoidUsingComputerNameHardcoded" }
$withoutProfile.Count | Should Be 1
}

It "Does not raise any violations for computername hard-coded" {
$withProfile = $suppression | Where-Object { $_.RuleName -eq "PSAvoidUsingComputerNameHardcoded" }
$withProfile.Count | Should be 0
}
}

Context "Severity" {
It "Raises 1 violation for internal url without profile" {
$withoutProfile = $violations | Where-Object { $_.RuleName -eq "PSAvoidUsingInternalURLs" }
$withoutProfile.Count | Should Be 1
}

It "Does not raise any violations for internal urls with profile" {
$withProfile = $suppression | Where-Object { $_.RuleName -eq "PSAvoidUsingInternalURLs" }
$withProfile.Count | Should be 0
}
}
}
9 changes: 9 additions & 0 deletions Tests/Engine/Profile.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@{
Severity='Warning'
IncludeRules=@('PSAvoidUsingCmdletAliases',
'PSAvoidUsingPositionalParameters',
'PSAvoidUsingInternalURLs'
'PSAvoidUninitializedVariable')
ExcludeRules=@('PSAvoidUsingCmdletAliases'
'PSAvoidUninitializedVariable')
}