diff --git a/Engine/Commands/InvokeScriptAnalyzerCommand.cs b/Engine/Commands/InvokeScriptAnalyzerCommand.cs
index f5598d5bd..176d4142d 100644
--- a/Engine/Commands/InvokeScriptAnalyzerCommand.cs
+++ b/Engine/Commands/InvokeScriptAnalyzerCommand.cs
@@ -21,7 +21,7 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.Commands
///
[Cmdlet(VerbsLifecycle.Invoke,
"ScriptAnalyzer",
- DefaultParameterSetName = "File",
+ DefaultParameterSetName = "FilePartial",
SupportsShouldProcess = true,
HelpUri = "https://go.microsoft.com/fwlink/?LinkId=525914")]
[OutputType(typeof(DiagnosticRecord))]
@@ -37,7 +37,12 @@ public class InvokeScriptAnalyzerCommand : PSCmdlet, IOutputWriter
/// Path: The path to the file or folder to invoke PSScriptAnalyzer on.
///
[Parameter(Position = 0,
- ParameterSetName = "File",
+ ParameterSetName = "FilePartial",
+ Mandatory = true,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true)]
+ [Parameter(Position = 0,
+ ParameterSetName = "FileAll",
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true)]
@@ -54,7 +59,12 @@ public string Path
/// ScriptDefinition: a script definition in the form of a string to run rules on.
///
[Parameter(Position = 0,
- ParameterSetName = "ScriptDefinition",
+ ParameterSetName = "ScriptDefinitionPartial",
+ Mandatory = true,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true)]
+ [Parameter(Position = 0,
+ ParameterSetName = "ScriptDefinitionAll",
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true)]
@@ -158,19 +168,24 @@ public SwitchParameter Recurse
///
/// ShowSuppressed: Show the suppressed message
///
- [Parameter(Mandatory = false)]
+ [Parameter(Mandatory = false, ParameterSetName = "FilePartial")]
+ [Parameter(Mandatory = false, ParameterSetName = "ScriptDefinitionPartial")]
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
- public SwitchParameter SuppressedOnly
- {
- get { return suppressedOnly; }
- set { suppressedOnly = value; }
- }
- private bool suppressedOnly;
+ public SwitchParameter SuppressedOnly { get; set; }
+
+ ///
+ /// ShowAll: Show the suppressed and non-suppressed message
+ ///
+ [Parameter(Mandatory = true, ParameterSetName = "FileAll")]
+ [Parameter(Mandatory = true, ParameterSetName = "ScriptDefinitionAll")]
+ [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
+ public SwitchParameter IncludeSuppressions { get; set; }
///
/// Resolves rule violations automatically where possible.
///
- [Parameter(Mandatory = false, ParameterSetName = "File")]
+ [Parameter(Mandatory = false, ParameterSetName = "FilePartial")]
+ [Parameter(Mandatory = false, ParameterSetName = "FileAll")]
public SwitchParameter Fix
{
get { return fix; }
@@ -341,7 +356,8 @@ protected override void BeginProcessing()
this.excludeRule,
this.severity,
combRulePaths == null || combIncludeDefaultRules,
- this.suppressedOnly);
+ this.SuppressedOnly,
+ this.IncludeSuppressions);
}
///
@@ -420,7 +436,7 @@ private void ProcessInput()
WriteToOutput(diagnosticsList);
}
}
- else if (String.Equals(this.ParameterSetName, "ScriptDefinition", StringComparison.OrdinalIgnoreCase))
+ else if (IsScriptParameterSet())
{
diagnosticsList = ScriptAnalyzer.Instance.AnalyzeScriptDefinition(scriptDefinition, out _, out _);
WriteToOutput(diagnosticsList);
@@ -499,7 +515,12 @@ private void ProcessPath()
private bool IsFileParameterSet()
{
- return String.Equals(this.ParameterSetName, "File", StringComparison.OrdinalIgnoreCase);
+ return String.Equals(this.ParameterSetName, "FilePartial", StringComparison.OrdinalIgnoreCase) || String.Equals(this.ParameterSetName, "FileAll", StringComparison.OrdinalIgnoreCase);
+ }
+
+ private bool IsScriptParameterSet()
+ {
+ return String.Equals(this.ParameterSetName, "ScriptDefinitionPartial", StringComparison.OrdinalIgnoreCase) || String.Equals(this.ParameterSetName, "ScriptDefinitionAll", StringComparison.OrdinalIgnoreCase);
}
private bool OverrideSwitchParam(bool paramValue, string paramName)
@@ -511,4 +532,4 @@ private bool OverrideSwitchParam(bool paramValue, string paramName)
#endregion // Private Methods
}
-}
+}
\ No newline at end of file
diff --git a/Engine/Generic/RuleSuppression.cs b/Engine/Generic/RuleSuppression.cs
index d8a41f974..25303f2c7 100644
--- a/Engine/Generic/RuleSuppression.cs
+++ b/Engine/Generic/RuleSuppression.cs
@@ -97,6 +97,15 @@ public string Justification
set;
}
+ ///
+ /// Returns the kind of the suppression
+ ///
+ public SuppressionKind Kind
+ {
+ get;
+ set;
+ }
+
private static HashSet scopeSet;
///
@@ -376,5 +385,15 @@ public static List GetSuppressions(IEnumerable at
return result;
}
+
+ ///
+ /// A string that indicates where the suppression is persisted.
+ ///
+ public enum SuppressionKind
+ {
+ None,
+ InSource,
+ External
+ }
}
}
diff --git a/Engine/Generic/SuppressedRecord.cs b/Engine/Generic/SuppressedRecord.cs
index ee50ea48b..c91aafd43 100644
--- a/Engine/Generic/SuppressedRecord.cs
+++ b/Engine/Generic/SuppressedRecord.cs
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
+using System.Collections.Generic;
+
namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic
{
///
@@ -11,20 +13,29 @@ public class SuppressedRecord : DiagnosticRecord
///
/// The rule suppression of this record
///
- public RuleSuppression Suppression
+ public IList Suppressions
{
- get;
- set;
+ get
+ {
+ if (suppressions == null) suppressions = new List();
+
+ return suppressions;
+ }
+ set
+ {
+ suppressions = value;
+ }
}
+ private IList suppressions;
///
/// Creates a suppressed record based on a diagnostic record and the rule suppression
///
///
///
- public SuppressedRecord(DiagnosticRecord record, RuleSuppression suppression)
+ public SuppressedRecord(DiagnosticRecord record, IList suppressions)
{
- Suppression = suppression;
+ Suppressions = suppressions;
if (record != null)
{
RuleName = record.RuleName;
diff --git a/Engine/Helper.cs b/Engine/Helper.cs
index 743cb4d68..30b6088f3 100644
--- a/Engine/Helper.cs
+++ b/Engine/Helper.cs
@@ -1348,50 +1348,47 @@ public Tuple, List> SuppressRule(
}
List ruleSuppressions = ruleSuppressionsDict[ruleName];
- var offsetArr = GetOffsetArray(diagnostics);
- int recordIndex = 0;
- int startRecord = 0;
- bool[] suppressed = new bool[diagnostics.Count];
- foreach (RuleSuppression ruleSuppression in ruleSuppressions)
- {
- int suppressionCount = 0;
- while (startRecord < diagnostics.Count
- // && diagnostics[startRecord].Extent.StartOffset < ruleSuppression.StartOffset)
- // && diagnostics[startRecord].Extent.StartLineNumber < ruleSuppression.st)
- && offsetArr[startRecord] != null && offsetArr[startRecord].Item1 < ruleSuppression.StartOffset)
- {
- startRecord += 1;
- }
+ bool[] applied = new bool[ruleSuppressions.Count];
- // at this point, start offset of startRecord is greater or equals to rulesuppression.startoffset
- recordIndex = startRecord;
-
- while (recordIndex < diagnostics.Count)
+ foreach(DiagnosticRecord diagnostic in diagnostics)
+ {
+ var curOffset = GetOffsetArray(diagnostic);
+ List suppressions = new List();
+ for (int ruleSuppressionIndex = 0; ruleSuppressionIndex < ruleSuppressions.Count; ruleSuppressionIndex++)
{
- DiagnosticRecord record = diagnostics[recordIndex];
- var curOffset = offsetArr[recordIndex];
-
- //if (record.Extent.EndOffset > ruleSuppression.EndOffset)
- if (curOffset != null && curOffset.Item2 > ruleSuppression.EndOffset)
+ RuleSuppression ruleSuppression = ruleSuppressions[ruleSuppressionIndex];
+ //if (diagnostic.Extent.StartOffset < ruleSuppression.StartOffset||diagnostic.Extent.EndOffset > ruleSuppression.EndOffset)
+ if (curOffset != null && (curOffset.Item1 < ruleSuppression.StartOffset || curOffset.Item2 > ruleSuppression.EndOffset))
{
- break;
+ continue;
}
// we suppress if there is no suppression id or if there is suppression id and it matches
- if (string.IsNullOrWhiteSpace(ruleSuppression.RuleSuppressionID)
- || (!String.IsNullOrWhiteSpace(record.RuleSuppressionID) &&
- string.Equals(ruleSuppression.RuleSuppressionID, record.RuleSuppressionID, StringComparison.OrdinalIgnoreCase)))
+ if (string.IsNullOrWhiteSpace(ruleSuppression.RuleSuppressionID) ||
+ (!String.IsNullOrWhiteSpace(diagnostic.RuleSuppressionID) &&
+ string.Equals(ruleSuppression.RuleSuppressionID, diagnostic.RuleSuppressionID, StringComparison.OrdinalIgnoreCase)))
{
- suppressed[recordIndex] = true;
- suppressedRecords.Add(new SuppressedRecord(record, ruleSuppression));
- suppressionCount += 1;
+ ruleSuppression.Kind = RuleSuppression.SuppressionKind.InSource;
+ suppressions.Add(ruleSuppression);
+ applied[ruleSuppressionIndex] = true;
}
+ }
- recordIndex += 1;
+ if (suppressions.Count() != 0)
+ {
+ suppressedRecords.Add(new SuppressedRecord(diagnostic, suppressions));
}
+ else
+ {
+ unSuppressedRecords.Add(diagnostic);
+ }
+ }
- // If we cannot found any error but the rulesuppression has a rulesuppressionid then it must be used wrongly
- if (!String.IsNullOrWhiteSpace(ruleSuppression.RuleSuppressionID) && suppressionCount == 0)
+ // If we cannot found any error but the rulesuppression has a rulesuppressionid then it must be used wrongly
+ for (int ruleSuppressionIndex = 0; ruleSuppressionIndex < ruleSuppressions.Count; ruleSuppressionIndex++)
+ {
+ RuleSuppression ruleSuppression = ruleSuppressions[ruleSuppressionIndex];
+ if ((!String.IsNullOrWhiteSpace(ruleSuppression.RuleSuppressionID)) && !applied[ruleSuppressionIndex])
{
// checks whether are given a string or a file path
if (String.IsNullOrWhiteSpace(diagnostics.First().Extent.File))
@@ -1409,70 +1406,56 @@ public Tuple, List> SuppressRule(
}
}
- for (int i = 0; i < suppressed.Length; i += 1)
- {
- if (!suppressed[i])
- {
- unSuppressedRecords.Add(diagnostics[i]);
- }
- }
-
return result;
}
- private Tuple[] GetOffsetArray(List diagnostics)
+ private Tuple GetOffsetArray(DiagnosticRecord diagnostic)
{
Func> GetTuple = (x, y) => new Tuple(x, y);
- Func> GetDefaultTuple = () => GetTuple(0, 0);
- var offsets = new Tuple[diagnostics.Count];
- for (int k = 0; k < diagnostics.Count; k++)
+ var offset = new Tuple(0, 0);
+ var ext = diagnostic.Extent;
+ if (ext == null)
{
- var ext = diagnostics[k].Extent;
- if (ext == null)
+ return null;
+ }
+ if (ext.StartOffset == 0 && ext.EndOffset == 0)
+ {
+ // check if line and column number correspond to 0 offsets
+ if (ext.StartLineNumber == 1
+ && ext.StartColumnNumber == 1
+ && ext.EndLineNumber == 1
+ && ext.EndColumnNumber == 1)
{
- continue;
+ return offset;
}
- if (ext.StartOffset == 0 && ext.EndOffset == 0)
+ // created using the ScriptExtent constructor, which sets
+ // StartOffset and EndOffset to 0
+ // find the token the corresponding start line and column number
+ var startToken = Tokens.Where(x
+ => x.Extent.StartLineNumber == ext.StartLineNumber
+ && x.Extent.StartColumnNumber == ext.StartColumnNumber)
+ .FirstOrDefault();
+ if (startToken == null)
{
- // check if line and column number correspond to 0 offsets
- if (ext.StartLineNumber == 1
- && ext.StartColumnNumber == 1
- && ext.EndLineNumber == 1
- && ext.EndColumnNumber == 1)
- {
- offsets[k] = GetDefaultTuple();
- continue;
- }
- // created using the ScriptExtent constructor, which sets
- // StartOffset and EndOffset to 0
- // find the token the corresponding start line and column number
- var startToken = Tokens.Where(x
- => x.Extent.StartLineNumber == ext.StartLineNumber
- && x.Extent.StartColumnNumber == ext.StartColumnNumber)
- .FirstOrDefault();
- if (startToken == null)
- {
- offsets[k] = GetDefaultTuple();
- continue;
- }
- var endToken = Tokens.Where(x
- => x.Extent.EndLineNumber == ext.EndLineNumber
- && x.Extent.EndColumnNumber == ext.EndColumnNumber)
- .FirstOrDefault();
- if (endToken == null)
- {
- offsets[k] = GetDefaultTuple();
- continue;
- }
- offsets[k] = GetTuple(startToken.Extent.StartOffset, endToken.Extent.EndOffset);
+ return offset;
}
- else
+ var endToken = Tokens.Where(x
+ => x.Extent.EndLineNumber == ext.EndLineNumber
+ && x.Extent.EndColumnNumber == ext.EndColumnNumber)
+ .FirstOrDefault();
+ if (endToken == null)
{
- // Extent has valid offsets
- offsets[k] = GetTuple(ext.StartOffset, ext.EndOffset);
+ return offset;
}
+ offset = GetTuple(startToken.Extent.StartOffset, endToken.Extent.EndOffset);
+ }
+ else
+ {
+ // Extent has valid offsets
+ offset = GetTuple(ext.StartOffset, ext.EndOffset);
}
- return offsets;
+
+ return offset;
}
public static string[] ProcessCustomRulePaths(string[] rulePaths, SessionState sessionState, bool recurse = false)
diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs
index ffe277375..be0ebbfab 100644
--- a/Engine/ScriptAnalyzer.cs
+++ b/Engine/ScriptAnalyzer.cs
@@ -42,6 +42,7 @@ public sealed class ScriptAnalyzer
List includeRegexList;
List excludeRegexList;
bool suppressedOnly;
+ bool includeSuppressions;
#if !PSV3
ModuleDependencyHandler moduleHandler;
#endif
@@ -118,7 +119,8 @@ internal void Initialize(
string[] excludeRuleNames = null,
string[] severity = null,
bool includeDefaultRules = false,
- bool suppressedOnly = false)
+ bool suppressedOnly = false,
+ bool includeSuppressions = false)
where TCmdlet : PSCmdlet, IOutputWriter
{
if (cmdlet == null)
@@ -135,7 +137,8 @@ internal void Initialize(
excludeRuleNames,
severity,
includeDefaultRules,
- suppressedOnly);
+ suppressedOnly,
+ includeSuppressions);
}
///
@@ -150,6 +153,7 @@ public void Initialize(
string[] severity = null,
bool includeDefaultRules = false,
bool suppressedOnly = false,
+ bool includeSuppressions = false,
string profile = null)
{
if (runspace == null)
@@ -174,6 +178,7 @@ public void Initialize(
severity,
includeDefaultRules,
suppressedOnly,
+ includeSuppressions,
profile);
}
@@ -188,6 +193,7 @@ public void CleanUp()
includeRegexList = null;
excludeRegexList = null;
suppressedOnly = false;
+ includeSuppressions = false;
}
///
@@ -672,6 +678,7 @@ private void Initialize(
string[] severity,
bool includeDefaultRules = false,
bool suppressedOnly = false,
+ bool includeSuppressions = false,
string profile = null)
{
if (outputWriter == null)
@@ -731,6 +738,7 @@ private void Initialize(
}
this.suppressedOnly = suppressedOnly;
+ this.includeSuppressions = includeSuppressions;
this.includeRegexList = new List();
this.excludeRegexList = new List();
@@ -2323,9 +2331,12 @@ public IEnumerable AnalyzeSyntaxTree(
// Need to reverse the concurrentbag to ensure that results are sorted in the increasing order of line numbers
IEnumerable diagnosticsList = diagnostics.Reverse();
+ IEnumerable suppressedList = suppressed.OfType();
+ IEnumerable allRecordsList = diagnosticsList.Concat(suppressedList);
return this.suppressedOnly ?
- suppressed.OfType() :
+ suppressedList : this.includeSuppressions ?
+ allRecordsList :
diagnosticsList;
}
}
diff --git a/Engine/ScriptAnalyzer.format.ps1xml b/Engine/ScriptAnalyzer.format.ps1xml
index e6d78fc28..244403b35 100644
--- a/Engine/ScriptAnalyzer.format.ps1xml
+++ b/Engine/ScriptAnalyzer.format.ps1xml
@@ -81,6 +81,10 @@
60
+
+ 10
+
+
@@ -101,6 +105,9 @@
Justification
+
+ Kind
+
diff --git a/Engine/ScriptAnalyzer.types.ps1xml b/Engine/ScriptAnalyzer.types.ps1xml
index da989657e..852ee05fa 100644
--- a/Engine/ScriptAnalyzer.types.ps1xml
+++ b/Engine/ScriptAnalyzer.types.ps1xml
@@ -50,7 +50,13 @@
Justification
- $this.Suppression.Justification
+ $this.Suppressions.Justification
+
+
+
+ Kind
+
+ $this.Suppressions.Kind
@@ -64,6 +70,7 @@
Line
Column
Justification
+ Kind
diff --git a/README.md b/README.md
index 56c04c1c1..e080de252 100644
--- a/README.md
+++ b/README.md
@@ -54,9 +54,9 @@ Usage
``` PowerShell
Get-ScriptAnalyzerRule [-CustomRulePath ] [-RecurseCustomRulePath] [-Name ] [-Severity ] []
-Invoke-ScriptAnalyzer [-Path] [-CustomRulePath ] [-RecurseCustomRulePath] [-ExcludeRule ] [-IncludeDefaultRules] [-IncludeRule ] [-Severity ] [-Recurse] [-SuppressedOnly] [-Fix] [-EnableExit] [-ReportSummary] [-Settings