From 75e89d886ed380c43c6ccae37f4e184de5bb998d Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Wed, 6 May 2026 14:06:42 +0200 Subject: [PATCH] ShowApiLevel filter on assembly tree Assisted-by: Claude:claude-opus-4-7:Claude Code Assisted-by: Claude:claude-opus-4-7:Claude Code --- .../Options/ApiVisibilityFilterTests.cs | 238 ++++++++++++++++++ ILSpy/AssemblyTree/AssemblyListPane.axaml.cs | 48 +++- ILSpy/Languages/CSharpLanguage.cs | 11 + ILSpy/Languages/Language.cs | 9 + ILSpy/TreeNodes/EventTreeNode.cs | 16 ++ ILSpy/TreeNodes/FieldTreeNode.cs | 16 ++ ILSpy/TreeNodes/FilterResult.cs | 37 +++ ILSpy/TreeNodes/ILSpyTreeNode.cs | 15 ++ ILSpy/TreeNodes/MethodTreeNode.cs | 16 ++ ILSpy/TreeNodes/PropertyTreeNode.cs | 16 ++ ILSpy/TreeNodes/TypeTreeNode.cs | 18 ++ 11 files changed, 438 insertions(+), 2 deletions(-) create mode 100644 ILSpy.Tests/Options/ApiVisibilityFilterTests.cs create mode 100644 ILSpy/TreeNodes/FilterResult.cs diff --git a/ILSpy.Tests/Options/ApiVisibilityFilterTests.cs b/ILSpy.Tests/Options/ApiVisibilityFilterTests.cs new file mode 100644 index 000000000..69817ea1c --- /dev/null +++ b/ILSpy.Tests/Options/ApiVisibilityFilterTests.cs @@ -0,0 +1,238 @@ +// 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.Linq; +using System.Threading.Tasks; +using System.Xml.Linq; + +using Avalonia.Controls; +using Avalonia.Controls.DataGridHierarchical; +using Avalonia.Headless.NUnit; +using Avalonia.VisualTree; + +using AwesomeAssertions; + +using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.ILSpyX; + +using ILSpy; +using ILSpy.AppEnv; +using ILSpy.AssemblyTree; +using ILSpy.Languages; +using ILSpy.TreeNodes; +using ILSpy.ViewModels; +using ILSpy.Views; + +using NUnit.Framework; + +namespace ICSharpCode.ILSpy.Tests; + +[TestFixture] +public class ApiVisibilityFilterTests +{ + [AvaloniaTest] + public async Task IsPublicAPI_Reflects_Method_Accessibility() + { + // IsPublicAPI is the gate the Filter() override consults when ShowApiLevel == PublicOnly. + // Public / Protected / ProtectedOrInternal members count as "public API"; Internal / + // ProtectedAndInternal / Private do not. + + // Arrange — boot, expand Enumerable, capture two methods of differing accessibility. + var (vm, enumerableNode) = await BootAndExpandEnumerableAsync(); + var publicMethod = enumerableNode.Children.OfType() + .Single(m => m.MethodDefinition.Name == "Empty"); + + // Act + Assert — Empty is public static. + publicMethod.IsPublicAPI.Should().BeTrue("Enumerable.Empty is public"); + + // Find a private helper somewhere in CoreLib. + var (privateNode, privateMethod) = FindNonPublicMethodInLoadedAssemblies(vm); + privateNode.IsPublicAPI.Should().BeFalse( + $"{privateMethod.DeclaringType?.Name}.{privateMethod.Name} is non-public"); + } + + [AvaloniaTest] + public async Task Filter_Hides_NonPublic_Method_When_ShowApiLevel_Is_PublicOnly() + { + // At PublicOnly, every member tree node whose IsPublicAPI is false reports + // FilterResult.Hidden so the assembly tree stops rendering it. + + // Arrange — boot, find a known non-public method. + var (vm, _) = await BootAndExpandEnumerableAsync(); + var (privateNode, _) = FindNonPublicMethodInLoadedAssemblies(vm); + var settings = AppComposition.Current.GetExport().SessionSettings.LanguageSettings; + + // Act + Assert — flip ApiLevel on the singleton settings between calls (they alias the + // same instance, so we must re-read after each set rather than capture both up front). + settings.ShowApiLevel = ApiVisibility.PublicOnly; + privateNode.Filter(settings).Should().Be(FilterResult.Hidden); + settings.ShowApiLevel = ApiVisibility.All; + privateNode.Filter(settings).Should().Be(FilterResult.Match); + } + + [AvaloniaTest] + public async Task Filter_PublicAndInternal_Shows_NonCompilerGenerated_Private_Members() + { + // "Show public, private and internal" (PublicAndInternal) keeps every member that isn't + // compiler-generated — including private + internal helpers. Only the All level loosens + // the compiler-generated cut. + + // Arrange — boot, find a private regular method. + var (vm, _) = await BootAndExpandEnumerableAsync(); + var (privateNode, _) = FindNonPublicMethodInLoadedAssemblies(vm); + var settings = AppComposition.Current.GetExport().SessionSettings.LanguageSettings; + + // Act + Assert — visible at PublicAndInternal (Language.ShowMember returns true for + // normal private methods). Hidden at PublicOnly. Visible at All. + settings.ShowApiLevel = ApiVisibility.PublicAndInternal; + privateNode.Filter(settings).Should().Be(FilterResult.Match); + settings.ShowApiLevel = ApiVisibility.PublicOnly; + privateNode.Filter(settings).Should().Be(FilterResult.Hidden); + settings.ShowApiLevel = ApiVisibility.All; + privateNode.Filter(settings).Should().Be(FilterResult.Match); + } + + [AvaloniaTest] + public async Task CSharpLanguage_ShowMember_Hides_Compiler_Generated_Members() + { + // At ShowApiLevel != All, the language-level ShowMember filter is consulted on top of + // IsPublicAPI to drop compiler-generated members (anonymous-type backing fields, lambda + // closure classes, async-state-machine fields, …). MemberIsHidden in the decompiler + // already knows how to spot these. + + // Arrange — boot. + var (vm, _) = await BootAndExpandEnumerableAsync(); + var typeSystem = GetCoreLibTypeSystem(vm); + // Find a compiler-generated entity — every modern CoreLib type has at least one. + var compilerGenerated = FindCompilerGeneratedMember(typeSystem); + var languageService = AppComposition.Current.GetExport(); + var csharp = languageService.Languages.Single(l => l.Name == "C#"); + + // Act + Assert — language reports the member as hidden. + csharp.ShowMember(compilerGenerated).Should().BeFalse( + $"compiler-generated entity '{compilerGenerated.Name}' should be hidden by C#'s ShowMember"); + } + + [AvaloniaTest] + public async Task Switching_ApiVis_PublicOnly_Reduces_Visible_Method_Count_On_Type() + { + // End-to-end: a type with a mix of public/non-public methods shows fewer methods after + // flipping ApiVisPublicOnly. Drives the AssemblyListPane's children-filter pipeline that + // re-evaluates visibility when ShowApiLevel changes. + + // Arrange — boot, find a CoreLib type with mixed accessibility (String has many). + var window = AppComposition.Current.GetExport(); + window.Show(); + var vm = (MainWindowViewModel)window.DataContext!; + await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3); + + var coreLibName = typeof(object).Assembly.GetName().Name!; + var stringType = vm.AssemblyTreeModel.FindNode(coreLibName, "System", "System.String"); + stringType.IsExpanded = true; + await Waiters.WaitForAsync(() => stringType.Children.OfType().Any()); + + var settings = AppComposition.Current.GetExport().SessionSettings.LanguageSettings; + settings.ShowApiLevel = ApiVisibility.All; + var allCount = CountVisibleMethods(stringType, settings); + + // Act — switch to PublicOnly. + settings.ShowApiLevel = ApiVisibility.PublicOnly; + var publicCount = CountVisibleMethods(stringType, settings); + + // Assert — strictly fewer methods at PublicOnly than at All (String has internal/private + // helpers we expect to disappear). + publicCount.Should().BeLessThan(allCount, + "flipping ApiVisPublicOnly should hide non-public String methods"); + publicCount.Should().BeGreaterThan(0, "public methods on String must still be visible"); + } + + [AvaloniaTest] + public async Task AssemblyListPane_Rebinds_When_ShowApiLevel_Changes() + { + // The pane subscribes to LanguageSettings.PropertyChanged so toggling ShowApiLevel + // triggers a tree rebind — without that wire-up, the menu radio would still flip the + // setting but the grid would never re-evaluate visibility. Asserts the pane swaps in a + // new HierarchicalModel reference whenever the setting changes. + + // Arrange — boot, locate the pane. + var window = AppComposition.Current.GetExport(); + window.Show(); + var vm = (MainWindowViewModel)window.DataContext!; + await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1); + await Waiters.WaitForAsync( + () => window.GetVisualDescendants().OfType().Any()); + var pane = window.GetVisualDescendants().OfType().Single(); + var grid = pane.GetVisualDescendants().OfType().Single(); + + // Force any pending DataContext propagation so the initial HierarchicalModel is set. + var settings = AppComposition.Current.GetExport().SessionSettings.LanguageSettings; + await Waiters.WaitForAsync(() => grid.HierarchicalModel != null); + var modelBefore = grid.HierarchicalModel; + + // Act — flip the setting; pane should swap the model. + settings.ShowApiLevel = settings.ShowApiLevel == ApiVisibility.PublicOnly + ? ApiVisibility.All + : ApiVisibility.PublicOnly; + + // Assert — model reference changed, indicating BindTree ran again with new filter state. + grid.HierarchicalModel.Should().NotBeSameAs(modelBefore, + "flipping ShowApiLevel must rebind the HierarchicalModel so the grid re-evaluates child visibility"); + } + + static int CountVisibleMethods(TypeTreeNode type, LanguageSettings settings) + => type.Children.OfType() + .Count(m => m.Filter(settings) != FilterResult.Hidden); + + static async Task<(MainWindowViewModel vm, TypeTreeNode enumerableNode)> BootAndExpandEnumerableAsync() + { + var window = AppComposition.Current.GetExport(); + window.Show(); + var vm = (MainWindowViewModel)window.DataContext!; + await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3); + var node = vm.AssemblyTreeModel.FindNode( + "System.Linq", "System.Linq", "System.Linq.Enumerable"); + node.IsExpanded = true; + await Waiters.WaitForAsync(() => node.Children.OfType().Any()); + return (vm, node); + } + + static ICompilation GetCoreLibTypeSystem(MainWindowViewModel vm) + { + var coreLibName = typeof(object).Assembly.GetName().Name!; + var assembly = vm.AssemblyTreeModel.AssemblyList!.GetAssemblies() + .Single(a => string.Equals(System.IO.Path.GetFileNameWithoutExtension(a.FileName), coreLibName, System.StringComparison.OrdinalIgnoreCase)); + return assembly.GetMetadataFileOrNull()!.GetTypeSystemOrNull()!; + } + + static (MethodTreeNode node, IMethod method) FindNonPublicMethodInLoadedAssemblies(MainWindowViewModel vm) + { + var typeSystem = GetCoreLibTypeSystem(vm); + var method = FindMethodWithAccessibility(typeSystem, Accessibility.Private); + return (new MethodTreeNode(method), method); + } + + static IMethod FindMethodWithAccessibility(ICompilation typeSystem, Accessibility accessibility) + => typeSystem.MainModule.TypeDefinitions + .SelectMany(t => t.Methods) + .First(m => m.Accessibility == accessibility); + + static IEntity FindCompilerGeneratedMember(ICompilation typeSystem) + => typeSystem.MainModule.TypeDefinitions + .SelectMany(t => t.NestedTypes.Cast().Concat(t.Methods).Concat(t.Fields)) + .First(e => e.HasAttribute(KnownAttribute.CompilerGenerated)); +} diff --git a/ILSpy/AssemblyTree/AssemblyListPane.axaml.cs b/ILSpy/AssemblyTree/AssemblyListPane.axaml.cs index e617e30e3..54d71a4a9 100644 --- a/ILSpy/AssemblyTree/AssemblyListPane.axaml.cs +++ b/ILSpy/AssemblyTree/AssemblyListPane.axaml.cs @@ -31,6 +31,7 @@ using Avalonia.VisualTree; using ICSharpCode.ILSpyX.TreeView; +using ILSpy.AppEnv; using ILSpy.TreeNodes; namespace ILSpy.AssemblyTree @@ -41,6 +42,8 @@ namespace ILSpy.AssemblyTree // selection into the DataGrid. bool syncingSelection; + LanguageSettings? languageSettings; + public AssemblyListPane() { InitializeComponent(); @@ -52,6 +55,33 @@ namespace ILSpy.AssemblyTree // composition host. Both paths route through the same Opening handler. var registry = TryGetContextMenuRegistry(); AttachContextMenu(registry?.Entries ?? System.Array.Empty()); + + // Subscribe to the active LanguageSettings so flipping ShowApiLevel rebuilds the + // tree and the new visibility takes effect without a restart. SettingsService is + // optional (design-time previews don't bootstrap composition). + languageSettings = TryGetLanguageSettings(); + if (languageSettings != null) + languageSettings.PropertyChanged += OnLanguageSettingsChanged; + } + + static LanguageSettings? TryGetLanguageSettings() + { + try + { + return AppComposition.Current.GetExport().SessionSettings.LanguageSettings; + } + catch + { + return null; + } + } + + void OnLanguageSettingsChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName != nameof(LanguageSettings.ShowApiLevel)) + return; + if (DataContext is AssemblyTreeModel model && model.Root != null) + BindTree(model.Root); } static ContextMenuEntryRegistry? TryGetContextMenuRegistry() @@ -322,12 +352,26 @@ namespace ILSpy.AssemblyTree } } + // Apply the active LanguageSettings filter (if any). Returns the children unfiltered when + // no settings are available (design-time / unit-host scenarios) so the tree still + // renders. Materialises into a List so each call returns a stable snapshot the grid + // can iterate twice without re-evaluating. + static IEnumerable FilterChildren(IEnumerable children, LanguageSettings? settings) + { + if (settings == null) + return children; + return children + .Where(c => c is not ILSpyTreeNode it || it.Filter(settings) != FilterResult.Hidden) + .ToList(); + } + void BindTree(SharpTreeNode root) { + var settings = languageSettings; var options = new HierarchicalOptions { ChildrenSelector = node => { node.EnsureLazyChildren(); - return node.Children; + return FilterChildren(node.Children, settings); }, IsLeafSelector = node => !node.ShowExpander, VirtualizeChildren = false, @@ -339,7 +383,7 @@ namespace ILSpy.AssemblyTree }; var hierarchicalModel = new HierarchicalModel(options); - hierarchicalModel.SetRoots((IEnumerable)root.Children); + hierarchicalModel.SetRoots(FilterChildren(root.Children, settings)); TreeGrid.HierarchicalModel = hierarchicalModel; } diff --git a/ILSpy/Languages/CSharpLanguage.cs b/ILSpy/Languages/CSharpLanguage.cs index bb703a306..a16b5bdb9 100644 --- a/ILSpy/Languages/CSharpLanguage.cs +++ b/ILSpy/Languages/CSharpLanguage.cs @@ -78,6 +78,17 @@ namespace ILSpy.Languages public override void WriteCommentLine(ITextOutput output, string comment) => output.WriteLine("// " + comment); + public override bool ShowMember(IEntity member) + { + ArgumentNullException.ThrowIfNull(member); + if (member.MetadataToken.IsNil) + return true; + var assembly = member.ParentModule?.MetadataFile; + if (assembly == null) + return true; + return !CSharpDecompiler.MemberIsHidden(assembly, member.MetadataToken, new DecompilerSettings()); + } + public override RichText GetRichTextTooltip(IEntity entity) { ArgumentNullException.ThrowIfNull(entity); diff --git a/ILSpy/Languages/Language.cs b/ILSpy/Languages/Language.cs index dfdb72c74..2a278f7eb 100644 --- a/ILSpy/Languages/Language.cs +++ b/ILSpy/Languages/Language.cs @@ -102,6 +102,15 @@ namespace ILSpy.Languages WriteCommentLine(output, MemberDescription(ev)); } + /// + /// Returns false for entities the active language wants to hide (compiler-generated + /// closures, anonymous-type backing fields, async-state-machine fields, …) when the + /// caller has opted into "everything except compiler-generated" via + /// / . + /// The default treats every entity as visible — language subclasses tighten the rule. + /// + public virtual bool ShowMember(IEntity member) => true; + // FakeMember-derived entities (the decompiler's fallback when a member or reference // can't be resolved) can carry a null DeclaringTypeDefinition; fall back to the // member name on its own when that's the case. diff --git a/ILSpy/TreeNodes/EventTreeNode.cs b/ILSpy/TreeNodes/EventTreeNode.cs index 105e96055..83fd972e9 100644 --- a/ILSpy/TreeNodes/EventTreeNode.cs +++ b/ILSpy/TreeNodes/EventTreeNode.cs @@ -21,7 +21,9 @@ using System; using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.Output; using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.ILSpyX; +using ILSpy; using ILSpy.Languages; namespace ILSpy.TreeNodes @@ -45,6 +47,20 @@ namespace ILSpy.TreeNodes public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) => language.DecompileEvent(EventDefinition, output, options); + public override bool IsPublicAPI => EventDefinition.Accessibility switch { + Accessibility.Public or Accessibility.Protected or Accessibility.ProtectedOrInternal => true, + _ => false, + }; + + public override FilterResult Filter(LanguageSettings settings) + { + if (settings.ShowApiLevel == ApiVisibility.PublicOnly && !IsPublicAPI) + return FilterResult.Hidden; + if (settings.ShowApiLevel == ApiVisibility.All || Language.ShowMember(EventDefinition)) + return FilterResult.Match; + return FilterResult.Hidden; + } + public override string ToString() => "Event " + EventDefinition.Name; } } diff --git a/ILSpy/TreeNodes/FieldTreeNode.cs b/ILSpy/TreeNodes/FieldTreeNode.cs index bd6da6aa9..718aaa15a 100644 --- a/ILSpy/TreeNodes/FieldTreeNode.cs +++ b/ILSpy/TreeNodes/FieldTreeNode.cs @@ -21,7 +21,9 @@ using System; using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.Output; using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.ILSpyX; +using ILSpy; using ILSpy.Languages; namespace ILSpy.TreeNodes @@ -45,6 +47,20 @@ namespace ILSpy.TreeNodes public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) => language.DecompileField(FieldDefinition, output, options); + public override bool IsPublicAPI => FieldDefinition.Accessibility switch { + Accessibility.Public or Accessibility.Protected or Accessibility.ProtectedOrInternal => true, + _ => false, + }; + + public override FilterResult Filter(LanguageSettings settings) + { + if (settings.ShowApiLevel == ApiVisibility.PublicOnly && !IsPublicAPI) + return FilterResult.Hidden; + if (settings.ShowApiLevel == ApiVisibility.All || Language.ShowMember(FieldDefinition)) + return FilterResult.Match; + return FilterResult.Hidden; + } + public override string ToString() => "Field " + FieldDefinition.Name; } } diff --git a/ILSpy/TreeNodes/FilterResult.cs b/ILSpy/TreeNodes/FilterResult.cs new file mode 100644 index 000000000..7ae4277a4 --- /dev/null +++ b/ILSpy/TreeNodes/FilterResult.cs @@ -0,0 +1,37 @@ +// 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. + +namespace ILSpy.TreeNodes +{ + /// + /// Outcome of an evaluation against the active + /// . Drives whether the assembly tree shows the node, hides + /// it outright, or shows it only if any descendant matches. + /// + public enum FilterResult + { + /// The node passes the filter and should be displayed. + Match, + + /// The node itself should be hidden, but its descendants must still be evaluated. + Recurse, + + /// The node and all its descendants are hidden. + Hidden, + } +} diff --git a/ILSpy/TreeNodes/ILSpyTreeNode.cs b/ILSpy/TreeNodes/ILSpyTreeNode.cs index 1de6c0ea7..f04349d6e 100644 --- a/ILSpy/TreeNodes/ILSpyTreeNode.cs +++ b/ILSpy/TreeNodes/ILSpyTreeNode.cs @@ -77,5 +77,20 @@ namespace ILSpy.TreeNodes /// embedded resources). /// public virtual bool Save() => false; + + /// + /// True for nodes whose member is part of the assembly's public surface + /// (Public / Protected / ProtectedOrInternal). Consulted by + /// to honour the setting. + /// + public virtual bool IsPublicAPI => true; + + /// + /// Decides whether this node is visible under the current . + /// Overrides on member tree nodes consult plus + /// (the compiler-generated cut) to drop + /// non-matching entries. Default treats every node as visible. + /// + public virtual FilterResult Filter(LanguageSettings settings) => FilterResult.Match; } } diff --git a/ILSpy/TreeNodes/MethodTreeNode.cs b/ILSpy/TreeNodes/MethodTreeNode.cs index 1f051dece..c3a0c7c79 100644 --- a/ILSpy/TreeNodes/MethodTreeNode.cs +++ b/ILSpy/TreeNodes/MethodTreeNode.cs @@ -21,7 +21,9 @@ using System; using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.Output; using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.ILSpyX; +using ILSpy; using ILSpy.Languages; namespace ILSpy.TreeNodes @@ -56,6 +58,20 @@ namespace ILSpy.TreeNodes public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) => language.DecompileMethod(MethodDefinition, output, options); + public override bool IsPublicAPI => MethodDefinition.Accessibility switch { + Accessibility.Public or Accessibility.Protected or Accessibility.ProtectedOrInternal => true, + _ => false, + }; + + public override FilterResult Filter(LanguageSettings settings) + { + if (settings.ShowApiLevel == ApiVisibility.PublicOnly && !IsPublicAPI) + return FilterResult.Hidden; + if (settings.ShowApiLevel == ApiVisibility.All || Language.ShowMember(MethodDefinition)) + return FilterResult.Match; + return FilterResult.Hidden; + } + // Stable identity for SessionSettings.ActiveTreeViewPath; format must round-trip // across launches so the saved path can be restored. public override string ToString() diff --git a/ILSpy/TreeNodes/PropertyTreeNode.cs b/ILSpy/TreeNodes/PropertyTreeNode.cs index e1fd09e92..20eeca603 100644 --- a/ILSpy/TreeNodes/PropertyTreeNode.cs +++ b/ILSpy/TreeNodes/PropertyTreeNode.cs @@ -21,7 +21,9 @@ using System; using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.Output; using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.ILSpyX; +using ILSpy; using ILSpy.Languages; namespace ILSpy.TreeNodes @@ -45,6 +47,20 @@ namespace ILSpy.TreeNodes public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) => language.DecompileProperty(PropertyDefinition, output, options); + public override bool IsPublicAPI => PropertyDefinition.Accessibility switch { + Accessibility.Public or Accessibility.Protected or Accessibility.ProtectedOrInternal => true, + _ => false, + }; + + public override FilterResult Filter(LanguageSettings settings) + { + if (settings.ShowApiLevel == ApiVisibility.PublicOnly && !IsPublicAPI) + return FilterResult.Hidden; + if (settings.ShowApiLevel == ApiVisibility.All || Language.ShowMember(PropertyDefinition)) + return FilterResult.Match; + return FilterResult.Hidden; + } + public override string ToString() => "Property " + new ICSharpCode.Decompiler.IL.ILAmbience { ConversionFlags = ConversionFlags.ShowTypeParameterList diff --git a/ILSpy/TreeNodes/TypeTreeNode.cs b/ILSpy/TreeNodes/TypeTreeNode.cs index 12d9057a9..59d341e4d 100644 --- a/ILSpy/TreeNodes/TypeTreeNode.cs +++ b/ILSpy/TreeNodes/TypeTreeNode.cs @@ -26,6 +26,7 @@ using ICSharpCode.Decompiler.Output; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.ILSpyX; +using ILSpy; using ILSpy.Languages; namespace ILSpy.TreeNodes @@ -86,6 +87,23 @@ namespace ILSpy.TreeNodes language.WriteCommentLine(output, "(could not resolve type)"); } + public override bool IsPublicAPI => ResolveTypeDefinition()?.Accessibility switch { + Accessibility.Public or Accessibility.Protected or Accessibility.ProtectedOrInternal => true, + _ => false, + }; + + public override FilterResult Filter(LanguageSettings settings) + { + if (settings.ShowApiLevel == ApiVisibility.PublicOnly && !IsPublicAPI) + return FilterResult.Hidden; + var typeDef = ResolveTypeDefinition(); + if (typeDef == null) + return FilterResult.Match; + if (settings.ShowApiLevel == ApiVisibility.All || Language.ShowMember(typeDef)) + return FilterResult.Match; + return FilterResult.Hidden; + } + // Stable identity for SessionSettings.ActiveTreeViewPath. ReflectionName is // language-independent. public override string ToString()