Browse Source

Surface property and event accessors as tree-node children

PropertyTreeNode now eagerly adds Getter / Setter as MethodTreeNode children
(when present); EventTreeNode does the same for Add / Remove / Invoke
accessors. The ShowExpander => false overrides drop so the base default
(Children.Any) lights up the chevron when accessors exist.

Assisted-by: Claude:claude-opus-4-7:Claude Code
pull/3755/head
Siegfried Pammer 2 months ago
parent
commit
6b5a3795b5
  1. 56
      ILSpy.Tests/AssemblyList/AssemblyTreeTests.cs
  2. 7
      ILSpy/TreeNodes/EventTreeNode.cs
  3. 5
      ILSpy/TreeNodes/PropertyTreeNode.cs

56
ILSpy.Tests/AssemblyList/AssemblyTreeTests.cs

@ -1216,4 +1216,60 @@ public class AssemblyTreeTests
ReferenceEquals(secondMeta, firstMeta).Should().BeFalse( ReferenceEquals(secondMeta, firstMeta).Should().BeFalse(
"a fresh metadata tab must be created — the previous one keeps its TypeDef state"); "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<MainWindow>();
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<TypeTreeNode>(
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<PropertyTreeNode>()
.First(p => p.PropertyDefinition.Name == "Length");
lengthProp.IsExpanded = true;
var accessors = lengthProp.Children.OfType<MethodTreeNode>().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<MainWindow>();
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<TypeTreeNode>(
coreLibName, "System", "System.AppDomain");
appDomainNode.IsExpanded = true;
var loadEvent = appDomainNode.Children.OfType<EventTreeNode>()
.First(e => e.EventDefinition.Name == "AssemblyLoad");
loadEvent.IsExpanded = true;
var accessors = loadEvent.Children.OfType<MethodTreeNode>().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");
}
} }

7
ILSpy/TreeNodes/EventTreeNode.cs

@ -37,12 +37,17 @@ namespace ILSpy.TreeNodes
public EventTreeNode(IEvent ev) public EventTreeNode(IEvent ev)
{ {
EventDefinition = ev ?? throw new ArgumentNullException(nameof(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 Text => Language.EntityToString(EventDefinition, ConversionFlags.None);
public override object Icon => Images.Images.GetIcon(Images.Images.Event, public override object Icon => Images.Images.GetIcon(Images.Images.Event,
Images.Images.GetOverlay(EventDefinition.Accessibility), EventDefinition.IsStatic); Images.Images.GetOverlay(EventDefinition.Accessibility), EventDefinition.IsStatic);
public override bool ShowExpander => false;
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
=> language.DecompileEvent(EventDefinition, output, options); => language.DecompileEvent(EventDefinition, output, options);

5
ILSpy/TreeNodes/PropertyTreeNode.cs

@ -37,12 +37,15 @@ namespace ILSpy.TreeNodes
public PropertyTreeNode(IProperty property) public PropertyTreeNode(IProperty property)
{ {
PropertyDefinition = property ?? throw new ArgumentNullException(nameof(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 Text => Language.EntityToString(PropertyDefinition, ConversionFlags.None);
public override object Icon => Images.Images.GetIcon(Images.Images.Property, public override object Icon => Images.Images.GetIcon(Images.Images.Property,
Images.Images.GetOverlay(PropertyDefinition.Accessibility), PropertyDefinition.IsStatic); Images.Images.GetOverlay(PropertyDefinition.Accessibility), PropertyDefinition.IsStatic);
public override bool ShowExpander => false;
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
=> language.DecompileProperty(PropertyDefinition, output, options); => language.DecompileProperty(PropertyDefinition, output, options);

Loading…
Cancel
Save