Skip to content

Commit 82af169

Browse files
author
quoctruong
committed
Merge pull request #24 from PowerShell/suppression
Add Scope and Target argument to rule suppression. Add Tests. Write Errors to terminal.
2 parents 99956f2 + 5fea714 commit 82af169

File tree

8 files changed

+313
-15
lines changed

8 files changed

+313
-15
lines changed

Engine/Commands/InvokeScriptAnalyzerCommand.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,8 +257,6 @@ private void AnalyzeFile(string filePath)
257257
ParseError[] errors = null;
258258
List<DiagnosticRecord> diagnostics = new List<DiagnosticRecord>();
259259

260-
IEnumerable<Ast> funcDefAsts;
261-
262260
// Use a List of KVP rather than dictionary, since for a script containing inline functions with same signature, keys clash
263261
List<KeyValuePair<CommandInfo, IScriptExtent>> cmdInfoTable = new List<KeyValuePair<CommandInfo, IScriptExtent>>();
264262

@@ -293,6 +291,17 @@ private void AnalyzeFile(string filePath)
293291

294292
Dictionary<string, List<RuleSuppression>> ruleSuppressions = Helper.Instance.GetRuleSuppression(ast);
295293

294+
foreach (List<RuleSuppression> ruleSuppressionsList in ruleSuppressions.Values)
295+
{
296+
foreach (RuleSuppression ruleSuppression in ruleSuppressionsList)
297+
{
298+
if (!String.IsNullOrWhiteSpace(ruleSuppression.Error))
299+
{
300+
WriteError(new ErrorRecord(new ArgumentException(ruleSuppression.Error), ruleSuppression.Error, ErrorCategory.InvalidArgument, ruleSuppression));
301+
}
302+
}
303+
}
304+
296305
#region Run VariableAnalysis
297306
try
298307
{

Engine/Generic/RuleSuppression.cs

Lines changed: 150 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
using System.Linq;
33
using System.Management.Automation.Language;
44
using System.Collections.Generic;
5+
using System.Text.RegularExpressions;
6+
using System.Globalization;
57

68
namespace Microsoft.Windows.Powershell.ScriptAnalyzer.Generic
79
{
@@ -10,6 +12,8 @@ namespace Microsoft.Windows.Powershell.ScriptAnalyzer.Generic
1012
/// </summary>
1113
public class RuleSuppression
1214
{
15+
private string _ruleName;
16+
1317
/// <summary>
1418
/// The start offset of the rule suppression
1519
/// </summary>
@@ -33,8 +37,24 @@ public int EndOffset
3337
/// </summary>
3438
public string RuleName
3539
{
36-
get;
37-
set;
40+
get
41+
{
42+
return _ruleName;
43+
}
44+
45+
set
46+
{
47+
_ruleName = value;
48+
if ((ScriptAnalyzer.Instance.ScriptRules != null
49+
&& ScriptAnalyzer.Instance.ScriptRules.Count(item => String.Equals(item.GetName(), _ruleName, StringComparison.OrdinalIgnoreCase)) == 0)
50+
&& (ScriptAnalyzer.Instance.TokenRules != null
51+
&& ScriptAnalyzer.Instance.TokenRules.Count(item => String.Equals(item.GetName(), _ruleName, StringComparison.OrdinalIgnoreCase)) == 0)
52+
&& (ScriptAnalyzer.Instance.ExternalRules != null
53+
&& ScriptAnalyzer.Instance.ExternalRules.Count(item => String.Equals(item.GetName(), _ruleName, StringComparison.OrdinalIgnoreCase)) == 0))
54+
{
55+
Error = String.Format(Strings.RuleSuppressionRuleNameNotFound, _ruleName);
56+
}
57+
}
3858
}
3959

4060
/// <summary>
@@ -46,6 +66,24 @@ public string RuleSuppressionID
4666
set;
4767
}
4868

69+
/// <summary>
70+
/// Scope of the rule suppression
71+
/// </summary>
72+
public string Scope
73+
{
74+
get;
75+
set;
76+
}
77+
78+
/// <summary>
79+
/// Target of the rule suppression
80+
/// </summary>
81+
public string Target
82+
{
83+
get;
84+
set;
85+
}
86+
4987
/// <summary>
5088
/// Returns error occurred in trying to parse the attribute
5189
/// </summary>
@@ -55,6 +93,18 @@ public string Error
5593
set;
5694
}
5795

96+
private static HashSet<string> scopeSet;
97+
98+
/// <summary>
99+
/// Initialize the scopeSet
100+
/// </summary>
101+
static RuleSuppression()
102+
{
103+
scopeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
104+
scopeSet.Add("function");
105+
scopeSet.Add("class");
106+
}
107+
58108
/// <summary>
59109
/// Returns rule suppression from an attribute ast that has the type suppressmessageattribute
60110
/// </summary>
@@ -85,6 +135,14 @@ public RuleSuppression(AttributeAst attrAst, int start, int end)
85135
{
86136
switch (count)
87137
{
138+
case 4:
139+
Target = (positionalArguments[3] as StringConstantExpressionAst).Value;
140+
goto case 3;
141+
142+
case 3:
143+
Scope = (positionalArguments[2] as StringConstantExpressionAst).Value;
144+
goto case 2;
145+
88146
case 2:
89147
RuleSuppressionID = (positionalArguments[1] as StringConstantExpressionAst).Value;
90148
goto case 1;
@@ -134,13 +192,64 @@ public RuleSuppression(AttributeAst attrAst, int start, int end)
134192
RuleSuppressionID = (name.Argument as StringConstantExpressionAst).Value;
135193
goto default;
136194

195+
case "scope":
196+
if (!String.IsNullOrWhiteSpace(Scope))
197+
{
198+
Error = String.Format(Strings.NamedAndPositionalArgumentsConflictError, name);
199+
}
200+
201+
Scope = (name.Argument as StringConstantExpressionAst).Value;
202+
203+
if (!scopeSet.Contains(Scope))
204+
{
205+
Error = Strings.WrongScopeArgumentSuppressionAttributeError;
206+
}
207+
208+
goto default;
209+
210+
case "target":
211+
if (!String.IsNullOrWhiteSpace(Target))
212+
{
213+
Error = String.Format(Strings.NamedAndPositionalArgumentsConflictError, name);
214+
}
215+
216+
Target = (name.Argument as StringConstantExpressionAst).Value;
217+
goto default;
218+
137219
default:
138220
break;
139221
}
140222
}
141223
}
224+
225+
// Must have scope and target together
226+
if (String.IsNullOrWhiteSpace(Scope) && !String.IsNullOrWhiteSpace(Target))
227+
{
228+
Error = Strings.TargetWithoutScopeSuppressionAttributeError;
229+
}
230+
}
231+
232+
StartOffset = start;
233+
EndOffset = end;
234+
235+
if (!String.IsNullOrWhiteSpace(Error))
236+
{
237+
Error = String.Format(CultureInfo.CurrentCulture, Strings.RuleSuppressionErrorFormat, attrAst.Extent.StartLineNumber,
238+
System.IO.Path.GetFileName(attrAst.Extent.File), Error);
142239
}
240+
}
143241

