Skip to content

Commit 78c848e

Browse files
author
Kapil Borle
committed
Check command compatibility in reference platform
1 parent 0477d08 commit 78c848e

File tree

1 file changed

+80
-83
lines changed

1 file changed

+80
-83
lines changed

Rules/UseCompatibleCmdlets.cs

Lines changed: 80 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)