Skip to content

Commit 14b5f18

Browse files
author
quoctruong
committed
Implement suppressing rule for function
1 parent 1831861 commit 14b5f18

File tree

3 files changed

+255
-14
lines changed

3 files changed

+255
-14
lines changed

Engine/Commands/InvokeScriptAnalyzerCommand.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ private void AnalyzeFile(string filePath)
257257
Token[] tokens = null;
258258
ParseError[] errors = null;
259259
List<DiagnosticRecord> diagnostics = new List<DiagnosticRecord>();
260+
260261
IEnumerable<Ast> funcDefAsts;
261262

262263
// Use a List of KVP rather than dictionary, since for a script containing inline functions with same signature, keys clash
@@ -291,6 +292,8 @@ private void AnalyzeFile(string filePath)
291292
return;
292293
}
293294

295+
Dictionary<string, LinkedList<Tuple<int, int>>> ruleSuppressions = Helper.Instance.GetRuleSuppression(ast);
296+
294297
#region Run VariableAnalysis
295298
try
296299
{
@@ -317,7 +320,7 @@ private void AnalyzeFile(string filePath)
317320
// We want the Engine to continue functioning even if one or more Rules throws an exception
318321
try
319322
{
320-
diagnostics.AddRange(scriptRule.AnalyzeScript(ast, filePath));
323+
diagnostics.AddRange(Helper.Instance.SuppressRule(scriptRule.GetName(), ruleSuppressions, scriptRule.AnalyzeScript(ast, filePath).ToList()));
321324
}
322325
catch (Exception scriptRuleException)
323326
{

Engine/Generic/RuleSuppression.cs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Linq;
23
using System.Management.Automation.Language;
34

45
namespace Microsoft.Windows.Powershell.ScriptAnalyzer.Generic
@@ -43,5 +44,104 @@ public string RuleSuppressionID
4344
get;
4445
set;
4546
}
47+
48+
/// <summary>
49+
/// Returns error occurred in trying to parse the attribute
50+
/// </summary>
51+
public string Error
52+
{
53+
get;
54+
set;
55+
}
56+
57+
/// <summary>
58+
/// Returns rule suppression from an attribute ast that has the type suppressmessageattribute
59+
/// </summary>
60+
/// <param name="attrAst"></param>
61+
/// <param name="start"></param>
62+
/// <param name="end"></param>
63+
public RuleSuppression(AttributeAst attrAst, int start, int end)
64+
{
65+
Error = String.Empty;
66+
67+
if (attrAst != null)
68+
{
69+
var positionalArguments = attrAst.PositionalArguments;
70+
var namedArguments = attrAst.NamedArguments;
71+
72+
int lastPositionalArgumentsOffset = -1;
73+
74+
if (positionalArguments != null && positionalArguments.Count != 0)
75+
{
76+
int count = positionalArguments.Count;
77+
lastPositionalArgumentsOffset = positionalArguments[positionalArguments.Count - 1].Extent.StartOffset;
78+
79+
if (positionalArguments.Any(item => !(item is StringConstantExpressionAst)))
80+
{
81+
Error = "All the arguments of the suppression message attribute should be string constant";
82+
}
83+
else
84+
{
85+
switch (count)
86+
{
87+
case 2:
88+
RuleSuppressionID = (positionalArguments[1] as StringConstantExpressionAst).Value;
89+
goto case 1;
90+
91+
case 1:
92+
RuleName = (positionalArguments[0] as StringConstantExpressionAst).Value;
93+
goto default;
94+
95+
default:
96+
break;
97+
}
98+
}
99+
}
100+
101+
if (namedArguments != null && namedArguments.Count != 0)
102+
{
103+
foreach (var name in namedArguments)
104+
{
105+
if (name.Extent.StartOffset < lastPositionalArgumentsOffset)
106+
{
107+
Error = "Named arguments must always come after positional arguments";
108+
break;
109+
}
110+
else if (!(name.Argument is StringConstantExpressionAst))
111+
{
112+
Error = "All the arguments of the suppression message attribute should be string constant";
113+
break;
114+
}
115+
116+
switch (name.ArgumentName.ToLower())
117+
{
118+
case "rulename":
119+
if (!String.IsNullOrWhiteSpace(RuleName))
120+
{
121+
Error = "RuleName cannot be set by both positional and named arguments";
122+
}
123+
124+
RuleName = (name.Argument as StringConstantExpressionAst).Value;
125+
goto default;
126+
127+
case "rulesuppressionid":
128+
if (!String.IsNullOrWhiteSpace(RuleSuppressionID))
129+
{
130+
Error = "RuleSuppressionID cannot be set by both positional and named arguments";
131+
}
132+
133+
RuleSuppressionID = (name.Argument as StringConstantExpressionAst).Value;
134+
goto default;
135+
136+
default:
137+
break;
138+
}
139+
}
140+
}
141+
}
142+
143+
StartOffSet = start;
144+
EndOffset = end;
145+
}
46146
}
47147
}

