From 673958a0380b04726bae1bb5c72b09a724da0ca9 Mon Sep 17 00:00:00 2001 From: Patrick Meinecke Date: Mon, 2 May 2022 18:28:22 -0700 Subject: [PATCH] Add `-Decoration` to `Find-Type` (#44) Fixes #43 * Move the decoration parameter to be a shared parameter for both `Find-Member` and `Find-Type`. * Also fallback to a global search when resolving attribute type if the type is not in the BCL or embedded in the target assembly. --- docs/en-US/Find-Member.md | 36 ++++++++--------- docs/en-US/Find-Type.md | 22 +++++++++- .../Commands/FindMemberCommand.cs | 10 +---- .../FindReflectionObjectCommandBase.cs | 21 +++------- src/ClassExplorer/Commands/FindTypeCommand.cs | 1 + src/ClassExplorer/MemberSearchOptions.cs | 2 - src/ClassExplorer/ReflectionSearchOptions.cs | 2 + .../Signatures/DecorationSignature.cs | 40 ++++++++++++++++++- src/ClassExplorer/TypeSearch.cs | 10 +++++ 9 files changed, 97 insertions(+), 47 deletions(-) diff --git a/docs/en-US/Find-Member.md b/docs/en-US/Find-Member.md index 78f53ef..854aad9 100644 --- a/docs/en-US/Find-Member.md +++ b/docs/en-US/Find-Member.md @@ -16,13 +16,13 @@ Find properties, methods, fields, etc that fit specific criteria. ```powershell -Find-Member [[-FilterScript] ] [-ParameterType ] [-GenericParameter ] [-ParameterCount ] [-GenericParameterCount ] [-ReturnType ] [-IncludeSpecialName] [-Decoration ] [-MemberType ] [-Static] [-Instance] [-Abstract] [-Virtual] [-Declared] [-IncludeObject] [-RecurseNestedType] [-Extension] [-Name ] [-Force] [-RegularExpression] [-InputObject ] [-Not] [-ResolutionMap ] [-AccessView ] [] +Find-Member [[-FilterScript] ] [-ParameterType ] [-GenericParameter ] [-ParameterCount ] [-GenericParameterCount ] [-ReturnType ] [-IncludeSpecialName] [-MemberType ] [-Static] [-Instance] [-Abstract] [-Virtual] [-Declared] [-IncludeObject] [-RecurseNestedType] [-Extension] [-Name ] [-Force] [-RegularExpression] [-InputObject ] [-Not] [-ResolutionMap ] [-AccessView ] [-Decoration ] [] ``` ### ByName ```powershell -Find-Member [[-Name] ] [-ParameterType ] [-GenericParameter ] [-ParameterCount ] [-GenericParameterCount ] [-ReturnType ] [-IncludeSpecialName] [-Decoration ] [-MemberType ] [-Static] [-Instance] [-Abstract] [-Virtual] [-Declared] [-IncludeObject] [-RecurseNestedType] [-Extension] [-FilterScript ] [-Force] [-RegularExpression] [-InputObject ] [-Not] [-ResolutionMap ] [-AccessView ] [] +Find-Member [[-Name] ] [-ParameterType ] [-GenericParameter ] [-ParameterCount ] [-GenericParameterCount ] [-ReturnType ] [-IncludeSpecialName] [-MemberType ] [-Static] [-Instance] [-Abstract] [-Virtual] [-Declared] [-IncludeObject] [-RecurseNestedType] [-Extension] [-FilterScript ] [-Force] [-RegularExpression] [-InputObject ] [-Not] [-ResolutionMap ] [-AccessView ] [-Decoration ] [] ``` ## DESCRIPTION @@ -36,7 +36,7 @@ The Find-Member cmdlet searches the process for type members that fit specified ```powershell Find-Member GetPowerShell -# ReflectedType: ScriptBlock +# ReflectedType: System.Management.Automation.ScriptBlock # # Name MemberType Definition # ---- ---------- ---------- @@ -54,7 +54,7 @@ Find all members in the AppDomain with the name "GetPowerShell" ```powershell [System.IO.Stream] | Find-Member -ParameterType { [anyof[Span[any], Memory[any]]] } -# ReflectedType: Stream +# ReflectedType: System.IO.Stream # # Name MemberType Definition # ---- ---------- ---------- @@ -69,19 +69,19 @@ Find all members that take a `Span<>` or a `Memory<>` as a parameter. ```powershell Find-Member -ParameterCount 0 -GenericParameter { [T[new]] } -# ReflectedType: InlineParserList +# ReflectedType: Markdig.Parsers.InlineParserList # # Name MemberType Definition # ---- ---------- ---------- # AddIfNotAlready Method public void AddIfNotAlready(); # -# ReflectedType: ParserList +# ReflectedType: Markdig.Parsers.ParserList # # Name MemberType Definition # ---- ---------- ---------- # AddIfNotAlready Method public void AddIfNotAlready(); # -# ReflectedType: OrderedList +# ReflectedType: Markdig.Parsers.OrderedList # # Name MemberType Definition # ---- ---------- ---------- @@ -95,13 +95,13 @@ Find all methods with no parameters and with a generic parameter with the `new` ```powershell Find-Member Emit -ParameterCount ..1, 7..8, 10.. -# ReflectedType: ILGenerator +# ReflectedType: System.Reflection.Emit.ILGenerator # # Name MemberType Definition # ---- ---------- ---------- # Emit Method public virtual void Emit(OpCode opcode); # -# ReflectedType: Compilation +# ReflectedType: Microsoft.CodeAnalysis.Compilation # # Name MemberType Definition # ---- ---------- ---------- @@ -110,11 +110,11 @@ Find-Member Emit -ParameterCount ..1, 7..8, 10.. # Emit Method public EmitResult Emit(Stream peStream, St… # Emit Method public EmitResult Emit(Stream peStream, St… # -# ReflectedType: FileSystemExtensions +# ReflectedType: Microsoft.CodeAnalysis.FileSystemExtensions # # Name MemberType Definition # ---- ---------- ---------- -# Emit Method public static EmitResult Emit(Compilation … +# Emit Method public static EmitResult Emit(this Compila… ``` Find all methods named `Emit` whose parameter count is any of the following: @@ -128,19 +128,19 @@ Find all methods named `Emit` whose parameter count is any of the following: ```powershell Find-Member -ReturnType System.Management.Automation.Language.Ast -Static -# ReflectedType: CommandCompletion +# ReflectedType: System.Management.Automation.CommandCompletion # # Name MemberType Definition # ---- ---------- ---------- # MapStringInputToPars… Method public static Tuple MapStringI… # -# ReflectedType: UsingExpressionAst +# ReflectedType: System.Management.Automation.Language.UsingExpressionAst # # Name MemberType Definition # ---- ---------- ---------- # ExtractUsingVariable Method public static VariableExpressionAst ExtractUsingVariable(Usin… # -# ReflectedType: Parser +# ReflectedType: System.Management.Automation.Language.Parser # # Name MemberType Definition # ---- ---------- ---------- @@ -156,13 +156,13 @@ Find all static members in the AppDomain that return any type of AST. ```powershell Find-Member -ParameterType runspace -Virtual -# ReflectedType: IHostSupportsInteractiveSession +# ReflectedType: System.Management.Automation.Host.IHostSupportsInteractiveSession # # Name MemberType Definition # ---- ---------- ---------- # PushRunspace Method public abstract void PushRunspace(Runspace runspace); # -# ReflectedType: IPSConsoleReadLineMockableMethods +# ReflectedType: Microsoft.PowerShell.Internal.IPSConsoleReadLineMockableMethods # # Name MemberType Definition # ---- ---------- ---------- @@ -176,7 +176,7 @@ Find all virtual members in the AppDomain that take any runspace type as a param ```powershell Find-Member Parse* -ParameterType System.Management.Automation.Language.Token -# ReflectedType: Parser +# ReflectedType: System.Management.Automation.Language.Parser # # Name MemberType Definition # ---- ---------- ---------- @@ -193,7 +193,7 @@ demonstrates how this will even match the element of a type that is both an arra ```powershell [runspace] | Find-Member -Force -Abstract | Find-Member -Not -AccessView Child -# ReflectedType: Runspace +# ReflectedType: System.Management.Automation.Runspaces.Runspace # # Name MemberType Definition # ---- ---------- ---------- diff --git a/docs/en-US/Find-Type.md b/docs/en-US/Find-Type.md index 5ed0b4c..cc72a9f 100644 --- a/docs/en-US/Find-Type.md +++ b/docs/en-US/Find-Type.md @@ -15,13 +15,13 @@ Find .NET classes in the AppDomain. ### ByFilter (Default) ```powershell -Find-Type [[-FilterScript] ] [[-Namespace] ] [-Name ] [-FullName ] [-InheritsType ] [-ImplementsInterface ] [-Signature ] [-Abstract] [-Static] [-Sealed] [-Interface] [-ValueType] [-Force] [-RegularExpression] [-InputObject ] [-Not] [-ResolutionMap ] [-AccessView ] [] +Find-Type [[-FilterScript] ] [[-Namespace] ] [-Name ] [-FullName ] [-InheritsType ] [-ImplementsInterface ] [-Signature ] [-Abstract] [-Static] [-Sealed] [-Interface] [-ValueType] [-Force] [-RegularExpression] [-InputObject ] [-Not] [-ResolutionMap ] [-AccessView ] [-Decoration ] [] ``` ### ByName ```powershell -Find-Type [[-Name] ] [[-Namespace] ] [-FullName ] [-InheritsType ] [-ImplementsInterface ] [-Signature ] [-Abstract] [-Static] [-Sealed] [-Interface] [-ValueType] [-FilterScript ] [-Force] [-RegularExpression] [-InputObject ] [-Not] [-ResolutionMap ] [-AccessView ] [] +Find-Type [[-Name] ] [[-Namespace] ] [-FullName ] [-InheritsType ] [-ImplementsInterface ] [-Signature ] [-Abstract] [-Static] [-Sealed] [-Interface] [-ValueType] [-FilterScript ] [-Force] [-RegularExpression] [-InputObject ] [-Not] [-ResolutionMap ] [-AccessView ] [-Decoration ] [] ``` ## DESCRIPTION @@ -389,6 +389,24 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -Decoration + +Specifies that a type must be decorated with this attribute for it to be included in results. This search will be done based on type name rather than strict type identity so it is safe to use for embedded attributes. + +This can also be a type signature (see [about_Type_Signatures](https://seemingly.dev/about-type-signatures)). + +```yaml +Type: Type +Parameter Sets: (All) +Aliases: HasAttr, attr + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -ResolutionMap Specifies a hashtable of `name` to `ScriptBlockStringOrType` to create your own keywords and/or override type resolution for any signature in this command. diff --git a/src/ClassExplorer/Commands/FindMemberCommand.cs b/src/ClassExplorer/Commands/FindMemberCommand.cs index 05e9dde..a4aa94e 100644 --- a/src/ClassExplorer/Commands/FindMemberCommand.cs +++ b/src/ClassExplorer/Commands/FindMemberCommand.cs @@ -93,15 +93,6 @@ public SwitchParameter IncludeSpecialName set => _options.IncludeSpecialName = value; } - [Parameter] - [Alias("HasAttr", "attr")] - [ArgumentCompleter(typeof(TypeFullNameArgumentCompleter))] - public ScriptBlockStringOrType? Decoration - { - get => _options.Decoration; - set => _options.Decoration = value; - } - /// /// Gets or sets the member type to match. /// @@ -220,6 +211,7 @@ protected override void InitializeFilters() _options.RegularExpression = RegularExpression; _options.ResolutionMap = resolutionMap; _options.AccessView = AccessView; + _options.Decoration = Decoration; _search = Search.Members(_options, new PipelineEmitter(this)); } } diff --git a/src/ClassExplorer/Commands/FindReflectionObjectCommandBase.cs b/src/ClassExplorer/Commands/FindReflectionObjectCommandBase.cs index 5d59d93..eb2e6d0 100644 --- a/src/ClassExplorer/Commands/FindReflectionObjectCommandBase.cs +++ b/src/ClassExplorer/Commands/FindReflectionObjectCommandBase.cs @@ -67,6 +67,11 @@ public abstract class FindReflectionObjectCommandBase : PSCmdlet [Alias("as")] public virtual AccessView AccessView { get; set; } + [Parameter] + [Alias("HasAttr", "attr")] + [ArgumentCompleter(typeof(TypeFullNameArgumentCompleter))] + public ScriptBlockStringOrType? Decoration { get; set; } + private bool _hadError; /// @@ -120,7 +125,7 @@ protected override void ProcessRecord() // you probably aren't passing the entire AppDomain like this. if (InputObject.BaseObject is IList list) { - foreach (var item in list) + foreach (object? item in list) { ProcessSingleObject(PSObject.AsPSObject(item)); } @@ -170,20 +175,6 @@ protected override void ProcessRecord() continue; } - // if (!LanguagePrimitives.TryConvertTo(entry.Value, out Type value)) - // { - // WriteError( - // new ErrorRecord( - // new PSInvalidCastException( - // SR.Format( - // "Cannot convert the \"{0}\" value of type \"{1}\" to type \"{1}\".", - // entry.Value, - // entry.Value.GetType().FullName)), - // "InvalidTypeResolutionMap", - // ErrorCategory.InvalidArgument, - // entry)); - // } - resolutionMap[key] = LanguagePrimitives.ConvertTo(entry.Value); } diff --git a/src/ClassExplorer/Commands/FindTypeCommand.cs b/src/ClassExplorer/Commands/FindTypeCommand.cs index 825e8a0..c577917 100644 --- a/src/ClassExplorer/Commands/FindTypeCommand.cs +++ b/src/ClassExplorer/Commands/FindTypeCommand.cs @@ -187,6 +187,7 @@ protected override void InitializeFilters() _options.ResolutionMap = resolutionMap; _options.Signature = signature; _options.AccessView = AccessView; + _options.Decoration = Decoration; _search = Search.Types(_options, new PipelineEmitter(this)); } } diff --git a/src/ClassExplorer/MemberSearchOptions.cs b/src/ClassExplorer/MemberSearchOptions.cs index 67d01f5..20ae24e 100644 --- a/src/ClassExplorer/MemberSearchOptions.cs +++ b/src/ClassExplorer/MemberSearchOptions.cs @@ -16,8 +16,6 @@ internal class MemberSearchOptions : ReflectionSearchOptions public bool IncludeSpecialName { get; set; } - public ScriptBlockStringOrType? Decoration { get; set; } - public MemberTypes MemberType { get; set; } public bool Static { get; set; } diff --git a/src/ClassExplorer/ReflectionSearchOptions.cs b/src/ClassExplorer/ReflectionSearchOptions.cs index a3edaac..90d86c3 100644 --- a/src/ClassExplorer/ReflectionSearchOptions.cs +++ b/src/ClassExplorer/ReflectionSearchOptions.cs @@ -20,4 +20,6 @@ internal abstract class ReflectionSearchOptions public Dictionary? ResolutionMap { get; set; } public AccessView AccessView { get; set; } + + public ScriptBlockStringOrType? Decoration { get; set; } } diff --git a/src/ClassExplorer/Signatures/DecorationSignature.cs b/src/ClassExplorer/Signatures/DecorationSignature.cs index 2373ef4..bcae8bc 100644 --- a/src/ClassExplorer/Signatures/DecorationSignature.cs +++ b/src/ClassExplorer/Signatures/DecorationSignature.cs @@ -1,3 +1,4 @@ +using System; using System.Reflection; namespace ClassExplorer.Signatures @@ -6,10 +7,29 @@ internal sealed class DecorationSignature : UniversialSignature { private readonly string _typeName; + private readonly Lazy _fallbackType; + internal DecorationSignature(string typeName) { Poly.Assert(typeName is not null or { Length: < 1 }); _typeName = typeName; + _fallbackType = new Lazy(() => + { + Type? publicResult = Search.FirstType( + new TypeSearchOptions() { FullName = _typeName }); + + if (publicResult is not null) + { + return publicResult; + } + + return Search.FirstType( + new TypeSearchOptions() + { + FullName = _typeName, + AccessView = AccessView.This, + }); + }); } public override bool IsMatch(ParameterInfo parameter) @@ -19,9 +39,27 @@ public override bool IsMatch(ParameterInfo parameter) return true; } + if (_fallbackType.Value is Type type && parameter.IsDefined(type, inherit: true)) + { + return true; + } + return IsMatch(parameter.ParameterType); } - public override bool IsMatch(MemberInfo member) => member.IsDefined(_typeName); + public override bool IsMatch(MemberInfo member) + { + if (member.IsDefined(_typeName)) + { + return true; + } + + if (_fallbackType.Value is Type type && member.IsDefined(type, inherit: true)) + { + return true; + } + + return false; + } } } diff --git a/src/ClassExplorer/TypeSearch.cs b/src/ClassExplorer/TypeSearch.cs index 56bc5d3..35d9bad 100644 --- a/src/ClassExplorer/TypeSearch.cs +++ b/src/ClassExplorer/TypeSearch.cs @@ -136,5 +136,15 @@ protected override void InitializeOtherFilters(List> filters, Signa _options.Signature, static (type, signature) => signature.IsMatch(type)); } + + if (_options.Decoration is not null) + { + filters.AddFilter( + new DecorationSignature( + SignatureParser.ResolveAttributeTypeName( + _options.Decoration, + _options.ResolutionMap)), + static (type, signature) => signature.IsMatch(type)); + } } }