@@ -21,8 +21,60 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules
2121#endif
2222 public class ReviewUnusedParameter : IScriptRule
2323 {
24+ private readonly string TraverseArgName = "CommandsToTraverse" ;
25+ public List < string > TraverseCommands { get ; private set ; }
26+
27+ /// <summary>
28+ /// Configure the rule.
29+ ///
30+ /// Sets the list of commands to traverse of this rule
31+ /// </summary>
32+ private void SetProperties ( )
33+ {
34+ TraverseCommands = new List < string > ( ) { "Where-Object" , "ForEach-Object" } ;
35+
36+ Dictionary < string , object > ruleArgs = Helper . Instance . GetRuleArguments ( GetName ( ) ) ;
37+ if ( ruleArgs == null )
38+ {
39+ return ;
40+ }
41+
42+ if ( ! ruleArgs . TryGetValue ( TraverseArgName , out object obj ) )
43+ {
44+ return ;
45+ }
46+ IEnumerable < string > commands = obj as IEnumerable < string > ;
47+ if ( commands == null )
48+ {
49+ // try with enumerable objects
50+ var enumerableObjs = obj as IEnumerable < object > ;
51+ if ( enumerableObjs == null )
52+ {
53+ return ;
54+ }
55+ foreach ( var x in enumerableObjs )
56+ {
57+ var y = x as string ;
58+ if ( y == null )
59+ {
60+ return ;
61+ }
62+ else
63+ {
64+ TraverseCommands . Add ( y ) ;
65+ }
66+ }
67+ }
68+ else
69+ {
70+ TraverseCommands . AddRange ( commands ) ;
71+ }
72+ }
73+
2474 public IEnumerable < DiagnosticRecord > AnalyzeScript ( Ast ast , string fileName )
2575 {
76+ SetProperties ( ) ;
77+
2678 if ( ast == null )
2779 {
2880 throw new ArgumentNullException ( Strings . NullAstErrorMessage ) ;
@@ -46,10 +98,7 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
4698 IEnumerable < Ast > parameterAsts = scriptBlockAst . FindAll ( oneAst => oneAst is ParameterAst , false ) ;
4799
48100 // list all variables
49- IDictionary < string , int > variableCount = scriptBlockAst . FindAll ( oneAst => oneAst is VariableExpressionAst , false )
50- . Select ( variableExpressionAst => ( ( VariableExpressionAst ) variableExpressionAst ) . VariablePath . UserPath )
51- . GroupBy ( variableName => variableName , StringComparer . OrdinalIgnoreCase )
52- . ToDictionary ( variableName => variableName . Key , variableName => variableName . Count ( ) , StringComparer . OrdinalIgnoreCase ) ;
101+ IDictionary < string , int > variableCount = GetVariableCount ( scriptBlockAst ) ;
53102
54103 foreach ( ParameterAst parameterAst in parameterAsts )
55104 {
@@ -164,5 +213,39 @@ public string GetSourceName()
164213 {
165214 return string . Format ( CultureInfo . CurrentCulture , Strings . SourceName ) ;
166215 }
216+
217+ /// <summary>
218+ /// Returns a dictionary including all variables in the scriptblock and their count
219+ /// </summary>
220+ /// <param name="ast">The scriptblock ast to scan</param>
221+ /// <param name="data">Previously generated data. New findings are added to any existing dictionary if present</param>
222+ /// <returns>a dictionary including all variables in the scriptblock and their count</returns>
223+ IDictionary < string , int > GetVariableCount ( ScriptBlockAst ast , Dictionary < string , int > data = null )
224+ {
225+ Dictionary < string , int > content = data ;
226+ if ( null == data )
227+ content = new Dictionary < string , int > ( StringComparer . OrdinalIgnoreCase ) ;
228+ IDictionary < string , int > result = ast . FindAll ( oneAst => oneAst is VariableExpressionAst , false )
229+ . Select ( variableExpressionAst => ( ( VariableExpressionAst ) variableExpressionAst ) . VariablePath . UserPath )
230+ . GroupBy ( variableName => variableName , StringComparer . OrdinalIgnoreCase )
231+ . ToDictionary ( variableName => variableName . Key , variableName => variableName . Count ( ) , StringComparer . OrdinalIgnoreCase ) ;
232+
233+ foreach ( string key in result . Keys )
234+ {
235+ if ( content . ContainsKey ( key ) )
236+ content [ key ] = content [ key ] + result [ key ] ;
237+ else
238+ content [ key ] = result [ key ] ;
239+ }
240+
241+ IEnumerable < Ast > foundScriptBlocks = ast . FindAll ( oneAst => oneAst is ScriptBlockExpressionAst , false )
242+ . Where ( oneAst => oneAst ? . Parent is CommandAst && ( ( CommandAst ) oneAst . Parent ) . CommandElements [ 0 ] is StringConstantExpressionAst && TraverseCommands . Contains ( ( ( StringConstantExpressionAst ) ( ( CommandAst ) oneAst . Parent ) . CommandElements [ 0 ] ) . Value , StringComparer . OrdinalIgnoreCase ) )
243+ . Select ( oneAst => ( ( ScriptBlockExpressionAst ) oneAst ) . ScriptBlock ) ;
244+ foreach ( Ast astItem in foundScriptBlocks )
245+ if ( astItem != ast )
246+ GetVariableCount ( ( ScriptBlockAst ) astItem , content ) ;
247+
248+ return content ;
249+ }
167250 }
168251}
0 commit comments