Browse Source

Group global-namespace types under a '-' node

The PE TypeDef table includes a <Module> pseudo-type plus any user-declared
top-level types in the empty namespace. Until now AssemblyTreeNode skipped
the empty namespace when building NamespaceTreeNodes and instead hung the
global types directly off the assembly node, breaking the long-standing
tree shape (every type lives under a namespace folder).
pull/3755/head
Siegfried Pammer 2 months ago
parent
commit
c4755bbe4d
  1. 39
      ILSpy.Tests/AssemblyList/AssemblyTreeTests.cs
  2. 16
      ILSpy/TreeNodes/AssemblyTreeNode.cs

39
ILSpy.Tests/AssemblyList/AssemblyTreeTests.cs

@ -1482,4 +1482,43 @@ public class AssemblyTreeTests @@ -1482,4 +1482,43 @@ public class AssemblyTreeTests
command.Invoking(c => c.Execute(null)).Should().NotThrow(
"Execute under headless must be a no-op rather than crash");
}
[AvaloniaTest]
public async Task Global_Namespace_Types_Appear_Under_Dash_Node()
{
// Every PE module carries a <Module> pseudo-type in the global (empty) namespace.
// To match the long-standing tree shape, those types must live under a
// NamespaceTreeNode whose Name is the empty string and whose displayed Text is "-",
// NOT as bare TypeTreeNodes hanging directly off the assembly node.
// Arrange — boot, wait for assemblies, locate CoreLib.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
assemblyNode.EnsureLazyChildren();
// Assert — exactly one global-namespace node, labelled "-", non-empty contents.
// Plain null check rather than .Should().NotBeNull() because the AwesomeAssertions
// SharpTreeNode-targeted extension hijacks Should() on NamespaceTreeNode? and lacks
// NotBeNull.
var globalNs = assemblyNode.Children.OfType<NamespaceTreeNode>()
.SingleOrDefault(n => n.Name.Length == 0);
Assert.That(globalNs, Is.Not.Null,
"types in the global namespace must be grouped under a NamespaceTreeNode with an empty Name");
globalNs!.Text.Should().Be("-",
"NamespaceTreeNode renders the empty namespace as '-' to match the long-standing tree shape");
// And no bare TypeTreeNodes should hang directly off the assembly — every type must
// route through one of the namespace nodes.
assemblyNode.Children.OfType<TypeTreeNode>().Should().BeEmpty(
"global-namespace types must live under the '-' node, not as direct assembly children");
globalNs.EnsureLazyChildren();
globalNs.Children.OfType<TypeTreeNode>().Should().NotBeEmpty(
"<Module> (and any other global types) belong under the '-' node");
}
}

16
ILSpy/TreeNodes/AssemblyTreeNode.cs

@ -269,25 +269,19 @@ namespace ILSpy.TreeNodes @@ -269,25 +269,19 @@ namespace ILSpy.TreeNodes
Children.Add(new ResourceListTreeNode(module));
var metadata = module.Metadata;
// Every top-level namespace string in the module — INCLUDING the empty string for
// types declared at module scope. The empty namespace becomes a NamespaceTreeNode
// whose Text renders as "-"; without that path, global-namespace types (every PE's
// <Module> pseudo-type plus any user-declared ones) would have no parent node to
// live under, and the long-standing tree shape would break.
var namespaces = metadata.TypeDefinitions
.Where(t => metadata.GetTypeDefinition(t).GetDeclaringType().IsNil)
.Select(t => metadata.GetString(metadata.GetTypeDefinition(t).Namespace))
.Where(ns => !string.IsNullOrEmpty(ns))
.Distinct()
.OrderBy(ns => ns, NaturalStringComparer.Instance);
foreach (var ns in namespaces)
Children.Add(new NamespaceTreeNode(ns, module));
var globalTypes = metadata.TypeDefinitions
.Where(t => {
var td = metadata.GetTypeDefinition(t);
return td.GetDeclaringType().IsNil
&& string.IsNullOrEmpty(metadata.GetString(td.Namespace));
});
foreach (var t in globalTypes)
Children.Add(new TypeTreeNode(t, module));
}
public override FilterResult Filter(LanguageSettings settings)

Loading…
Cancel
Save