@@ -33,6 +33,14 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules
33
33
#endif
34
34
public class UseCompatibleCmdlets : AstVisitor , IScriptRule
35
35
{
36
+
37
+ private struct RuleParameters
38
+ {
39
+ public string mode ;
40
+ public string [ ] compatibility ;
41
+ public string referencePlatform ;
42
+ }
43
+
36
44
private List < DiagnosticRecord > diagnosticRecords ;
37
45
private Dictionary < string , HashSet < string > > psCmdletMap ;
38
46
private readonly List < string > validParameters ;
@@ -44,6 +52,7 @@ public class UseCompatibleCmdlets : AstVisitor, IScriptRule
44
52
private bool hasInitializationError ;
45
53
private string referencePlatform ;
46
54
private readonly string defaultReferencePlatform = "desktop-5.1.14393.206-windows" ;
55
+ private RuleParameters ruleParameters ;
47
56
48
57
public UseCompatibleCmdlets ( )
49
58
{
@@ -176,12 +185,26 @@ public override AstVisitAction VisitCommand(CommandAst commandAst)
176
185
/// </summary>
177
186
private void GenerateDiagnosticRecords ( )
178
187
{
179
- foreach ( var curCmdletCompat in curCmdletCompatibilityMap )
188
+ bool referenceCompatibility = curCmdletCompatibilityMap [ referencePlatform ] ;
189
+ bool requestedCompatibility = ruleParameters . compatibility . Any ( x => curCmdletCompatibilityMap [ x ] ) ;
190
+
191
+ // If the command is present in reference platform but not in any of the given platforms.
192
+ // Or if the command is not present in reference platform but present in any of the given platforms
193
+ // then declare it as an incompatible cmdlet.
194
+ // If it is present neither in reference platform nor any given platforms, then it is probably a
195
+ // non-builtin command and hence do not declare it as an incompatible cmdlet.
196
+ if ( ! ( referenceCompatibility ^ requestedCompatibility ) )
180
197
{
181
- if ( ! curCmdletCompat . Value )
198
+ return ;
199
+ }
200
+
201
+ foreach ( var platform in ruleParameters . compatibility )
202
+ {
203
+ var curCmdletCompat = curCmdletCompatibilityMap [ platform ] ;
204
+ if ( ! curCmdletCompat )
182
205
{
183
206
var cmdletName = curCmdletAst . GetCommandName ( ) ;
184
- var platformInfo = platformSpecMap [ curCmdletCompat . Key ] ;
207
+ var platformInfo = platformSpecMap [ platform ] ;
185
208
var funcNameTokens = Helper . Instance . Tokens . Where (
186
209
token =>
187
210
Helper . ContainsExtent ( curCmdletAst . Extent , token . Extent )
@@ -262,18 +285,7 @@ private void SetupCmdletsDictionary()
262
285
}
263
286
}
264
287
265
- foreach ( var compat in compatibilityList )
266
- {
267
- string psedition , psversion , os ;
268
-
269
- // ignore (warn) invalid entries
270
- if ( GetVersionInfoFromPlatformString ( compat , out psedition , out psversion , out os ) )
271
- {
272
- platformSpecMap . Add ( compat , new { PSEdition = psedition , PSVersion = psversion , OS = os } ) ;
273
- curCmdletCompatibilityMap . Add ( compat , true ) ;
274
- }
275
- }
276
-
288
+ ruleParameters . compatibility = compatibilityList . ToArray ( ) ;
277
289
referencePlatform = defaultReferencePlatform ;
278
290
#if DEBUG
279
291
// Setup reference file
@@ -283,53 +295,71 @@ private void SetupCmdletsDictionary()
283
295
referencePlatform = referenceObject as string ;
284
296
if ( referencePlatform == null )
285
297
{
286
- return ;
298
+ referencePlatform = GetStringArgFromListStringArg ( referenceObject ) ;
299
+ if ( referencePlatform == null )
300
+ {
301
+ return ;
302
+ }
287
303
}
288
304
}
305
+ #endif
306
+ ruleParameters . referencePlatform = referencePlatform ;
307
+
308
+ // check if the reference file has valid platformSpec
309
+ if ( ! IsValidPlatformString ( referencePlatform ) )
310
+ {
311
+ return ;
312
+ }
289
313
314
+ string settingsPath ;
315
+ settingsPath = GetShippedSettingsDirectory ( ) ;
316
+ #if DEBUG
290
317
object modeObject ;
291
318
if ( ruleArgs . TryGetValue ( "mode" , out modeObject ) )
292
319
{
293
320
// This is for testing only. User should not be specifying mode!
294
321
var mode = GetStringArgFromListStringArg ( modeObject ) ;
322
+ ruleParameters . mode = mode ;
295
323
switch ( mode )
296
324
{
297
325
case "offline" :
298
- var uri = GetStringArgFromListStringArg ( ruleArgs [ "uri" ] ) ;
299
- if ( uri == null
300
- || ! Directory . Exists ( uri )
301
- || ! ContainsReferenceFile ( uri ) )
302
- {
303
- return ;
304
- }
305
-
306
- ProcessDirectory ( uri ) ;
326
+ settingsPath = GetStringArgFromListStringArg ( ruleArgs [ "uri" ] ) ;
307
327
break ;
308
328
309
329
case "online" : // not implemented yet.
310
330
case null :
311
331
default :
312
- break ;
332
+ return ;
313
333
}
314
334
315
- return ;
316
335
}
317
336
#endif
318
-
319
- // check if the reference file has valid platformSpec
320
- if ( ! IsValidPlatformString ( referencePlatform ) )
337
+ if ( settingsPath == null
338
+ || ! ContainsReferenceFile ( settingsPath ) )
321
339
{
322
340
return ;
323
341
}
324
342
325
- var settingsPath = GetShippedSettingsDirectory ( ) ;
326
- if ( settingsPath == null
327
- || ! ContainsReferenceFile ( settingsPath ) )
343
+ var extentedCompatibilityList = compatibilityList . Concat ( Enumerable . Repeat ( referencePlatform , 1 ) ) ;
344
+ foreach ( var compat in extentedCompatibilityList )
328
345
{
329
- return ;
346
+ string psedition , psversion , os ;
347
+
348
+ // ignore (warn) invalid entries
349
+ if ( GetVersionInfoFromPlatformString ( compat , out psedition , out psversion , out os ) )
350
+ {
351
+ platformSpecMap . Add ( compat , new { PSEdition = psedition , PSVersion = psversion , OS = os } ) ;
352
+ curCmdletCompatibilityMap . Add ( compat , true ) ;
353
+ }
330
354
}
331
355
332
- ProcessDirectory ( settingsPath ) ;
356
+ ProcessDirectory (
357
+ settingsPath ,
358
+ extentedCompatibilityList ) ;
359
+ if ( psCmdletMap . Keys . Count != extentedCompatibilityList . Count ( ) )
360
+ {
361
+ return ;
362
+ }
333
363
334
364
// reached this point, so no error
335
365
hasInitializationError = false ;
@@ -437,30 +467,10 @@ private string GetStringArgFromListStringArg(object arg)
437
467
return strList [ 0 ] ;
438
468
}
439
469
440
- /// <summary>
441
- /// Process arguments when 'offline' mode is specified
442
- /// </summary>
443
- private void ProcessOfflineModeArgs ( Dictionary < string , object > ruleArgs )
444
- {
445
- var uri = GetStringArgFromListStringArg ( ruleArgs [ "uri" ] ) ;
446
- if ( uri == null )
447
- {
448
- // TODO: log this
449
- return ;
450
- }
451
- if ( ! Directory . Exists ( uri ) )
452
- {
453
- // TODO: log this
454
- return ;
455
- }
456
-
457
- ProcessDirectory ( uri ) ;
458
- }
459
-
460
470
/// <summary>
461
471
/// Search a directory for files of form [PSEdition]-[PSVersion]-[OS].json
462
472
/// </summary>
463
- private void ProcessDirectory ( string path )
473
+ private void ProcessDirectory ( string path , IEnumerable < string > acceptablePlatformSpecs )
464
474
{
465
475
foreach ( var filePath in Directory . EnumerateFiles ( path ) )
466
476
{
@@ -472,36 +482,14 @@ private void ProcessDirectory(string path)
472
482
}
473
483
474
484
var fileNameWithoutExt = Path . GetFileNameWithoutExtension ( filePath ) ;
475
- if ( ! platformSpecMap . ContainsKey ( fileNameWithoutExt ) )
485
+ if ( acceptablePlatformSpecs != null
486
+ && ! acceptablePlatformSpecs . Contains ( fileNameWithoutExt , StringComparer . OrdinalIgnoreCase ) )
476
487
{
477
488
continue ;
478
489
}
479
490
480
491
psCmdletMap [ fileNameWithoutExt ] = GetCmdletsFromData ( JObject . Parse ( File . ReadAllText ( filePath ) ) ) ;
481
492
}
482
-
483
- RemoveUnavailableKeys ( ) ;
484
- }
485
-
486
- /// <summary>
487
- /// Remove keys that are not present in psCmdletMap but present in platformSpecMap and curCmdletCompatibilityMap
488
- /// </summary>
489
- private void RemoveUnavailableKeys ( )
490
- {
491
- var keysToRemove = new List < string > ( ) ;
492
- foreach ( var key in platformSpecMap . Keys )
493
- {
494
- if ( ! psCmdletMap . ContainsKey ( key ) )
495
- {
496
- keysToRemove . Add ( key ) ;
497
- }
498
- }
499
-
500
- foreach ( var key in keysToRemove )
501
- {
502
- platformSpecMap . Remove ( key ) ;
503
- curCmdletCompatibilityMap . Remove ( key ) ;
504
- }
505
493
}
506
494
507
495
/// <summary>
@@ -513,11 +501,20 @@ private HashSet<string> GetCmdletsFromData(dynamic deserializedObject)
513
501
{
514
502
var cmdlets = new HashSet < string > ( StringComparer . OrdinalIgnoreCase ) ;
515
503
dynamic modules = deserializedObject . Modules ;
516
- foreach ( var module in modules )
504
+ foreach ( dynamic module in modules )
517
505
{
518
- foreach ( var cmdlet in module . ExportedCommands )
506
+ if ( module . ExportedCommands == null )
507
+ {
508
+ continue ;
509
+ }
510
+
511
+ foreach ( dynamic cmdlet in module . ExportedCommands )
519
512
{
520
- var name = cmdlet . Name . Value as string ;
513
+ var name = cmdlet . Name as string ;
514
+ if ( name == null )
515
+ {
516
+ name = cmdlet . Name . ToObject < string > ( ) ;
517
+ }
521
518
cmdlets . Add ( name ) ;
522
519
}
523
520
}
0 commit comments