242+
/// <summary>
243+
/// Constructs rule expression from rule name, id, start and end
244+
/// </summary>
245+
/// <param name="ruleName"></param>
246+
/// <param name="ruleSuppressionID"></param>
247+
/// <param name="start"></param>
248+
/// <param name="end"></param>
249+
public RuleSuppression(string ruleName, string ruleSuppressionID, int start, int end)
250+
{
251+
RuleName = ruleName;
252+
RuleSuppressionID = ruleSuppressionID;
144253
StartOffset = start;
145254
EndOffset = end;
146255
}
@@ -153,11 +262,11 @@ public RuleSuppression(AttributeAst attrAst, int start, int end)
153262
/// <param name="start"></param>
154263
/// <param name="end"></param>
155264
/// <returns></returns>
156-
public static List<RuleSuppression> GetSuppressions(IEnumerable<AttributeAst> attrAsts, int start, int end)
265+
public static List<RuleSuppression> GetSuppressions(IEnumerable<AttributeAst> attrAsts, int start, int end, Ast scopeAst)
157266
{
158267
List<RuleSuppression> result = new List<RuleSuppression>();
159268

160-
if (attrAsts == null)
269+
if (attrAsts == null || scopeAst == null)
161270
{
162271
return result;
163272
}
@@ -169,8 +278,44 @@ public static List<RuleSuppression> GetSuppressions(IEnumerable<AttributeAst> at
169278
{
170279
RuleSuppression ruleSupp = new RuleSuppression(attributeAst, start, end);
171280

172-
if (string.IsNullOrWhiteSpace(ruleSupp.Error))
281+
// If there is no error and scope is not null
282+
if (String.IsNullOrWhiteSpace(ruleSupp.Error) && !String.IsNullOrWhiteSpace(ruleSupp.Scope))
283+
{
284+
if (String.IsNullOrWhiteSpace(ruleSupp.Target))
285+
{
286+
ruleSupp.Target = "*";
287+
}
288+
289+
// regex for wild card *
290+
Regex reg = new Regex(String.Format("^{0}$", Regex.Escape(ruleSupp.Target).Replace(@"\*", ".*")));
291+
IEnumerable<Ast> targetAsts = null;
292+
293+
switch (ruleSupp.Scope.ToLower())
294+
{
295+
case "function":
296+
targetAsts = scopeAst.FindAll(item => item is FunctionDefinitionAst && reg.IsMatch((item as FunctionDefinitionAst).Name), true);
297+
goto default;
298+
299+
case "class":
300+
targetAsts = scopeAst.FindAll(item => item is TypeDefinitionAst && reg.IsMatch((item as TypeDefinitionAst).Name), true);
301+
goto default;
302+
303+
default:
304+
break;
305+
}
306+
307+
if (targetAsts != null)
308+
{
309+
foreach (Ast targetAst in targetAsts)
310+
{
311+
result.Add(new RuleSuppression(ruleSupp.RuleName, ruleSupp.RuleSuppressionID, targetAst.Extent.StartOffset, targetAst.Extent.EndOffset));
312+
}
313+
}
314+
315+
}
316+
else
173317
{
318+
// this may add rule suppression that contains erro but we will check for this in the engine to throw out error
174319
result.Add(ruleSupp);
175320
}
176321
}

Engine/Helper.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -627,7 +627,7 @@ public Dictionary<string, List<RuleSuppression>> GetRuleSuppression(Ast ast)
627627
// Get rule suppression from the ast itself if it is scriptblockast
628628
if (sbAst != null && sbAst.ParamBlock != null && sbAst.ParamBlock.Attributes != null)
629629
{
630-
ruleSuppressionList.AddRange(RuleSuppression.GetSuppressions(sbAst.ParamBlock.Attributes, sbAst.Extent.StartOffset, sbAst.Extent.EndOffset));
630+
ruleSuppressionList.AddRange(RuleSuppression.GetSuppressions(sbAst.ParamBlock.Attributes, sbAst.Extent.StartOffset, sbAst.Extent.EndOffset, sbAst));
631631
}
632632

633633
// Get rule suppression from functions
@@ -674,7 +674,7 @@ internal List<RuleSuppression> GetSuppressionsFunction(FunctionDefinitionAst fun
674674
if (funcAst != null && funcAst.Body != null
675675
&& funcAst.Body.ParamBlock != null && funcAst.Body.ParamBlock.Attributes != null)
676676
{
677-
result.AddRange(RuleSuppression.GetSuppressions(funcAst.Body.ParamBlock.Attributes, funcAst.Extent.StartOffset, funcAst.Extent.EndOffset));
677+
result.AddRange(RuleSuppression.GetSuppressions(funcAst.Body.ParamBlock.Attributes, funcAst.Extent.StartOffset, funcAst.Extent.EndOffset, funcAst));
678678
}
679679

680680
return result;
@@ -691,7 +691,7 @@ internal List<RuleSuppression> GetSuppressionsClass(TypeDefinitionAst typeAst)
691691

692692
if (typeAst != null && typeAst.Attributes != null && typeAst.Attributes.Count != 0)
693693
{
694-
result.AddRange(RuleSuppression.GetSuppressions(typeAst.Attributes, typeAst.Extent.StartOffset, typeAst.Extent.EndOffset));
694+
result.AddRange(RuleSuppression.GetSuppressions(typeAst.Attributes, typeAst.Extent.StartOffset, typeAst.Extent.EndOffset, typeAst));
695695
}
696696

697697
if (typeAst.Members == null)
@@ -708,7 +708,7 @@ internal List<RuleSuppression> GetSuppressionsClass(TypeDefinitionAst typeAst)
708708
continue;
709709
}
710710

711-
result.AddRange(RuleSuppression.GetSuppressions(funcMemb.Attributes, funcMemb.Extent.StartOffset, funcMemb.Extent.EndOffset));
711+
result.AddRange(RuleSuppression.GetSuppressions(funcMemb.Attributes, funcMemb.Extent.StartOffset, funcMemb.Extent.EndOffset, funcMemb));
712712
}
713713

714714
return result;

Engine/Strings.Designer.cs

Lines changed: 37 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Engine/Strings.resx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,13 +162,25 @@
162162
<data name="RulesNotFound" xml:space="preserve">
163163
<value>Cannot find analyzer rules.</value>
164164
</data>
165+
<data name="RuleSuppressionErrorFormat" xml:space="preserve">
166+
<value>Suppression Message Attribute error at line {0} in {1} : {2}</value>
167+
</data>
168+
<data name="RuleSuppressionRuleNameNotFound" xml:space="preserve">
169+
<value>Rule {0} cannot be found.</value>
170+
</data>
165171
<data name="StringConstantArgumentsSuppressionAttributeError" xml:space="preserve">
166-
<value>All the arguments of the suppression message attribute should be string constants.</value>
172+
<value>All the arguments of the should be string constants.</value>
173+
</data>
174+
<data name="TargetWithoutScopeSuppressionAttributeError" xml:space="preserve">
175+
<value>If Target is specified, Scope must be specified.</value>
167176
</data>
168177
<data name="VerboseFileMessage" xml:space="preserve">
169178
<value>Analyzing file: {0}</value>
170179
</data>
171180
<data name="VerboseRunningMessage" xml:space="preserve">
172181
<value>Running {0} rule.</value>
173182
</data>
183+
<data name="WrongScopeArgumentSuppressionAttributeError" xml:space="preserve">
184+
<value>Scope can only be either function or class.</value>
185+
</data>
174186
</root>

Rules/AvoidUnitializedVariable.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
4242
if (Helper.Instance.IsUninitialized(varAst, ast))
4343
{
4444
yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.AvoidUninitializedVariableError, varAst.VariablePath.UserPath),
45-
varAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName, string.Format(CultureInfo.CurrentCulture, "{0}{1}", GetName(), varAst.VariablePath.UserPath));
45+
varAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName, varAst.VariablePath.UserPath);
4646
}
4747
}
4848

@@ -59,7 +59,7 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
5959
if (Helper.Instance.IsUninitialized(varAst, funcAst))
6060
{
6161
yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.AvoidUninitializedVariableError, varAst.VariablePath.UserPath),
62-
varAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName, string.Format(CultureInfo.CurrentCulture, "{0}{1}", GetName(), varAst.VariablePath.UserPath));
62+
varAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName, varAst.VariablePath.UserPath);
6363
}
6464
}
6565
}

0 commit comments

Comments
 (0)