Browse Source

Resolve assembly, resource, and namespace search results to tree nodes

FindTreeNode only handled entity references, so activating an assembly,
resource, or namespace search result -- and clicking a resource or
namespace link -- silently did nothing. Restore the non-entity cases
(LoadedAssembly / MetadataFile / Resource / namespace) the previous
version handled.

Assisted-by: Claude:claude-opus-4-8:Claude Code
pull/3755/head
Siegfried Pammer 4 weeks ago
parent
commit
9d095705e9
  1. 70
      ILSpy.Tests/AssemblyList/FindTreeNodeReferenceTests.cs
  2. 60
      ILSpy/AssemblyTree/AssemblyTreeModel.cs

70
ILSpy.Tests/AssemblyList/FindTreeNodeReferenceTests.cs

@ -0,0 +1,70 @@ @@ -0,0 +1,70 @@
// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System.Threading.Tasks;
using Avalonia.Headless.NUnit;
using AwesomeAssertions;
using ILSpy.TreeNodes;
using NUnit.Framework;
namespace ICSharpCode.ILSpy.Tests;
/// <summary>
/// <c>AssemblyTreeModel.FindTreeNode</c> must resolve non-entity references -- a whole assembly
/// (LoadedAssembly / MetadataFile) and a namespace -- to their tree nodes, not just entities.
/// (Regression: the Avalonia port only handled entity references, so Assembly/Namespace search
/// results and resource/namespace links silently did nothing.)
/// </summary>
[TestFixture]
public class FindTreeNodeReferenceTests
{
[AvaloniaTest]
public async Task FindTreeNode_Resolves_Assembly_And_Namespace_References()
{
var (_, vm) = await TestHarness.BootAsync(3);
var atm = vm.AssemblyTreeModel;
var asmNode = atm.FindNode<AssemblyTreeNode>("System.Linq");
Assert.That(asmNode, Is.Not.Null);
var loaded = asmNode!.LoadedAssembly;
// LoadedAssembly reference -> its assembly node. (Custom TreeNodeAssertions shadows
// .Should() on tree nodes, so use Assert.That here.)
Assert.That(atm.FindTreeNode(loaded), Is.SameAs(asmNode),
"a LoadedAssembly reference resolves to its assembly node");
// MetadataFile reference -> the same assembly node.
var metadataFile = loaded.GetMetadataFileOrNull();
Assert.That(metadataFile, Is.Not.Null);
Assert.That(atm.FindTreeNode(metadataFile), Is.SameAs(asmNode),
"a MetadataFile reference resolves to its assembly node");
// Namespace reference -> its namespace node.
var compilation = loaded.GetTypeSystemOrNull();
Assert.That(compilation, Is.Not.Null);
var ns = compilation!.MainModule.RootNamespace.GetChildNamespace("System")?.GetChildNamespace("Linq");
Assert.That(ns, Is.Not.Null);
Assert.That(atm.FindTreeNode(ns), Is.InstanceOf<NamespaceTreeNode>(),
"a namespace reference resolves to its namespace node");
}
}

60
ILSpy/AssemblyTree/AssemblyTreeModel.cs

@ -620,11 +620,71 @@ namespace ILSpy.AssemblyTree @@ -620,11 +620,71 @@ namespace ILSpy.AssemblyTree
case IMember member:
return FindMemberNode(assemblyListTreeNode, member);
case LoadedAssembly lasm:
return FindAssemblyNode(lasm);
case MetadataFile metadataFile:
return assemblyListTreeNode.Children.OfType<AssemblyTreeNode>()
.FirstOrDefault(a => a.LoadedAssembly.GetMetadataFileOrNull() == metadataFile);
case Resource resource:
return FindResourceNode(assemblyListTreeNode, resource, null);
case ValueTuple<Resource, string> resourceWithName:
return FindResourceNode(assemblyListTreeNode, resourceWithName.Item1, resourceWithName.Item2);
case INamespace ns:
return FindNamespaceNode(assemblyListTreeNode, ns);
default:
return null;
}
}
// Resolves a resource (optionally a named sub-entry) to its tree node. Mirrors the previous
// version's AssemblyListTreeNode.FindResourceNode so resource search results / links navigate.
static ILSpyTreeNode? FindResourceNode(AssemblyListTreeNode root, Resource resource, string? name)
{
if (resource == null)
return null;
ILSpyTreeNode? resourceNode = null;
foreach (var node in root.Children.OfType<AssemblyTreeNode>())
{
if (!node.LoadedAssembly.IsLoaded)
continue;
node.EnsureLazyChildren();
foreach (var list in node.Children.OfType<ResourceListTreeNode>())
{
resourceNode = list.Children.OfType<ResourceTreeNode>().FirstOrDefault(x => x.Resource == resource)
?? (ILSpyTreeNode?)list.Children.OfType<ResourceEntryNode>().FirstOrDefault(x => resource.Name.Equals(x.Text));
if (resourceNode != null)
break;
}
if (resourceNode != null)
break;
}
if (resourceNode == null || name == null || name.Equals(resourceNode.Text))
return resourceNode;
resourceNode.EnsureLazyChildren();
return resourceNode.Children.OfType<ILSpyTreeNode>().FirstOrDefault(x => name.Equals(x.Text)) ?? resourceNode;
}
// Resolves a namespace to its tree node within its contributing assembly. Mirrors the previous
// version's AssemblyListTreeNode.FindNamespaceNode.
static NamespaceTreeNode? FindNamespaceNode(AssemblyListTreeNode root, INamespace ns)
{
var module = ns.ContributingModules.FirstOrDefault();
if (module?.MetadataFile == null)
return null;
var assembly = root.Children.OfType<AssemblyTreeNode>()
.FirstOrDefault(a => a.LoadedAssembly.GetMetadataFileOrNull() == module.MetadataFile);
if (assembly == null)
return null;
assembly.EnsureLazyChildren();
return assembly.Children.OfType<NamespaceTreeNode>()
.FirstOrDefault(n => ns.FullName.Length == 0 || ns.FullName.Equals(n.Text));
}
static TypeTreeNode? FindTypeNode(AssemblyListTreeNode root, ITypeDefinition type)
{
var module = type.ParentModule?.MetadataFile;

Loading…
Cancel
Save