diff --git a/ILSpy.Tests/AssemblyList/AssemblyTreeTests.cs b/ILSpy.Tests/AssemblyList/AssemblyTreeTests.cs index 121a576c3..7841481b1 100644 --- a/ILSpy.Tests/AssemblyList/AssemblyTreeTests.cs +++ b/ILSpy.Tests/AssemblyList/AssemblyTreeTests.cs @@ -1216,4 +1216,60 @@ public class AssemblyTreeTests ReferenceEquals(secondMeta, firstMeta).Should().BeFalse( "a fresh metadata tab must be created — the previous one keeps its TypeDef state"); } + + [AvaloniaTest] + public async Task Property_Tree_Node_Has_Getter_And_Setter_Method_Children() + { + // Properties expose their accessors as MethodTreeNode children, mirroring WPF + // ILSpy. Without this, tree-based navigation can't reach get_X / set_X — and + // MMB on a property-accessor row in the metadata grid silently no-ops because + // FindTreeNode can't resolve the IMethod to a tree node. + var window = AppComposition.Current.GetExport(); + window.Show(); + var vm = (MainWindowViewModel)window.DataContext!; + await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1); + + var coreLibName = typeof(object).Assembly.GetName().Name!; + var stringTypeNode = vm.AssemblyTreeModel.FindNode( + coreLibName, "System", "System.String"); + stringTypeNode.IsExpanded = true; + + // String.Length is read-only — a property with a getter but no setter. + var lengthProp = stringTypeNode.Children.OfType() + .First(p => p.PropertyDefinition.Name == "Length"); + lengthProp.IsExpanded = true; + + var accessors = lengthProp.Children.OfType().ToList(); + accessors.Should().NotBeEmpty( + "a property tree node must surface its accessors as MethodTreeNode children"); + accessors.Should().Contain(m => m.MethodDefinition.Name == "get_Length", + "String.Length must expose its getter"); + } + + [AvaloniaTest] + public async Task Event_Tree_Node_Has_Add_And_Remove_Method_Children() + { + // Events surface add_X / remove_X (and invoke_X if present) as MethodTreeNode + // children, mirroring WPF ILSpy. Same regression-protection as the property + // case above. + var window = AppComposition.Current.GetExport(); + window.Show(); + var vm = (MainWindowViewModel)window.DataContext!; + await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1); + + var coreLibName = typeof(object).Assembly.GetName().Name!; + // AppDomain.AssemblyLoad is a public, well-known event in CoreLib. + var appDomainNode = vm.AssemblyTreeModel.FindNode( + coreLibName, "System", "System.AppDomain"); + appDomainNode.IsExpanded = true; + var loadEvent = appDomainNode.Children.OfType() + .First(e => e.EventDefinition.Name == "AssemblyLoad"); + loadEvent.IsExpanded = true; + + var accessors = loadEvent.Children.OfType().ToList(); + accessors.Should().Contain(m => m.MethodDefinition.Name == "add_AssemblyLoad", + "event tree node must expose its add accessor"); + accessors.Should().Contain(m => m.MethodDefinition.Name == "remove_AssemblyLoad", + "event tree node must expose its remove accessor"); + } } diff --git a/ILSpy/TreeNodes/EventTreeNode.cs b/ILSpy/TreeNodes/EventTreeNode.cs index 83fd972e9..be55600ef 100644 --- a/ILSpy/TreeNodes/EventTreeNode.cs +++ b/ILSpy/TreeNodes/EventTreeNode.cs @@ -37,12 +37,17 @@ namespace ILSpy.TreeNodes public EventTreeNode(IEvent ev) { EventDefinition = ev ?? throw new ArgumentNullException(nameof(ev)); + if (ev.CanAdd) + Children.Add(new MethodTreeNode(ev.AddAccessor)); + if (ev.CanRemove) + Children.Add(new MethodTreeNode(ev.RemoveAccessor)); + if (ev.CanInvoke) + Children.Add(new MethodTreeNode(ev.InvokeAccessor)); } public override object Text => Language.EntityToString(EventDefinition, ConversionFlags.None); public override object Icon => Images.Images.GetIcon(Images.Images.Event, Images.Images.GetOverlay(EventDefinition.Accessibility), EventDefinition.IsStatic); - public override bool ShowExpander => false; public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) => language.DecompileEvent(EventDefinition, output, options); diff --git a/ILSpy/TreeNodes/PropertyTreeNode.cs b/ILSpy/TreeNodes/PropertyTreeNode.cs index 20eeca603..dd36a769f 100644 --- a/ILSpy/TreeNodes/PropertyTreeNode.cs +++ b/ILSpy/TreeNodes/PropertyTreeNode.cs @@ -37,12 +37,15 @@ namespace ILSpy.TreeNodes public PropertyTreeNode(IProperty property) { PropertyDefinition = property ?? throw new ArgumentNullException(nameof(property)); + if (property.CanGet) + Children.Add(new MethodTreeNode(property.Getter)); + if (property.CanSet) + Children.Add(new MethodTreeNode(property.Setter)); } public override object Text => Language.EntityToString(PropertyDefinition, ConversionFlags.None); public override object Icon => Images.Images.GetIcon(Images.Images.Property, Images.Images.GetOverlay(PropertyDefinition.Accessibility), PropertyDefinition.IsStatic); - public override bool ShowExpander => false; public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) => language.DecompileProperty(PropertyDefinition, output, options);