Browse Source

Namespace IsPublicAPI aggregates children + filter override

Mirrors the WPF AssemblyTreeNode.SetPublicAPI recursive walk: a namespace
counts as public-API iff at least one type it contains is public-API. Inline
on NamespaceTreeNode itself as a lazy-evaluated cached aggregate (types don't
change accessibility at runtime), so the cell template's gray-foreground
binding picks up the right value on first render.

Assisted-by: Claude:claude-opus-4-7:Claude Code
pull/3755/head
Siegfried Pammer 2 months ago
parent
commit
46e2df4b10
  1. 16
      ILSpy.Tests/Options/ApiVisibilityFilterTests.cs
  2. 24
      ILSpy/TreeNodes/NamespaceTreeNode.cs

16
ILSpy.Tests/Options/ApiVisibilityFilterTests.cs

@ -65,6 +65,22 @@ public class ApiVisibilityFilterTests @@ -65,6 +65,22 @@ public class ApiVisibilityFilterTests
var (privateNode, privateMethod) = FindNonPublicMethodInLoadedAssemblies(vm);
privateNode.IsPublicAPI.Should().BeFalse(
$"{privateMethod.DeclaringType?.Name}.{privateMethod.Name} is non-public");
// Assembly + root nodes inherit ILSpyTreeNode's true default — no accessibility to
// weigh, so they never pick up the gray non-public styling. Without this assertion,
// flipping the base default to false silently passes everything else even though
// every assembly row would render gray (mutation-test hole, May 2026).
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(
typeof(System.Linq.Enumerable).Assembly.GetName().Name!);
assemblyNode.IsPublicAPI.Should().BeTrue(
"AssemblyTreeNode must inherit the true default");
// Namespaces aggregate from their children — System.Linq holds public types so its
// namespace node reports IsPublicAPI=true.
var publicNamespace = vm.AssemblyTreeModel.FindNode<NamespaceTreeNode>(
"System.Linq", "System.Linq");
publicNamespace.IsPublicAPI.Should().BeTrue(
"a namespace whose children include public types is itself public-API");
}
[AvaloniaTest]

24
ILSpy/TreeNodes/NamespaceTreeNode.cs

@ -24,6 +24,7 @@ using ICSharpCode.Decompiler.Metadata; @@ -24,6 +24,7 @@ using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.ILSpyX;
using ILSpy;
using ILSpy.Languages;
namespace ILSpy.TreeNodes
@ -78,5 +79,28 @@ namespace ILSpy.TreeNodes @@ -78,5 +79,28 @@ namespace ILSpy.TreeNodes
.Where(t => t.Namespace == name && t.DeclaringTypeDefinition == null);
language.DecompileNamespace(name, types, output, options);
}
// A namespace counts as public-API iff at least one type it contains is public-API.
// Forces lazy children once so the aggregate is available before the cell template
// queries it for the gray-foreground binding; the result is cached because types
// don't change accessibility at runtime. Mirrors WPF's AssemblyTreeNode.SetPublicAPI
// recursive walk.
bool? cachedIsPublicAPI;
public override bool IsPublicAPI {
get {
if (cachedIsPublicAPI is { } cached)
return cached;
EnsureLazyChildren();
cachedIsPublicAPI = Children.OfType<ILSpyTreeNode>().Any(c => c.IsPublicAPI);
return cachedIsPublicAPI.Value;
}
}
public override FilterResult Filter(LanguageSettings settings)
{
if (settings.ShowApiLevel == ApiVisibility.PublicOnly && !IsPublicAPI)
return FilterResult.Hidden;
return FilterResult.Match;
}
}
}

Loading…
Cancel
Save