@@ -33,6 +33,14 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules
3333#endif
3434 public class UseCompatibleCmdlets : AstVisitor , IScriptRule
3535 {
36+
37+ private struct RuleParameters
38+ {
39+ public string mode ;
40+ public string [ ] compatibility ;
41+ public string referencePlatform ;
42+ }
43+
3644 private List < DiagnosticRecord > diagnosticRecords ;
3745 private Dictionary < string , HashSet < string > > psCmdletMap ;
3846 private readonly List < string > validParameters ;
@@ -44,6 +52,7 @@ public class UseCompatibleCmdlets : AstVisitor, IScriptRule
4452 private bool hasInitializationError ;
4553 private string referencePlatform ;
4654 private readonly string defaultReferencePlatform = "desktop-5.1.14393.206-windows" ;
55+ private RuleParameters ruleParameters ;
4756
4857 public UseCompatibleCmdlets ( )
4958 {
@@ -176,12 +185,26 @@ public override AstVisitAction VisitCommand(CommandAst commandAst)
176185 /// </summary>
177186 private void GenerateDiagnosticRecords ( )
178187 {
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 ) )
180197 {
181- if ( ! curCmdletCompat . Value )
198+ return ;
199+ }
200+
201+ foreach ( var platform in ruleParameters . compatibility )
202+ {
203+ var curCmdletCompat = curCmdletCompatibilityMap [ platform ] ;
204+ if ( ! curCmdletCompat )
182205 {
183206 var cmdletName = curCmdletAst . GetCommandName ( ) ;
184- var platformInfo = platformSpecMap [ curCmdletCompat . Key ] ;
207+ var platformInfo = platformSpecMap [ platform ] ;
185208 var funcNameTokens = Helper . Instance . Tokens . Where (
186209 token =>
187210 Helper . ContainsExtent ( curCmdletAst . Extent , token . Extent )
@@ -262,18 +285,7 @@ private void SetupCmdletsDictionary()
262285 }
263286 }
264287
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 ( ) ;
277289 referencePlatform = defaultReferencePlatform ;
278290#if DEBUG
279291 // Setup reference file
@@ -283,53 +295,71 @@ private void SetupCmdletsDictionary()
283295 referencePlatform = referenceObject as string ;
284296 if ( referencePlatform == null )
285297 {
286- return ;
298+ referencePlatform = GetStringArgFromListStringArg ( referenceObject ) ;
299+ if ( referencePlatform == null )
300+ {
301+ return ;
302+ }
287303 }
288304 }
305+ #endif
306+ ruleParameters . referencePlatform = referencePlatform ;
307+
308+ // check if the reference file has valid platformSpec
309+ if ( ! IsValidPlatformString ( referencePlatform ) )
310+ {
311+ return ;
312+ }
289313
314+ string settingsPath ;
315+ settingsPath = GetShippedSettingsDirectory ( ) ;
316+ #if DEBUG
290317 object modeObject ;
291318 if ( ruleArgs . TryGetValue ( "mode" , out modeObject ) )
292319 {
293320 // This is for testing only. User should not be specifying mode!
294321 var mode = GetStringArgFromListStringArg ( modeObject ) ;
322+ ruleParameters . mode = mode ;
295323 switch ( mode )
296324 {
297325 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" ] ) ;
307327 break ;
308328
309329 case "online" : // not implemented yet.
310330 case null :
311331 default :
312- break ;
332+ return ;
313333 }
314334
315- return ;
316335 }
317336#endif
318-
319- // check if the reference file has valid platformSpec
320- if ( ! IsValidPlatformString ( referencePlatform ) )
337+ if ( settingsPath == null
338+ || ! ContainsReferenceFile ( settingsPath ) )
321339 {
322340 return ;
323341 }
324342
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 )
328345 {
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+ }
330354 }
331355
332- ProcessDirectory ( settingsPath ) ;
356+ ProcessDirectory (
357+ settingsPath ,
358+ extentedCompatibilityList ) ;
359+ if ( psCmdletMap . Keys . Count != extentedCompatibilityList . Count ( ) )
360+ {
361+ return ;
362+ }
333363
334364 // reached this point, so no error
335365 hasInitializationError = false ;
@@ -437,30 +467,10 @@ private string GetStringArgFromListStringArg(object arg)
437467 return strList [ 0 ] ;
438468 }
439469
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-
460470 /// <summary>
461471 /// Search a directory for files of form [PSEdition]-[PSVersion]-[OS].json
462472 /// </summary>
463- private void ProcessDirectory ( string path )
473+ private void ProcessDirectory ( string path , IEnumerable < string > acceptablePlatformSpecs )
464474 {
465475 foreach ( var filePath in Directory . EnumerateFiles ( path ) )
466476 {
@@ -472,36 +482,14 @@ private void ProcessDirectory(string path)
472482 }
473483
474484 var fileNameWithoutExt = Path . GetFileNameWithoutExtension ( filePath ) ;
475- if ( ! platformSpecMap . ContainsKey ( fileNameWithoutExt ) )
485+ if ( acceptablePlatformSpecs != null
486+ && ! acceptablePlatformSpecs . Contains ( fileNameWithoutExt , StringComparer . OrdinalIgnoreCase ) )
476487 {
477488 continue ;
478489 }
479490
480491 psCmdletMap [ fileNameWithoutExt ] = GetCmdletsFromData ( JObject . Parse ( File . ReadAllText ( filePath ) ) ) ;
481492 }
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- }
505493 }
506494
507495 /// <summary>
@@ -513,11 +501,20 @@ private HashSet<string> GetCmdletsFromData(dynamic deserializedObject)
513501 {
514502 var cmdlets = new HashSet < string > ( StringComparer . OrdinalIgnoreCase ) ;
515503 dynamic modules = deserializedObject . Modules ;
516- foreach ( var module in modules )
504+ foreach ( dynamic module in modules )
517505 {
518- foreach ( var cmdlet in module . ExportedCommands )
506+ if ( module . ExportedCommands == null )
507+ {
508+ continue ;
509+ }
510+
511+ foreach ( dynamic cmdlet in module . ExportedCommands )
519512 {
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+ }
521518 cmdlets . Add ( name ) ;
522519 }
523520 }
0 commit comments