diff --git a/PSCompatibilityAnalyzer/Microsoft.PowerShell.CrossCompatibility/Data/Types/AssemblyData.cs b/PSCompatibilityAnalyzer/Microsoft.PowerShell.CrossCompatibility/Data/Types/AssemblyData.cs index 5da42f383..c9026464b 100644 --- a/PSCompatibilityAnalyzer/Microsoft.PowerShell.CrossCompatibility/Data/Types/AssemblyData.cs +++ b/PSCompatibilityAnalyzer/Microsoft.PowerShell.CrossCompatibility/Data/Types/AssemblyData.cs @@ -24,7 +24,7 @@ public class AssemblyData : ICloneable /// and then type name. /// [DataMember(EmitDefaultValue = false)] - public JsonCaseInsensitiveStringDictionary> Types { get; set; } + public JsonDictionary> Types { get; set; } /// /// Create a deep clone of the assembly data object. @@ -34,7 +34,7 @@ public object Clone() return new AssemblyData() { AssemblyName = (AssemblyNameData)AssemblyName.Clone(), - Types = (JsonCaseInsensitiveStringDictionary>)Types?.Clone() + Types = (JsonDictionary>)Types?.Clone() }; } } diff --git a/PSCompatibilityAnalyzer/Microsoft.PowerShell.CrossCompatibility/Data/Types/AvailableTypeData.cs b/PSCompatibilityAnalyzer/Microsoft.PowerShell.CrossCompatibility/Data/Types/AvailableTypeData.cs index d721ce147..e876b1051 100644 --- a/PSCompatibilityAnalyzer/Microsoft.PowerShell.CrossCompatibility/Data/Types/AvailableTypeData.cs +++ b/PSCompatibilityAnalyzer/Microsoft.PowerShell.CrossCompatibility/Data/Types/AvailableTypeData.cs @@ -26,7 +26,7 @@ public class AvailableTypeData : ICloneable /// keyed by simple assembly name. /// [DataMember] - public JsonCaseInsensitiveStringDictionary Assemblies { get; set; } + public JsonDictionary Assemblies { get; set; } /// /// Create a deep clone of the available type data object. @@ -36,7 +36,7 @@ public object Clone() return new AvailableTypeData() { TypeAccelerators = (JsonCaseInsensitiveStringDictionary)TypeAccelerators.Clone(), - Assemblies = (JsonCaseInsensitiveStringDictionary)Assemblies.Clone() + Assemblies = (JsonDictionary)Assemblies.Clone() }; } } diff --git a/PSCompatibilityAnalyzer/Microsoft.PowerShell.CrossCompatibility/Data/Types/MemberData.cs b/PSCompatibilityAnalyzer/Microsoft.PowerShell.CrossCompatibility/Data/Types/MemberData.cs index 5206cae54..7ba92fedc 100644 --- a/PSCompatibilityAnalyzer/Microsoft.PowerShell.CrossCompatibility/Data/Types/MemberData.cs +++ b/PSCompatibilityAnalyzer/Microsoft.PowerShell.CrossCompatibility/Data/Types/MemberData.cs @@ -26,13 +26,13 @@ public class MemberData : ICloneable /// Fields on the type, keyed by field name. /// [DataMember(EmitDefaultValue = false)] - public JsonCaseInsensitiveStringDictionary Fields { get; set; } + public JsonDictionary Fields { get; set; } /// /// Properties on this type, keyed by property name. /// [DataMember(EmitDefaultValue = false)] - public JsonCaseInsensitiveStringDictionary Properties { get; set; } + public JsonDictionary Properties { get; set; } /// /// Indexers on the type. @@ -44,19 +44,19 @@ public class MemberData : ICloneable /// Methods on the type, keyed by method name. /// [DataMember(EmitDefaultValue = false)] - public JsonCaseInsensitiveStringDictionary Methods { get; set; } + public JsonDictionary Methods { get; set; } /// /// Events on the type, keyed by event name. /// [DataMember(EmitDefaultValue = false)] - public JsonCaseInsensitiveStringDictionary Events { get; set; } + public JsonDictionary Events { get; set; } /// /// Types nested within the type, keyed by type name. /// [DataMember(EmitDefaultValue = false)] - public JsonCaseInsensitiveStringDictionary NestedTypes { get; set; } + public JsonDictionary NestedTypes { get; set; } /// /// Create a deep clone of the member data object. @@ -66,12 +66,12 @@ public object Clone() return new MemberData() { Constructors = Constructors?.Select(c => (string[])c.Clone()).ToArray(), - Events = (JsonCaseInsensitiveStringDictionary)Events?.Clone(), - Fields = (JsonCaseInsensitiveStringDictionary)Fields?.Clone(), + Events = (JsonDictionary)Events?.Clone(), + Fields = (JsonDictionary)Fields?.Clone(), Indexers = Indexers?.Select(i => (IndexerData)i.Clone()).ToArray(), - Methods = (JsonCaseInsensitiveStringDictionary)Methods?.Clone(), - NestedTypes = (JsonCaseInsensitiveStringDictionary)NestedTypes?.Clone(), - Properties = (JsonCaseInsensitiveStringDictionary)Properties?.Clone(), + Methods = (JsonDictionary)Methods?.Clone(), + NestedTypes = (JsonDictionary)NestedTypes?.Clone(), + Properties = (JsonDictionary)Properties?.Clone(), }; } } diff --git a/PSCompatibilityAnalyzer/Microsoft.PowerShell.CrossCompatibility/Query/Types/AssemblyData.cs b/PSCompatibilityAnalyzer/Microsoft.PowerShell.CrossCompatibility/Query/Types/AssemblyData.cs index 74c6bc12d..2f996fd22 100644 --- a/PSCompatibilityAnalyzer/Microsoft.PowerShell.CrossCompatibility/Query/Types/AssemblyData.cs +++ b/PSCompatibilityAnalyzer/Microsoft.PowerShell.CrossCompatibility/Query/Types/AssemblyData.cs @@ -38,10 +38,10 @@ public AssemblyData(Data.Types.AssemblyData assemblyData) /// public IReadOnlyDictionary> Types => _types?.Value; - private static IReadOnlyDictionary> CreateTypeDictionary(IReadOnlyDictionary> typeData) + private static IReadOnlyDictionary> CreateTypeDictionary(IReadOnlyDictionary> typeData) { var namespaceDict = new Dictionary>(typeData.Count, StringComparer.OrdinalIgnoreCase); - foreach (KeyValuePair> nspace in typeData) + foreach (KeyValuePair> nspace in typeData) { var typeDict = new Dictionary(nspace.Value.Count, StringComparer.OrdinalIgnoreCase); foreach (KeyValuePair type in nspace.Value) diff --git a/PSCompatibilityAnalyzer/Microsoft.PowerShell.CrossCompatibility/Utility/ProfileCombination.cs b/PSCompatibilityAnalyzer/Microsoft.PowerShell.CrossCompatibility/Utility/ProfileCombination.cs index 314274b6d..dabdc869c 100644 --- a/PSCompatibilityAnalyzer/Microsoft.PowerShell.CrossCompatibility/Utility/ProfileCombination.cs +++ b/PSCompatibilityAnalyzer/Microsoft.PowerShell.CrossCompatibility/Utility/ProfileCombination.cs @@ -122,7 +122,7 @@ private static object Union(ParameterSetData thisParameterSet, ParameterSetData private static object Union(AvailableTypeData thisTypes, AvailableTypeData thatTypes) { - thisTypes.Assemblies = StringDictionaryUnion(thisTypes.Assemblies, thatTypes.Assemblies, Union); + thisTypes.Assemblies = DictionaryUnion(thisTypes.Assemblies, thatTypes.Assemblies, Union); thisTypes.TypeAccelerators = StringDictionaryUnion(thisTypes.TypeAccelerators, thatTypes.TypeAccelerators); return thisTypes; @@ -136,10 +136,10 @@ private static object Union(AssemblyData thisAssembly, AssemblyData thatAssembly { if (thisAssembly.Types == null) { - thisAssembly.Types = new JsonCaseInsensitiveStringDictionary>(); + thisAssembly.Types = new JsonDictionary>(); } - foreach (KeyValuePair> nspace in thatAssembly.Types) + foreach (KeyValuePair> nspace in thatAssembly.Types) { if (!thisAssembly.Types.ContainsKey(nspace.Key)) { @@ -147,7 +147,7 @@ private static object Union(AssemblyData thisAssembly, AssemblyData thatAssembly continue; } - thisAssembly.Types[nspace.Key] = StringDictionaryUnion(thisAssembly.Types[nspace.Key], nspace.Value, Union); + thisAssembly.Types[nspace.Key] = DictionaryUnion(thisAssembly.Types[nspace.Key], nspace.Value, Union); } } @@ -192,11 +192,11 @@ private static object Union(MemberData thisMembers, MemberData thatMembers) thisMembers.Constructors = ParameterUnion(thisMembers.Constructors, thatMembers.Constructors); - thisMembers.Events = StringDictionaryUnion(thisMembers.Events, thatMembers.Events); - thisMembers.Fields = StringDictionaryUnion(thisMembers.Fields, thatMembers.Fields); - thisMembers.Methods = StringDictionaryUnion(thisMembers.Methods, thatMembers.Methods, Union); - thisMembers.NestedTypes = StringDictionaryUnion(thisMembers.NestedTypes, thatMembers.NestedTypes, Union); - thisMembers.Properties = StringDictionaryUnion(thisMembers.Properties, thatMembers.Properties, Union); + thisMembers.Events = DictionaryUnion(thisMembers.Events, thatMembers.Events); + thisMembers.Fields = DictionaryUnion(thisMembers.Fields, thatMembers.Fields); + thisMembers.Methods = DictionaryUnion(thisMembers.Methods, thatMembers.Methods, Union); + thisMembers.NestedTypes = DictionaryUnion(thisMembers.NestedTypes, thatMembers.NestedTypes, Union); + thisMembers.Properties = DictionaryUnion(thisMembers.Properties, thatMembers.Properties, Union); return thisMembers; } diff --git a/PSCompatibilityAnalyzer/Microsoft.PowerShell.CrossCompatibility/Utility/TypeDataConversion.cs b/PSCompatibilityAnalyzer/Microsoft.PowerShell.CrossCompatibility/Utility/TypeDataConversion.cs index 0faa00854..c9f93f762 100644 --- a/PSCompatibilityAnalyzer/Microsoft.PowerShell.CrossCompatibility/Utility/TypeDataConversion.cs +++ b/PSCompatibilityAnalyzer/Microsoft.PowerShell.CrossCompatibility/Utility/TypeDataConversion.cs @@ -50,7 +50,7 @@ public static AvailableTypeData AssembleAvailableTypes( typeAcceleratorDict.Add(typeAccelerator.Key, ta); } - var asms = new JsonCaseInsensitiveStringDictionary(); + var asms = new JsonDictionary(); foreach (Assembly asm in assemblies) { // Don't want to include this module or assembly in the output @@ -61,8 +61,17 @@ public static AvailableTypeData AssembleAvailableTypes( try { + // First check whether an assembly with this name already exists + // Only replace it if the current one is newer + AssemblyName asmName = asm.GetName(); + if (asms.TryGetValue(asmName.Name, out AssemblyData currentAssemblyData) + && asmName.Version < currentAssemblyData.AssemblyName.Version) + { + continue; + } + KeyValuePair asmData = AssembleAssembly(asm); - asms.Add(asmData.Key, asmData.Value); + asms[asmData.Key] = asmData.Value; } catch (ReflectionTypeLoadException e) { @@ -96,10 +105,10 @@ public static KeyValuePair AssembleAssembly(Assembly asm) }; Type[] types = asm.GetTypes(); - JsonCaseInsensitiveStringDictionary> namespacedTypes = null; + JsonDictionary> namespacedTypes = null; if (types.Any()) { - namespacedTypes = new JsonCaseInsensitiveStringDictionary>(); + namespacedTypes = new JsonDictionary>(); foreach (Type type in asm.GetTypes()) { if (!type.IsPublic) @@ -110,14 +119,14 @@ public static KeyValuePair AssembleAssembly(Assembly asm) // Some types don't have a namespace, but we still want to file them string typeNamespace = type.Namespace ?? ""; - if (!namespacedTypes.ContainsKey(typeNamespace)) + if (!namespacedTypes.TryGetValue(typeNamespace, out JsonDictionary typeDictionary)) { - namespacedTypes.Add(typeNamespace, new JsonCaseInsensitiveStringDictionary()); + typeDictionary = new JsonDictionary(); } TypeData typeData = AssembleType(type); - namespacedTypes[typeNamespace][type.Name] = typeData; + typeDictionary[type.Name] = typeData; } } @@ -271,12 +280,12 @@ private static MemberData AssembleMembers(Type type, BindingFlags memberBinding) return new MemberData() { Constructors = constructors.Any() ? constructors.Select(c => AssembleConstructor(c)).ToArray() : null, - Events = events.Any() ? new JsonCaseInsensitiveStringDictionary(events.ToDictionary(e => e.Name, e => AssembleEvent(e), StringComparer.OrdinalIgnoreCase)) : null, - Fields = fields.Any() ? new JsonCaseInsensitiveStringDictionary(fields.ToDictionary(f => f.Name, f => AssembleField(f), StringComparer.OrdinalIgnoreCase)) : null, + Events = events.Any() ? new JsonDictionary(events.ToDictionary(e => e.Name, e => AssembleEvent(e))) : null, + Fields = fields.Any() ? new JsonDictionary(fields.ToDictionary(f => f.Name, f => AssembleField(f))) : null, Indexers = indexers.Any() ? indexers.Select(i => AssembleIndexer(i)).ToArray() : null, - Methods = methods.Any() ? new JsonCaseInsensitiveStringDictionary(methods.ToDictionary(m => m.Key, m => AssembleMethod(m.Value), StringComparer.OrdinalIgnoreCase)) : null, - NestedTypes = nestedTypes.Any() ? new JsonCaseInsensitiveStringDictionary(nestedTypes.ToDictionary(t => t.Name, t => AssembleType(t), StringComparer.OrdinalIgnoreCase)) : null, - Properties = properties.Any() ? new JsonCaseInsensitiveStringDictionary(properties.ToDictionary(p => p.Name, p => AssembleProperty(p), StringComparer.OrdinalIgnoreCase)) : null + Methods = methods.Any() ? new JsonDictionary(methods.ToDictionary(m => m.Key, m => AssembleMethod(m.Value))) : null, + NestedTypes = nestedTypes.Any() ? new JsonDictionary(nestedTypes.ToDictionary(t => t.Name, t => AssembleType(t))) : null, + Properties = properties.Any() ? new JsonDictionary(properties.ToDictionary(p => p.Name, p => AssembleProperty(p))) : null }; } diff --git a/PSCompatibilityAnalyzer/Tests/QueryApi.Tests.ps1 b/PSCompatibilityAnalyzer/Tests/QueryApi.Tests.ps1 new file mode 100644 index 000000000..a374f40b7 --- /dev/null +++ b/PSCompatibilityAnalyzer/Tests/QueryApi.Tests.ps1 @@ -0,0 +1,36 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +Describe ".NET type with members with names differing only by name" { + BeforeAll { + Add-Type -TypeDefinition @' +namespace PSScriptAnalyzerTests +{ + public class QueryApiTestObject + { + public string JobId { get; set; } + + public object JOBID { get; set; } + } +} +'@ + } + + It "Does not crash the query API" { + $typeData = [Microsoft.PowerShell.CrossCompatibility.Utility.TypeDataConversion]::AssembleType([PSScriptAnalyzerTests.QueryApiTestObject]) + + $typeQueryObject = New-Object 'Microsoft.PowerShell.CrossCompatibility.Query.TypeData' ('QueryApiTestObject', $typeData) + + $typeData.Instance.Properties.Count | Should -Be 2 + + $typeData.Instance.Properties.ContainsKey('JobId') | Should -BeTrue + $typeData.Instance.Properties.ContainsKey('JOBID') | Should -BeTrue + $typeData.Instance.Properties.ContainsKey('jobid') | Should -Not -BeTrue + + $typeQueryObject.Instance.Properties.Count | Should -Be 1 + + $typeQueryObject.Instance.Properties.ContainsKey('JobId') | Should -BeTrue + $typeQueryObject.Instance.Properties.ContainsKey('JobID') | Should -BeTrue + $typeQueryObject.Instance.Properties.ContainsKey('jobid') | Should -BeTrue + } +} \ No newline at end of file