Engine/Helper.cs

Lines changed: 151 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public static Helper Instance
5959
/// </summary>
6060
public PSCmdlet MyCmdlet { get; set; }
6161

62-
private TupleComparer tupleComparer = new TupleComparer();
62+
internal TupleComparer tupleComparer = new TupleComparer();
6363

6464
/// <summary>
6565
/// My Tokens
@@ -605,9 +605,157 @@ public bool HasSpecialVars(string varName)
605605
return false;
606606
}
607607

608-
public RuleSuppression GetRuleSuppression()
608+
/// <summary>
609+
/// Returns a dictionary of rule suppression from the ast.
610+
/// Key of the dictionary is rule name.
611+
/// Value is a list of tuple of integers that represents the interval to apply the rule
612+
/// </summary>
613+
/// <param name="ast"></param>
614+
/// <returns></returns>
615+
public Dictionary<string, LinkedList<Tuple<int, int>>> GetRuleSuppression(Ast ast)
609616
{
610-
return null;
617+
List<RuleSuppression> ruleSuppressionList = new List<RuleSuppression>();
618+
IEnumerable<FunctionDefinitionAst> funcAsts = ast.FindAll(item => item is FunctionDefinitionAst, true).Cast<FunctionDefinitionAst>();
619+
foreach (var funcAst in funcAsts)
620+
{
621+
ruleSuppressionList.AddRange(GetSuppressionFunction(funcAst));
622+
}
623+
624+
Dictionary<string, LinkedList<Tuple<int, int>>> results = new Dictionary<string, LinkedList<Tuple<int, int>>>(StringComparer.OrdinalIgnoreCase);
625+
ruleSuppressionList.Sort((item, item2) => item.StartOffSet.CompareTo(item2.StartOffSet));
626+
627+
foreach (RuleSuppression ruleSuppression in ruleSuppressionList)
628+
{
629+
if (!results.ContainsKey(ruleSuppression.RuleName))
630+
{
631+
LinkedList<Tuple<int, int>> intervals = new LinkedList<Tuple<int, int>>();
632+
intervals.AddLast(Tuple.Create(ruleSuppression.StartOffSet, ruleSuppression.EndOffset));
633+
results.Add(ruleSuppression.RuleName, intervals);
634+
}
635+
else
636+
{
637+
LinkedList<Tuple<int, int>> intervals = results[ruleSuppression.RuleName];
638+
int previousEnd = intervals.Last.Value.Item2;
639+
640+
// proccess the intervals so they do not overlap and is sorted in order
641+
if (ruleSuppression.StartOffSet <= previousEnd)
642+
{
643+
if (ruleSuppression.EndOffset <= previousEnd)
644+
{
645+
continue;
646+
}
647+
else
648+
{
649+
intervals.Last.Value = Tuple.Create(intervals.Last.Value.Item1, ruleSuppression.EndOffset);
650+
}
651+
}
652+
else
653+
{
654+
intervals.AddLast(Tuple.Create(ruleSuppression.StartOffSet, ruleSuppression.EndOffset));
655+
}
656+
}
657+
}
658+
659+
return results;
660+
}
661+
662+
/// <summary>
663+
/// Returns a list of rule suppressions from the function
664+
/// </summary>
665+
/// <param name="funcAst"></param>
666+
/// <returns></returns>
667+
internal List<RuleSuppression> GetSuppressionFunction(FunctionDefinitionAst funcAst)
668+
{
669+
List<RuleSuppression> result = new List<RuleSuppression>();
670+
671+
if (funcAst != null && funcAst.Body != null
672+
&& funcAst.Body.ParamBlock != null && funcAst.Body.ParamBlock.Attributes != null)
673+
{
674+
IEnumerable<AttributeAst> suppressionAttribute = funcAst.Body.ParamBlock.Attributes.Where(
675+
item => item.TypeName.GetReflectionType() == typeof(System.Diagnostics.CodeAnalysis.SuppressMessageAttribute));
676+
677+
foreach (var attributeAst in suppressionAttribute)
678+
{
679+
RuleSuppression ruleSupp = new RuleSuppression(attributeAst, funcAst.Extent.StartOffset, funcAst.Extent.EndOffset);
680+
if (String.IsNullOrWhiteSpace(ruleSupp.Error))
681+
{
682+
result.Add(ruleSupp);
683+
}
684+
}
685+
}
686+
687+
return result;
688+
}
689+
690+
/// <summary>
691+
/// Suppress the rules from the diagnostic records list and return the result
692+
/// </summary>
693+
/// <param name="ruleSuppressions"></param>
694+
/// <param name="diagnostics"></param>
695+
public List<DiagnosticRecord> SuppressRule(string ruleName, Dictionary<string, LinkedList<Tuple<int, int>>> ruleSuppressions, List<DiagnosticRecord> diagnostics)
696+
{
697+
List<DiagnosticRecord> results = new List<DiagnosticRecord>();
698+
if (!ruleSuppressions.ContainsKey(ruleName) || diagnostics.Count == 0)
699+
{
700+
return diagnostics;
701+
}
702+
703+
LinkedList<Tuple<int, int>> intervals = ruleSuppressions[ruleName];
704+
705+
if (intervals.Count == 0)
706+
{
707+
return diagnostics;
708+
}
709+
710+
int recordIndex = 0;
711+
DiagnosticRecord record = diagnostics.First();
712+
LinkedListNode<Tuple<int, int>> interval = intervals.First;
713+
714+
while (recordIndex < diagnostics.Count)
715+
{
716+
// the record precedes the interval so don't apply the suppression
717+
if (record.Extent.StartOffset < interval.Value.Item1)
718+
{
719+
results.Add(record);
720+
recordIndex += 1;
721+
722+
if (recordIndex == diagnostics.Count)
723+
{
724+
break;
725+
}
726+
else
727+
{
728+
// move on to the next record
729+
record = diagnostics[recordIndex];
730+
continue;
731+
}
732+
}
733+
734+
// end of the rule suppression is less than the record start offset so move on to next interval
735+
if (interval.Value.Item2 < record.Extent.StartOffset)
736+
{
737+
interval = interval.Next;
738+
739+
if (interval == null)
740+
{
741+
break;
742+
}
743+
744+
continue;
745+
}
746+
747+
// here the record is inside the interval so we don't add it to the result
748+
recordIndex += 1;
749+
750+
if (recordIndex == diagnostics.Count)
751+
{
752+
break;
753+
}
754+
755+
record = diagnostics[recordIndex];
756+
}
757+
758+
return results;
611759
}
612760

613761
#endregion
@@ -696,16 +844,6 @@ public object VisitScriptBlock(ScriptBlockAst scriptBlockAst)
696844
return null;
697845
}
698846

699-
/// <summary>
700-
/// Do nothing
701-
/// </summary>
702-
/// <param name="baseCtorInvokeMemberExpressionAst"></param>
703-
/// <returns></returns>
704-
public object VisitBaseCtorInvokeMemberExpression(BaseCtorInvokeMemberExpressionAst baseCtorInvokeMemberExpressionAst)
705-
{
706-
return null;
707-
}
708-
709847
/// <summary>
710848
/// Do nothing
711849
/// </summary>

0 commit comments

Comments
 (0)