1212
1313using System ;
1414using System . Collections . Generic ;
15+ using System . Linq ;
1516using System . Management . Automation . Language ;
1617#if ! CORECLR
1718using System . ComponentModel . Composition ;
1819#endif
1920using System . Globalization ;
2021using Microsoft . Windows . PowerShell . ScriptAnalyzer . Generic ;
22+ using System . Collections . Specialized ;
23+ using System . Collections . ObjectModel ;
2124
2225namespace Microsoft . Windows . PowerShell . ScriptAnalyzer . BuiltinRules
2326{
@@ -35,7 +38,6 @@ public UseLiteralInitializerForHashtable()
3538 var presetTypeNames = new string [ ]
3639 {
3740 "system.collection.hashtable" ,
38- "collection.hashtable" ,
3941 "hashtable"
4042 } ;
4143 presetTypeNameSet = new HashSet < string > ( presetTypeNames , StringComparer . OrdinalIgnoreCase ) ;
@@ -62,7 +64,9 @@ public override AstVisitAction VisitCommand(CommandAst commandAst)
6264 return AstVisitAction . SkipChildren ;
6365 }
6466
65- if ( ! commandAst . GetCommandName ( ) . Equals ( "new-object" , StringComparison . OrdinalIgnoreCase ) )
67+ var commandName = commandAst . GetCommandName ( ) ;
68+ if ( commandName == null
69+ || ! commandName . Equals ( "new-object" , StringComparison . OrdinalIgnoreCase ) )
6670 {
6771 return AstVisitAction . Continue ;
6872 }
@@ -72,23 +76,133 @@ public override AstVisitAction VisitCommand(CommandAst commandAst)
7276
7377 private void AnalyzeNewObjectCommand ( CommandAst commandAst )
7478 {
75- //new-object hashtable
76- var typeNameAst = commandAst . CommandElements [ 1 ] as StringConstantExpressionAst ;
77- if ( typeNameAst != null )
79+ string typeName ;
80+ List < string > argumentList ;
81+ GetParametersFromCommandAst ( commandAst , out typeName , out argumentList ) ;
82+ if ( typeName == null
83+ || ! presetTypeNameSet . Contains ( typeName ) )
7884 {
79- if ( presetTypeNameSet . Contains ( typeNameAst . Value ) )
85+ return ;
86+ }
87+
88+ if ( argumentList != null )
89+ {
90+ if ( argumentList . Any ( arg => arg != null && arg . EndsWith ( "ignorecase" , StringComparison . OrdinalIgnoreCase ) ) )
91+ {
92+ return ;
93+ }
94+ }
95+ var dr = new DiagnosticRecord (
96+ Strings . UseLiteralInitilializerForHashtableDescription ,
97+ commandAst . Extent ,
98+ GetName ( ) ,
99+ GetDiagnosticSeverity ( ) ,
100+ fileName ,
101+ ruleId : null ,
102+ suggestedCorrections : GetSuggestedCorrections ( commandAst , this . fileName ) ) ;
103+ diagnosticRecords . Add ( dr ) ;
104+ }
105+
106+ private void GetParametersFromCommandAst ( CommandAst commandAst , out string typeName , out List < string > argumentList )
107+ {
108+ // This should read the command in all the following form
109+ // new-object hashtable
110+ // new-object -Typename hashtable
111+ // new-object hashtable -ArgumentList comparer
112+ // new-object -Typename hashtable -ArgumentList blah1,blah2
113+ // new-object -ArgumentList blah1,blah2 -typename hashtable
114+
115+ argumentList = null ;
116+ typeName = null ;
117+ var namedArguments = new OrderedDictionary ( StringComparer . OrdinalIgnoreCase ) ;
118+ namedArguments . Add ( "typename" , null ) ;
119+ namedArguments . Add ( "argumentlist" , null ) ;
120+ var positinalElems = GetNamedArguments ( commandAst . CommandElements , ref namedArguments ) ;
121+ GetPositionalArguments ( new ReadOnlyCollection < CommandElementAst > ( positinalElems ) , ref namedArguments ) ;
122+ if ( namedArguments [ "typename" ] == null )
123+ {
124+ return ;
125+ }
126+
127+ var typenameAst = namedArguments [ "typename" ] as StringConstantExpressionAst ;
128+ if ( typenameAst == null )
129+ {
130+ return ;
131+ }
132+
133+ typeName = typenameAst . Value ;
134+ var argumentListAst = namedArguments [ "argumentlist" ] as ExpressionAst ;
135+ if ( argumentListAst == null )
136+ {
137+ return ;
138+ }
139+
140+ argumentList = new List < string > ( Helper . Instance . GetStringsFromExpressionAst ( argumentListAst ) ) ;
141+ }
142+
143+ private int GetFirstEmptyIndex ( OrderedDictionary namedArguments )
144+ {
145+ for ( int k = 0 ; k < namedArguments . Count ; k ++ )
146+ {
147+ if ( namedArguments [ k ] == null )
80148 {
81- var dr = new DiagnosticRecord (
82- Strings . UseLiteralInitilializerForHashtableDescription ,
83- commandAst . Extent ,
84- GetName ( ) ,
85- GetDiagnosticSeverity ( ) ,
86- fileName ,
87- ruleId : null ,
88- suggestedCorrections : null ) ;
89- diagnosticRecords . Add ( dr ) ;
149+ return k ;
90150 }
91151 }
152+ return - 1 ;
153+ }
154+
155+ private void GetPositionalArguments ( ReadOnlyCollection < CommandElementAst > positinalArguments , ref OrderedDictionary namedArguments )
156+ {
157+ for ( int k = 0 ; k < positinalArguments . Count ; k ++ )
158+ {
159+ int firstEmptyIndex = GetFirstEmptyIndex ( namedArguments ) ;
160+ if ( firstEmptyIndex == - 1 )
161+ {
162+ return ;
163+ }
164+ var elem = positinalArguments [ k ] ;
165+ namedArguments [ firstEmptyIndex ] = elem as ExpressionAst ;
166+ }
167+ }
168+
169+ private List < CommandElementAst > GetNamedArguments ( ReadOnlyCollection < CommandElementAst > commandElements , ref OrderedDictionary namedArguments )
170+ {
171+ bool paramFound = false ;
172+ string paramName = null ;
173+ var remainingCommandElements = new List < CommandElementAst > ( ) ;
174+ for ( int k = 1 ; k < commandElements . Count ; k ++ )
175+ {
176+ if ( paramFound )
177+ {
178+ paramFound = false ;
179+ var argAst = commandElements [ k ] as ExpressionAst ;
180+ if ( argAst != null )
181+ {
182+ namedArguments [ paramName ] = argAst ;
183+ continue ;
184+ }
185+ }
186+ var paramAst = commandElements [ k ] as CommandParameterAst ;
187+ if ( paramAst != null )
188+ {
189+ foreach ( var key in namedArguments . Keys )
190+ {
191+ var keyStr = key as string ;
192+ if ( keyStr . Equals ( paramAst . ParameterName , StringComparison . OrdinalIgnoreCase ) )
193+ {
194+ paramFound = true ;
195+ paramName = paramAst . ParameterName ;
196+ break ;
197+ }
198+ }
199+ }
200+ else
201+ {
202+ remainingCommandElements . Add ( commandElements [ k ] ) ;
203+ }
204+ }
205+ return remainingCommandElements ;
92206 }
93207
94208 public override AstVisitAction VisitInvokeMemberExpression ( InvokeMemberExpressionAst methodCallAst )
@@ -112,8 +226,10 @@ public override AstVisitAction VisitInvokeMemberExpression(InvokeMemberExpressio
112226 return AstVisitAction . Continue ;
113227 }
114228
115- // no arguments provided to new
116- if ( methodCallAst . Arguments == null )
229+ // no arguments provided to new OR one of the argument ends with ignorecase
230+ // (heuristics find to something like [system.stringcomparer]::ordinalignorecase)
231+ if ( methodCallAst . Arguments == null
232+ || ! HasIgnoreCaseComparerArg ( methodCallAst . Arguments ) )
117233 {
118234
119235 var dr = new DiagnosticRecord (
@@ -123,26 +239,66 @@ public override AstVisitAction VisitInvokeMemberExpression(InvokeMemberExpressio
123239 GetDiagnosticSeverity ( ) ,
124240 fileName ,
125241 ruleId : null ,
126- suggestedCorrections : null ) ;
242+ suggestedCorrections : GetSuggestedCorrections ( methodCallAst , this . fileName ) ) ;
127243 diagnosticRecords . Add ( dr ) ;
128244 }
129245
130246 return AstVisitAction . Continue ;
131247 }
132248
249+ private bool HasIgnoreCaseComparerArg ( ReadOnlyCollection < ExpressionAst > arguments )
250+ {
251+ foreach ( var arg in arguments )
252+ {
253+ var memberExprAst = arg as MemberExpressionAst ;
254+ if ( memberExprAst == null )
255+ {
256+ continue ;
257+ }
258+ var strConstExprAst = memberExprAst . Member as StringConstantExpressionAst ;
259+ if ( strConstExprAst == null )
260+ {
261+ continue ;
262+ }
263+ if ( strConstExprAst . Value . EndsWith ( "ignorecase" ) )
264+ {
265+ return true ;
266+ }
267+ }
268+ return false ;
269+ }
270+
271+ private List < CorrectionExtent > GetSuggestedCorrections ( Ast violation , string filename )
272+ {
273+ var correctionExtents = new List < CorrectionExtent > ( ) ;
274+ correctionExtents . Add ( new CorrectionExtent (
275+ violation . Extent . StartLineNumber ,
276+ violation . Extent . EndLineNumber ,
277+ violation . Extent . StartColumnNumber ,
278+ violation . Extent . EndColumnNumber ,
279+ "@{}" ,
280+ filename ,
281+ GetDescription ( ) ) ) ;
282+ return correctionExtents ;
283+ }
284+
133285 public string GetCommonName ( )
134286 {
135- return Strings . UseLiteralInitilializerForHashtableCommonName ;
287+ return string . Format ( CultureInfo . CurrentCulture , Strings . UseLiteralInitilializerForHashtableCommonName ) ;
136288 }
137289
138290 public string GetDescription ( )
139291 {
140- return Strings . UseLiteralInitilializerForHashtableDescription ;
292+ return string . Format ( CultureInfo . CurrentCulture , Strings . UseLiteralInitilializerForHashtableDescription ) ;
141293 }
142294
143295 public string GetName ( )
144296 {
145- return Strings . UseLiteralInitilializerForHashtableName ;
297+ return string . Format (
298+ CultureInfo . CurrentCulture ,
299+ Strings . NameSpaceFormat ,
300+ GetSourceName ( ) ,
301+ Strings . UseLiteralInitilializerForHashtableName ) ;
146302 }
147303
148304 public RuleSeverity GetSeverity ( )
0 commit comments