Fix a regression in formatting caused by the FirstOrDefault primitive added in 7.0.0-Preview1 (#11258)

This commit is contained in:
Dongbo Wang
2019-12-11 15:37:18 -08:00
committed by Aditya Patwardhan
parent 4fb5f94a3b
commit 5605bf4195
3 changed files with 147 additions and 35 deletions
@@ -1983,13 +1983,39 @@ namespace System.Management.Automation
#endregion base
}
/// <summary>
/// The abstract cache entry type.
/// All specific cache entry types should derive from it.
/// </summary>
internal abstract class CacheEntry
{
/// <summary>
/// Gets the boolean value to indicate if the member is hidden.
/// </summary>
/// <remarks>
/// Currently, we only check the 'HiddenAttribute' declared for properties and methods,
/// because it can be done for them through the 'hidden' keyword in PowerShell Class.
///
/// We can't currently write a parameterized property in a PowerShell class so it's not too important
/// to check for the 'HiddenAttribute' for parameterized properties. But if someone added the attribute
/// to their C#, it'd be good to set this property correctly.
/// </remarks>
internal virtual bool IsHidden => false;
}
/// <summary>
/// Ordered and case insensitive hashtable.
/// </summary>
internal class CacheTable
{
/// <summary>
/// An object collection is used to help make populating method cache table more efficient
/// <see cref="DotNetAdapter.PopulateMethodReflectionTable(Type, CacheTable, BindingFlags)"/>.
/// </summary>
internal Collection<object> memberCollection;
private Dictionary<string, int> _indexes;
internal CacheTable()
{
memberCollection = new Collection<object>();
@@ -2012,17 +2038,30 @@ namespace System.Management.Automation
return null;
}
return this.memberCollection[indexObj];
return memberCollection[indexObj];
}
}
/// <summary>
/// Get the first non-hidden member that satisfies the predicate.
/// </summary>
/// <remarks>
/// Hidden members are not returned for any fuzzy searches (searching by 'match' or enumerating a collection).
/// A hidden member is returned only if the member name is explicitly looked for.
/// </remarks>
internal object GetFirstOrDefault(MemberNamePredicate predicate)
{
foreach (var entry in _indexes)
{
if (predicate(entry.Key))
{
return this.memberCollection[entry.Value];
object member = memberCollection[entry.Value];
if (member is CacheEntry cacheEntry && cacheEntry.IsHidden)
{
continue;
}
return member;
}
}
@@ -2565,9 +2604,9 @@ namespace System.Management.Automation
private static readonly Dictionary<Type, Dictionary<string, EventCacheEntry>> s_staticEventCacheTable
= new Dictionary<Type, Dictionary<string, EventCacheEntry>>();
internal class MethodCacheEntry
internal class MethodCacheEntry : CacheEntry
{
internal MethodInformation[] methodInformationStructures;
internal readonly MethodInformation[] methodInformationStructures;
/// <summary>
/// Cache delegate to the ctor of PSMethod&lt;&gt; with a template parameter derived from the methodInformationStructures.
/// </summary>
@@ -2585,9 +2624,33 @@ namespace System.Management.Automation
return methodInformationStructures[i];
}
}
private bool? _isHidden;
internal override bool IsHidden
{
get
{
if (_isHidden == null)
{
bool hasHiddenAttribute = false;
foreach (var method in methodInformationStructures)
{
if (method.method.GetCustomAttributes(typeof(HiddenAttribute), inherit: false).Length != 0)
{
hasHiddenAttribute = true;
break;
}
}
_isHidden = hasHiddenAttribute;
}
return _isHidden.Value;
}
}
}
internal class EventCacheEntry
internal class EventCacheEntry : CacheEntry
{
internal EventInfo[] events;
@@ -2597,7 +2660,7 @@ namespace System.Management.Automation
}
}
internal class ParameterizedPropertyCacheEntry
internal class ParameterizedPropertyCacheEntry : CacheEntry
{
internal MethodInformation[] getterInformation;
internal MethodInformation[] setterInformation;
@@ -2676,7 +2739,7 @@ namespace System.Management.Automation
}
}
internal class PropertyCacheEntry
internal class PropertyCacheEntry : CacheEntry
{
internal delegate object GetterDelegate(object instance);
internal delegate void SetterDelegate(object instance, object setValue);
@@ -2916,6 +2979,20 @@ namespace System.Management.Automation
internal bool isStatic;
internal Type propertyType;
private bool? _isHidden;
internal override bool IsHidden
{
get
{
if (_isHidden == null)
{
_isHidden = member.GetCustomAttributes(typeof(HiddenAttribute), inherit: false).Length != 0;
}
return _isHidden.Value;
}
}
private AttributeCollection _attributes;
internal AttributeCollection Attributes
{
@@ -3578,8 +3655,7 @@ namespace System.Management.Automation
case null:
return null;
case PropertyCacheEntry cacheEntry when lookingForProperties:
var isHidden = cacheEntry.member.GetCustomAttributes(typeof(HiddenAttribute), false).Any();
return new PSProperty(cacheEntry.member.Name, this, obj, cacheEntry) { IsHidden = isHidden } as T;
return new PSProperty(cacheEntry.member.Name, this, obj, cacheEntry) { IsHidden = cacheEntry.IsHidden } as T;
case ParameterizedPropertyCacheEntry paramCacheEntry when lookingForParameterizedProperties:
// TODO: check for HiddenAttribute
@@ -3612,17 +3688,8 @@ namespace System.Management.Automation
var isCtor = methods[0].method is ConstructorInfo;
bool isSpecial = !isCtor && methods[0].method.IsSpecialName;
bool isHidden = false;
foreach (var method in methods.methodInformationStructures)
{
if (method.method.GetCustomAttributes(typeof(HiddenAttribute), false).Any())
{
isHidden = true;
break;
}
}
return PSMethod.Create(methods[0].method.Name, this, obj, methods, isSpecial, isHidden) as T;
return PSMethod.Create(methods[0].method.Name, this, obj, methods, isSpecial, methods.IsHidden) as T;
}
internal T GetDotNetProperty<T>(object obj, string propertyName) where T : PSMemberInfo
@@ -3712,10 +3779,12 @@ namespace System.Management.Automation
{
if (!ignoreDuplicates || (members[propertyEntry.member.Name] == null))
{
var isHidden = propertyEntry.member.GetCustomAttributes(typeof(HiddenAttribute), false).Any();
members.Add(new PSProperty(propertyEntry.member.Name, this,
obj, propertyEntry)
{ IsHidden = isHidden } as T);
members.Add(
new PSProperty(
name: propertyEntry.member.Name,
adapter: this,
baseObject: obj,
adapterData: propertyEntry) { IsHidden = propertyEntry.IsHidden } as T);
}
}
}
@@ -3754,17 +3823,7 @@ namespace System.Management.Automation
if (!ignoreDuplicates || (members[name] == null))
{
bool isSpecial = !isCtor && method[0].method.IsSpecialName;
bool isHidden = false;
foreach (var m in method.methodInformationStructures)
{
if (m.method.GetCustomAttributes(typeof(HiddenAttribute), false).Any())
{
isHidden = true;
break;
}
}
members.Add(PSMethod.Create(name, this, obj, method, isSpecial, isHidden) as T);
members.Add(PSMethod.Create(name, this, obj, method, isSpecial, method.IsHidden) as T);
}
}
}
@@ -32,7 +32,7 @@ namespace System.Management.Automation
/// by Get-Member cmdlet, original MethodData and computed method information such
/// as whether a method is static etc.
/// </summary>
internal class WMIMethodCacheEntry
internal class WMIMethodCacheEntry : CacheEntry
{
public string Name { get; }
@@ -0,0 +1,53 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
Describe "Hidden properties should not be returned by the 'FirstOrDefault' primitive" -Tag CI {
It "Formatting for an object with no property/field should use 'ToString'" {
class Empty {
[String]ToString() { return 'MyString' }
}
$outstring = [Empty]::new() | Out-String
$outstring.Trim() | Should -BeExactly "MyString"
class Empty2 { }
$outstring = [Empty2]::new() | Out-String
$outstring.Trim() | Should -BeLike "*.Empty2"
}
It "Formatting for an object with only hidden property should use 'ToString'" {
class Hidden {
hidden $Param = 'Foo'
[String]ToString() { return 'MyString' }
}
$outstring = [Hidden]::new() | Out-String
$outstring.Trim() | Should -BeExactly "MyString"
class Hidden2 {
hidden $Param = 'Foo'
}
$outstring = [Hidden2]::new() | Out-String
$outstring.Trim() | Should -BeLike "*.Hidden2"
}
It 'Formatting for an object with no-hidden property should use the default view' {
class Params {
$Param = 'Foo'
[String]ToString() { return 'MyString' }
}
$outstring = [Params]::new() | Out-String
$outstring.Trim() | Should -BeExactly "Param$([System.Environment]::NewLine)-----$([System.Environment]::NewLine)Foo"
class Params2 {
$Param = 'Foo'
}
$outstring = [Params2]::new() | Out-String
$outstring.Trim() | Should -BeExactly "Param$([System.Environment]::NewLine)-----$([System.Environment]::NewLine)Foo"
}
}