mirror of https://github.com/icsharpcode/ILSpy.git
Browse Source
Lay the foundation for the analyzer pane. None of these classes are wired into the live view yet — that lands when the pane view-model and the ProDataGrid view are filled in two commits down. Background-fetch behaviour on AnalyzerSearchTreeNode is also deferred. Assisted-by: Claude:claude-opus-4-7:Claude Codepull/3755/head
16 changed files with 1207 additions and 0 deletions
@ -0,0 +1,100 @@
@@ -0,0 +1,100 @@
|
||||
// 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 Avalonia.Headless.NUnit; |
||||
|
||||
using AwesomeAssertions; |
||||
|
||||
using ICSharpCode.Decompiler.TypeSystem; |
||||
|
||||
using ILSpy.Analyzers; |
||||
using ILSpy.Analyzers.TreeNodes; |
||||
using ILSpy.AppEnv; |
||||
using ILSpy.TreeNodes; |
||||
using ILSpy.ViewModels; |
||||
using ILSpy.Views; |
||||
|
||||
using NUnit.Framework; |
||||
|
||||
namespace ICSharpCode.ILSpy.Tests.Analyzers; |
||||
|
||||
[TestFixture] |
||||
public class AnalyzedMethodTreeNodeTests |
||||
{ |
||||
[AvaloniaTest] |
||||
public async Task Wrapping_A_Method_Shows_It_With_Its_Declaring_Type_Prefix() |
||||
{ |
||||
// AnalyzedMethodTreeNode renders the analysed method with `ShowDeclaringType` +
|
||||
// `UseFullyQualifiedEntityNames` so users see "System.Linq.Enumerable.Empty<TSource>"
|
||||
// in the pane rather than the bare method name. An optional `prefix` argument lets
|
||||
// accessor / event-helper nodes prepend a tag without touching the entity string.
|
||||
|
||||
var window = AppComposition.Current.GetExport<MainWindow>(); |
||||
window.Show(); |
||||
var vm = (MainWindowViewModel)window.DataContext!; |
||||
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1); |
||||
|
||||
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>( |
||||
"System.Linq", "System.Linq", "System.Linq.Enumerable"); |
||||
typeNode.EnsureLazyChildren(); |
||||
var emptyMethod = (IMethod)typeNode.Children.OfType<MethodTreeNode>() |
||||
.First(m => m.MethodDefinition.Name == "Empty").Member!; |
||||
|
||||
var node = new AnalyzedMethodTreeNode(emptyMethod, source: null); |
||||
node.Member.Should().BeSameAs(emptyMethod); |
||||
node.Text.ToString().Should().Contain("Empty"); |
||||
node.Text.ToString().Should().Contain("Enumerable", |
||||
"AnalyzedMethodTreeNode must show the declaring type so the user can disambiguate " |
||||
+ "identically-named methods across the analyser pane"); |
||||
} |
||||
|
||||
[AvaloniaTest] |
||||
public async Task LoadChildren_On_A_Method_Materialises_Uses_And_Used_By_Headers() |
||||
{ |
||||
// Expanding an analysed method materialises one AnalyzerSearchTreeNode per analyser
|
||||
// whose Show(method) returns true. The two canonical entries for any callable method
|
||||
// are "Uses" (MethodUsesAnalyzer, body scanner) and "Used By" (MethodUsedByAnalyzer,
|
||||
// call-site scanner).
|
||||
|
||||
var window = AppComposition.Current.GetExport<MainWindow>(); |
||||
window.Show(); |
||||
var vm = (MainWindowViewModel)window.DataContext!; |
||||
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1); |
||||
|
||||
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>( |
||||
"System.Linq", "System.Linq", "System.Linq.Enumerable"); |
||||
typeNode.EnsureLazyChildren(); |
||||
var firstMethod = (IMethod)typeNode.Children.OfType<MethodTreeNode>() |
||||
.First(m => m.MethodDefinition.Name == "AsEnumerable").Member!; |
||||
|
||||
var node = new AnalyzedMethodTreeNode(firstMethod, source: null); |
||||
node.EnsureLazyChildren(); |
||||
|
||||
var headers = node.Children.OfType<AnalyzerSearchTreeNode>() |
||||
.Select(c => c.AnalyzerHeader) |
||||
.ToArray(); |
||||
|
||||
headers.Should().Contain("Used By", |
||||
"MethodUsedByAnalyzer is the headline reverse-lookup for any concrete method"); |
||||
headers.Should().Contain("Uses", |
||||
"MethodUsesAnalyzer scans the body for callees and field/type touches"); |
||||
} |
||||
} |
||||
@ -0,0 +1,106 @@
@@ -0,0 +1,106 @@
|
||||
// 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 Avalonia.Headless.NUnit; |
||||
|
||||
using AwesomeAssertions; |
||||
|
||||
using ICSharpCode.Decompiler.TypeSystem; |
||||
using ICSharpCode.ILSpyX; |
||||
|
||||
using ILSpy.Analyzers; |
||||
using ILSpy.Analyzers.TreeNodes; |
||||
using ILSpy.AppEnv; |
||||
using ILSpy.TreeNodes; |
||||
using ILSpy.ViewModels; |
||||
using ILSpy.Views; |
||||
|
||||
using NUnit.Framework; |
||||
|
||||
namespace ICSharpCode.ILSpy.Tests.Analyzers; |
||||
|
||||
[TestFixture] |
||||
public class AnalyzedTypeTreeNodeTests |
||||
{ |
||||
[AvaloniaTest] |
||||
public async Task Wrapping_A_TypeDefinition_Produces_The_Languages_Stringification() |
||||
{ |
||||
// AnalyzedTypeTreeNode renders the analysed type using the same Language.TypeToString
|
||||
// the rest of the assembly tree uses — so users see "System.Object" exactly the way
|
||||
// it appears under the assembly node, not a raw ReflectionName.
|
||||
|
||||
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 assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName); |
||||
var module = assemblyNode.LoadedAssembly.GetMetadataFileOrNull(); |
||||
module.Should().NotBeNull(); |
||||
var typeSystem = module!.GetTypeSystemOrNull(); |
||||
typeSystem.Should().NotBeNull(); |
||||
|
||||
var objectType = typeSystem!.MainModule.TopLevelTypeDefinitions |
||||
.Single(t => t.FullName == "System.Object"); |
||||
|
||||
var node = new AnalyzedTypeTreeNode(objectType, source: null); |
||||
node.Text.ToString().Should().Contain("Object"); |
||||
node.Member.Should().BeSameAs(objectType); |
||||
node.Icon.Should().NotBeNull("every entity node must surface a non-null icon"); |
||||
} |
||||
|
||||
[AvaloniaTest] |
||||
public async Task LoadChildren_On_A_Type_Materialises_All_Applicable_Analyzer_Headers() |
||||
{ |
||||
// Expanding an analysed type lazily fills its Children with one AnalyzerSearchTreeNode
|
||||
// per applicable analyzer (the analyzers whose Show(symbol) returns true). For a public
|
||||
// non-static reference type like System.Object, "Used By", "Instantiated By", "Exposed
|
||||
// By", and "Extension Methods" all match.
|
||||
|
||||
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 assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName); |
||||
var module = assemblyNode.LoadedAssembly.GetMetadataFileOrNull(); |
||||
module.Should().NotBeNull(); |
||||
var typeSystem = module!.GetTypeSystemOrNull(); |
||||
typeSystem.Should().NotBeNull(); |
||||
|
||||
var objectType = typeSystem!.MainModule.TopLevelTypeDefinitions |
||||
.Single(t => t.FullName == "System.Object"); |
||||
|
||||
var node = new AnalyzedTypeTreeNode(objectType, source: null); |
||||
node.EnsureLazyChildren(); |
||||
|
||||
var headers = node.Children.OfType<AnalyzerSearchTreeNode>() |
||||
.Select(c => c.AnalyzerHeader) |
||||
.ToArray(); |
||||
|
||||
headers.Should().Contain("Used By", "TypeUsedByAnalyzer applies to every TypeDefinition"); |
||||
headers.Should().Contain("Exposed By", "TypeExposedByAnalyzer applies to public types"); |
||||
headers.Should().Contain("Extension Methods", |
||||
"TypeExtensionMethodsAnalyzer applies to every TypeDefinition that could be a target"); |
||||
} |
||||
} |
||||
@ -0,0 +1,111 @@
@@ -0,0 +1,111 @@
|
||||
// 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 Avalonia.Headless.NUnit; |
||||
|
||||
using AwesomeAssertions; |
||||
|
||||
using ICSharpCode.ILSpyX; |
||||
using ICSharpCode.ILSpyX.Analyzers; |
||||
|
||||
using ILSpy.Analyzers; |
||||
using ILSpy.Analyzers.TreeNodes; |
||||
using ILSpy.AppEnv; |
||||
using ILSpy.TreeNodes; |
||||
using ILSpy.ViewModels; |
||||
using ILSpy.Views; |
||||
|
||||
using NUnit.Framework; |
||||
|
||||
namespace ICSharpCode.ILSpy.Tests.Analyzers; |
||||
|
||||
[TestFixture] |
||||
public class AnalyzerTreeNodeTests |
||||
{ |
||||
[AvaloniaTest] |
||||
public async Task Analyzers_Property_Surfaces_All_MEF_Registered_Analyzers_Sorted_By_Order() |
||||
{ |
||||
// AnalyzerTreeNode.Analyzers exposes the MEF-discovered IAnalyzer exports — the
|
||||
// list every Analyzed*TreeNode walks when populating its child analyzer headers.
|
||||
// Two contracts: (a) every [ExportAnalyzer] in ICSharpCode.ILSpyX.Analyzers.Builtin
|
||||
// reaches the list, (b) the order is monotonically non-decreasing by Metadata.Order
|
||||
// so users see headers in the same sequence the WPF host produces.
|
||||
|
||||
var window = AppComposition.Current.GetExport<MainWindow>(); |
||||
window.Show(); |
||||
var vm = (MainWindowViewModel)window.DataContext!; |
||||
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1); |
||||
|
||||
var analyzers = AnalyzerTreeNode.Analyzers.ToArray(); |
||||
analyzers.Should().HaveCountGreaterThanOrEqualTo(15, |
||||
"the shared analyzer library defines 17 built-in analyzers — all reachable through MEF"); |
||||
|
||||
var orders = analyzers.Select(a => a.Metadata?.Order ?? 0).ToArray(); |
||||
orders.Should().BeInAscendingOrder( |
||||
"AnalyzerTreeNode.Analyzers must hand back exports sorted by metadata Order"); |
||||
|
||||
var headers = analyzers.Select(a => a.Metadata?.Header).ToArray(); |
||||
headers.Should().Contain("Used By", |
||||
"TypeUsedByAnalyzer / MethodUsedByAnalyzer is the canonical first analyzer"); |
||||
} |
||||
|
||||
[AvaloniaTest] |
||||
public async Task AnalyzerRootNode_Clears_Children_When_The_Active_AssemblyList_Resets() |
||||
{ |
||||
// AnalyzerRootNode is the pane's tree root. When the active AssemblyList raises
|
||||
// a Reset CollectionChanged event (typically when a different list is loaded
|
||||
// from the dropdown), the analyzer pane's results are stale by definition —
|
||||
// every Analyzed*TreeNode's symbol references a module the new list may not
|
||||
// contain. The root must wipe its children rather than risk a stale navigation.
|
||||
|
||||
var window = AppComposition.Current.GetExport<MainWindow>(); |
||||
window.Show(); |
||||
var vm = (MainWindowViewModel)window.DataContext!; |
||||
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1); |
||||
|
||||
var root = new AnalyzerRootNode(); |
||||
var coreLibName = typeof(object).Assembly.GetName().Name!; |
||||
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName); |
||||
var module = assemblyNode.LoadedAssembly.GetMetadataFileOrNull(); |
||||
module.Should().NotBeNull("the test needs a loaded CoreLib module to seed a sample analyzed entry"); |
||||
|
||||
var typeSystem = module!.GetTypeSystemOrNull(); |
||||
typeSystem.Should().NotBeNull(); |
||||
var sampleType = typeSystem!.MainModule.TopLevelTypeDefinitions.First(); |
||||
root.Children.Add(new AnalyzedTypeTreeNode(sampleType, source: null)); |
||||
root.Children.Should().HaveCount(1); |
||||
|
||||
// Simulate a Reset by firing the collection-changed pathway the root subscribes to.
|
||||
// We do this through the live AssemblyList rather than reaching into private subscribers
|
||||
// — the active list firing CollectionChanged with Reset is what triggers the same flow
|
||||
// at app-runtime.
|
||||
var assemblyList = vm.AssemblyTreeModel.AssemblyList!; |
||||
var snapshot = assemblyList.GetAssemblies(); |
||||
// Clearing then re-adding the assemblies emits a Reset on the underlying ObservableCollection.
|
||||
assemblyList.Clear(); |
||||
root.Children.Should().BeEmpty( |
||||
"AnalyzerRootNode.CurrentAssemblyList_Changed must wipe children on Reset"); |
||||
|
||||
// Restore the assemblies so subsequent tests don't see an empty list.
|
||||
foreach (var asm in snapshot) |
||||
assemblyList.OpenAssembly(asm.FileName); |
||||
} |
||||
} |
||||
@ -0,0 +1,45 @@
@@ -0,0 +1,45 @@
|
||||
// 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; |
||||
|
||||
using ICSharpCode.Decompiler.TypeSystem; |
||||
|
||||
namespace ILSpy.Analyzers.TreeNodes |
||||
{ |
||||
/// <summary>
|
||||
/// Renders a property getter / setter or event add / remove / invoke as a short
|
||||
/// "get" / "set" / "add" / "remove" label under its containing entity. Inherits the
|
||||
/// method-node analyser plumbing so the accessor still surfaces "Used By" / "Uses"
|
||||
/// children at its own level.
|
||||
/// </summary>
|
||||
internal sealed class AnalyzedAccessorTreeNode : AnalyzedMethodTreeNode |
||||
{ |
||||
readonly string name; |
||||
|
||||
public AnalyzedAccessorTreeNode(IMethod analyzedMethod, IEntity? source, string name) |
||||
: base(analyzedMethod, source) |
||||
{ |
||||
if (string.IsNullOrWhiteSpace(name)) |
||||
throw new ArgumentException("name must be a non-empty string", nameof(name)); |
||||
this.name = name; |
||||
} |
||||
|
||||
public override object Text => name; |
||||
} |
||||
} |
||||
@ -0,0 +1,85 @@
@@ -0,0 +1,85 @@
|
||||
// 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; |
||||
using System.Diagnostics.CodeAnalysis; |
||||
|
||||
using ICSharpCode.Decompiler.Output; |
||||
using ICSharpCode.Decompiler.TypeSystem; |
||||
|
||||
namespace ILSpy.Analyzers.TreeNodes |
||||
{ |
||||
internal sealed class AnalyzedEventTreeNode : AnalyzerEntityTreeNode |
||||
{ |
||||
readonly IEvent analyzedEvent; |
||||
readonly string prefix; |
||||
|
||||
public AnalyzedEventTreeNode(IEvent analyzedEvent, IEntity? source, string prefix = "") |
||||
{ |
||||
this.analyzedEvent = analyzedEvent ?? throw new ArgumentNullException(nameof(analyzedEvent)); |
||||
this.SourceMember = source; |
||||
this.prefix = prefix; |
||||
LazyLoading = true; |
||||
} |
||||
|
||||
public override IEntity Member => analyzedEvent; |
||||
|
||||
public override object Text => prefix + Language.EntityToString(analyzedEvent, |
||||
ConversionFlags.ShowDeclaringType | ConversionFlags.UseFullyQualifiedEntityNames); |
||||
|
||||
public override object Icon => Images.Images.GetIcon(Images.Images.Event, |
||||
Images.Images.GetOverlay(analyzedEvent.Accessibility), analyzedEvent.IsStatic); |
||||
|
||||
protected override void LoadChildren() |
||||
{ |
||||
if (analyzedEvent.CanAdd) |
||||
this.Children.Add(new AnalyzedAccessorTreeNode(analyzedEvent.AddAccessor!, this.SourceMember, "add")); |
||||
if (analyzedEvent.CanRemove) |
||||
this.Children.Add(new AnalyzedAccessorTreeNode(analyzedEvent.RemoveAccessor!, this.SourceMember, "remove")); |
||||
if (TryFindBackingField(analyzedEvent, out var backingField)) |
||||
this.Children.Add(new AnalyzedFieldTreeNode(backingField, this.SourceMember)); |
||||
|
||||
foreach (var factory in Analyzers) |
||||
{ |
||||
var analyzer = factory.CreateExport().Value; |
||||
if (analyzer.Show(analyzedEvent)) |
||||
{ |
||||
this.Children.Add( |
||||
new AnalyzerSearchTreeNode(analyzedEvent, analyzer, factory.Metadata?.Header)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
static bool TryFindBackingField(IEvent analyzedEvent, [NotNullWhen(true)] out IField? backingField) |
||||
{ |
||||
backingField = null; |
||||
var declaringType = analyzedEvent.DeclaringTypeDefinition; |
||||
if (declaringType == null) |
||||
return false; |
||||
foreach (var field in declaringType.GetFields(options: GetMemberOptions.IgnoreInheritedMembers)) |
||||
{ |
||||
if (field.Name == analyzedEvent.Name && field.Accessibility == Accessibility.Private) |
||||
{ |
||||
backingField = field; |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,58 @@
@@ -0,0 +1,58 @@
|
||||
// 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; |
||||
|
||||
using ICSharpCode.Decompiler.Output; |
||||
using ICSharpCode.Decompiler.TypeSystem; |
||||
|
||||
namespace ILSpy.Analyzers.TreeNodes |
||||
{ |
||||
internal sealed class AnalyzedFieldTreeNode : AnalyzerEntityTreeNode |
||||
{ |
||||
readonly IField analyzedField; |
||||
|
||||
public AnalyzedFieldTreeNode(IField analyzedField, IEntity? source) |
||||
{ |
||||
this.analyzedField = analyzedField ?? throw new ArgumentNullException(nameof(analyzedField)); |
||||
this.SourceMember = source; |
||||
LazyLoading = true; |
||||
} |
||||
|
||||
public override IEntity Member => analyzedField; |
||||
|
||||
public override object Text => Language.EntityToString(analyzedField, |
||||
ConversionFlags.ShowDeclaringType | ConversionFlags.UseFullyQualifiedEntityNames); |
||||
|
||||
public override object Icon => Images.Images.GetIcon(Images.Images.Field, |
||||
Images.Images.GetOverlay(analyzedField.Accessibility), analyzedField.IsStatic); |
||||
|
||||
protected override void LoadChildren() |
||||
{ |
||||
foreach (var factory in Analyzers) |
||||
{ |
||||
var analyzer = factory.CreateExport().Value; |
||||
if (analyzer.Show(analyzedField)) |
||||
{ |
||||
this.Children.Add( |
||||
new AnalyzerSearchTreeNode(analyzedField, analyzer, factory.Metadata?.Header)); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,73 @@
@@ -0,0 +1,73 @@
|
||||
// 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; |
||||
|
||||
using ICSharpCode.Decompiler.Output; |
||||
using ICSharpCode.Decompiler.TypeSystem; |
||||
|
||||
namespace ILSpy.Analyzers.TreeNodes |
||||
{ |
||||
internal class AnalyzedMethodTreeNode : AnalyzerEntityTreeNode |
||||
{ |
||||
readonly IMethod analyzedMethod; |
||||
readonly string prefix; |
||||
|
||||
public AnalyzedMethodTreeNode(IMethod analyzedMethod, IEntity? source, string prefix = "") |
||||
{ |
||||
this.analyzedMethod = analyzedMethod ?? throw new ArgumentNullException(nameof(analyzedMethod)); |
||||
this.SourceMember = source; |
||||
this.prefix = prefix; |
||||
LazyLoading = true; |
||||
} |
||||
|
||||
public override IEntity Member => analyzedMethod; |
||||
|
||||
public override object Text |
||||
=> prefix + Language.EntityToString(analyzedMethod, |
||||
ConversionFlags.ShowDeclaringType | ConversionFlags.UseFullyQualifiedEntityNames); |
||||
|
||||
public override object Icon => ResolveIcon(analyzedMethod); |
||||
|
||||
internal static object ResolveIcon(IMethod method) |
||||
{ |
||||
var baseImage = method.IsConstructor |
||||
? Images.Images.Constructor |
||||
: method.IsOperator |
||||
? Images.Images.Operator |
||||
: Images.Images.Method; |
||||
return Images.Images.GetIcon(baseImage, |
||||
Images.Images.GetOverlay(method.Accessibility), |
||||
method.IsStatic, |
||||
method.IsExtensionMethod); |
||||
} |
||||
|
||||
protected override void LoadChildren() |
||||
{ |
||||
foreach (var factory in Analyzers) |
||||
{ |
||||
var analyzer = factory.CreateExport().Value; |
||||
if (analyzer.Show(analyzedMethod)) |
||||
{ |
||||
this.Children.Add( |
||||
new AnalyzerSearchTreeNode(analyzedMethod, analyzer, factory.Metadata?.Header)); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,81 @@
@@ -0,0 +1,81 @@
|
||||
// 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; |
||||
using System.Collections.Generic; |
||||
|
||||
using ICSharpCode.Decompiler.TypeSystem; |
||||
using ICSharpCode.ILSpyX; |
||||
|
||||
namespace ILSpy.Analyzers.TreeNodes |
||||
{ |
||||
/// <summary>
|
||||
/// Wraps an entire module as an analysable entry. <see cref="Member"/> is
|
||||
/// <see langword="null"/> — module-level analyses (e.g. "Applied To" against an
|
||||
/// assembly-level attribute) hang their results off this row.
|
||||
/// </summary>
|
||||
internal sealed class AnalyzedModuleTreeNode : AnalyzerEntityTreeNode |
||||
{ |
||||
readonly IModule analyzedModule; |
||||
|
||||
public AnalyzedModuleTreeNode(IModule analyzedModule, IEntity? source) |
||||
{ |
||||
this.analyzedModule = analyzedModule ?? throw new ArgumentNullException(nameof(analyzedModule)); |
||||
this.SourceMember = source; |
||||
LazyLoading = true; |
||||
} |
||||
|
||||
public override IEntity? Member => null; |
||||
|
||||
public override object Text => analyzedModule.AssemblyName; |
||||
|
||||
public override object Icon => Images.Images.Assembly; |
||||
|
||||
public override object? ToolTip => analyzedModule.MetadataFile?.FileName; |
||||
|
||||
public IModule Module => analyzedModule; |
||||
|
||||
protected override void LoadChildren() |
||||
{ |
||||
foreach (var factory in Analyzers) |
||||
{ |
||||
var analyzer = factory.CreateExport().Value; |
||||
if (analyzer.Show(analyzedModule)) |
||||
{ |
||||
this.Children.Add( |
||||
new AnalyzerSearchTreeNode(analyzedModule, analyzer, factory.Metadata?.Header)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public override bool HandleAssemblyListChanged( |
||||
ICollection<LoadedAssembly> removedAssemblies, |
||||
ICollection<LoadedAssembly> addedAssemblies) |
||||
{ |
||||
foreach (var asm in removedAssemblies) |
||||
{ |
||||
if (analyzedModule.MetadataFile == asm.GetMetadataFileOrNull()) |
||||
return false; // module is gone — drop me
|
||||
} |
||||
this.Children.RemoveAll(node => |
||||
node is not AnalyzerTreeNode an |
||||
|| !an.HandleAssemblyListChanged(removedAssemblies, addedAssemblies)); |
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,65 @@
@@ -0,0 +1,65 @@
|
||||
// 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; |
||||
|
||||
using ICSharpCode.Decompiler.Output; |
||||
using ICSharpCode.Decompiler.TypeSystem; |
||||
|
||||
namespace ILSpy.Analyzers.TreeNodes |
||||
{ |
||||
internal sealed class AnalyzedPropertyTreeNode : AnalyzerEntityTreeNode |
||||
{ |
||||
readonly IProperty analyzedProperty; |
||||
readonly string prefix; |
||||
|
||||
public AnalyzedPropertyTreeNode(IProperty analyzedProperty, IEntity? source, string prefix = "") |
||||
{ |
||||
this.analyzedProperty = analyzedProperty ?? throw new ArgumentNullException(nameof(analyzedProperty)); |
||||
this.SourceMember = source; |
||||
this.prefix = prefix; |
||||
LazyLoading = true; |
||||
} |
||||
|
||||
public override IEntity Member => analyzedProperty; |
||||
|
||||
public override object Text => prefix + Language.EntityToString(analyzedProperty, |
||||
ConversionFlags.ShowDeclaringType | ConversionFlags.UseFullyQualifiedEntityNames); |
||||
|
||||
public override object Icon => Images.Images.GetIcon(Images.Images.Property, |
||||
Images.Images.GetOverlay(analyzedProperty.Accessibility), analyzedProperty.IsStatic); |
||||
|
||||
protected override void LoadChildren() |
||||
{ |
||||
if (analyzedProperty.CanGet) |
||||
this.Children.Add(new AnalyzedAccessorTreeNode(analyzedProperty.Getter!, this.SourceMember, "get")); |
||||
if (analyzedProperty.CanSet) |
||||
this.Children.Add(new AnalyzedAccessorTreeNode(analyzedProperty.Setter!, this.SourceMember, "set")); |
||||
|
||||
foreach (var factory in Analyzers) |
||||
{ |
||||
var analyzer = factory.CreateExport().Value; |
||||
if (analyzer.Show(analyzedProperty)) |
||||
{ |
||||
this.Children.Add( |
||||
new AnalyzerSearchTreeNode(analyzedProperty, analyzer, factory.Metadata?.Header)); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,68 @@
@@ -0,0 +1,68 @@
|
||||
// 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; |
||||
|
||||
using ICSharpCode.Decompiler.TypeSystem; |
||||
|
||||
namespace ILSpy.Analyzers.TreeNodes |
||||
{ |
||||
internal sealed class AnalyzedTypeTreeNode : AnalyzerEntityTreeNode |
||||
{ |
||||
readonly ITypeDefinition analyzedType; |
||||
|
||||
public AnalyzedTypeTreeNode(ITypeDefinition analyzedType, IEntity? source) |
||||
{ |
||||
this.analyzedType = analyzedType ?? throw new ArgumentNullException(nameof(analyzedType)); |
||||
this.SourceMember = source; |
||||
LazyLoading = true; |
||||
} |
||||
|
||||
public override IEntity Member => analyzedType; |
||||
|
||||
public override object Text => Language.TypeToString(analyzedType); |
||||
|
||||
public override object Icon => ResolveIcon(analyzedType); |
||||
|
||||
static object ResolveIcon(ITypeDefinition type) |
||||
{ |
||||
var baseImage = type.Kind switch { |
||||
TypeKind.Interface => Images.Images.Interface, |
||||
TypeKind.Struct or TypeKind.Void => Images.Images.Struct, |
||||
TypeKind.Delegate => Images.Images.Delegate, |
||||
TypeKind.Enum => Images.Images.Enum, |
||||
_ => Images.Images.Class, |
||||
}; |
||||
return Images.Images.GetIcon(baseImage, |
||||
Images.Images.GetOverlay(type.Accessibility), type.IsStatic); |
||||
} |
||||
|
||||
protected override void LoadChildren() |
||||
{ |
||||
foreach (var factory in Analyzers) |
||||
{ |
||||
var analyzer = factory.CreateExport().Value; |
||||
if (analyzer.Show(analyzedType)) |
||||
{ |
||||
this.Children.Add( |
||||
new AnalyzerSearchTreeNode(analyzedType, analyzer, factory.Metadata?.Header)); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,69 @@
@@ -0,0 +1,69 @@
|
||||
// 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.Collections.Generic; |
||||
|
||||
using ICSharpCode.Decompiler.TypeSystem; |
||||
using ICSharpCode.ILSpyX; |
||||
using ICSharpCode.ILSpyX.TreeView; |
||||
|
||||
namespace ILSpy.Analyzers |
||||
{ |
||||
/// <summary>
|
||||
/// Base for nodes that wrap an <see cref="IEntity"/> in the analyzer pane (the
|
||||
/// per-entity root row plus every analyser result row underneath an
|
||||
/// <see cref="AnalyzerSearchTreeNode"/>). Concrete subclasses supply the entity, its
|
||||
/// icon, and its text; this base owns the navigation hook and the assembly-change
|
||||
/// pruning logic.
|
||||
/// </summary>
|
||||
public abstract class AnalyzerEntityTreeNode : AnalyzerTreeNode |
||||
{ |
||||
/// <summary>
|
||||
/// The entity this row analyses (or represents as an analyser result). Subclasses
|
||||
/// return <see langword="null"/> only for non-entity rows such as
|
||||
/// <see cref="ILSpy.Analyzers.TreeNodes.AnalyzedModuleTreeNode"/>.
|
||||
/// </summary>
|
||||
public abstract IEntity? Member { get; } |
||||
|
||||
/// <summary>
|
||||
/// The entity that the user originally clicked "Analyze" on. Used by analyser
|
||||
/// result rows to reverse-look-up the analysis they belong to (e.g. for the
|
||||
/// "Remove" context menu entry).
|
||||
/// </summary>
|
||||
public IEntity? SourceMember { get; protected set; } |
||||
|
||||
public override object? ToolTip => Member?.ParentModule?.MetadataFile?.FileName; |
||||
|
||||
public override bool HandleAssemblyListChanged( |
||||
ICollection<LoadedAssembly> removedAssemblies, |
||||
ICollection<LoadedAssembly> addedAssemblies) |
||||
{ |
||||
if (Member == null) |
||||
return true; |
||||
foreach (var asm in removedAssemblies) |
||||
{ |
||||
if (this.Member.ParentModule?.MetadataFile == asm.GetMetadataFileOrNull()) |
||||
return false; // ask parent to drop me — my module is gone
|
||||
} |
||||
this.Children.RemoveAll(node => |
||||
node is not AnalyzerTreeNode an |
||||
|| !an.HandleAssemblyListChanged(removedAssemblies, addedAssemblies)); |
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,34 @@
@@ -0,0 +1,34 @@
|
||||
// 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.Analyzers |
||||
{ |
||||
/// <summary>
|
||||
/// Concrete metadata view consumed by <see cref="AnalyzerRegistry"/>. System.Composition
|
||||
/// only projects metadata through a class with a parameterless constructor and public
|
||||
/// settable properties — the shared <c>IAnalyzerMetadata</c> interface doesn't qualify.
|
||||
/// Property names mirror <c>ExportAnalyzerAttribute</c>'s so the framework copies values
|
||||
/// across by name.
|
||||
/// </summary>
|
||||
public sealed class AnalyzerMetadata |
||||
{ |
||||
public string? Header { get; set; } |
||||
|
||||
public int Order { get; set; } |
||||
} |
||||
} |
||||
@ -0,0 +1,49 @@
@@ -0,0 +1,49 @@
|
||||
// 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.Collections.Generic; |
||||
using System.Composition; |
||||
using System.Linq; |
||||
|
||||
using ICSharpCode.ILSpyX.Analyzers; |
||||
|
||||
namespace ILSpy.Analyzers |
||||
{ |
||||
/// <summary>
|
||||
/// MEF aggregator for <see cref="ExportAnalyzerAttribute"/>-tagged analyzers. System.Composition
|
||||
/// only resolves <c>[ImportMany]</c> with metadata through constructor injection, so this
|
||||
/// registry is the single place that pulls the factories out of the composition host. Each
|
||||
/// <see cref="AnalyzerEntityTreeNode"/> reads <see cref="Analyzers"/> through the static
|
||||
/// accessor on <see cref="AnalyzerTreeNode"/>, which in turn resolves this registry once.
|
||||
/// </summary>
|
||||
[Export] |
||||
[Shared] |
||||
public sealed class AnalyzerRegistry |
||||
{ |
||||
[ImportingConstructor] |
||||
public AnalyzerRegistry( |
||||
[ImportMany("Analyzer")] IEnumerable<ExportFactory<IAnalyzer, AnalyzerMetadata>> analyzers) |
||||
{ |
||||
Analyzers = analyzers |
||||
.OrderBy(a => a.Metadata?.Order ?? 0) |
||||
.ToArray(); |
||||
} |
||||
|
||||
public IReadOnlyList<ExportFactory<IAnalyzer, AnalyzerMetadata>> Analyzers { get; } |
||||
} |
||||
} |
||||
@ -0,0 +1,115 @@
@@ -0,0 +1,115 @@
|
||||
// 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.Collections.Generic; |
||||
using System.Collections.Specialized; |
||||
using System.ComponentModel; |
||||
using System.Linq; |
||||
|
||||
using ICSharpCode.ILSpyX; |
||||
using ICSharpCode.ILSpyX.TreeView; |
||||
|
||||
using ILSpy.AppEnv; |
||||
using ILSpy.AssemblyTree; |
||||
|
||||
namespace ILSpy.Analyzers |
||||
{ |
||||
/// <summary>
|
||||
/// Root of the analyzer pane's tree. Holds one <see cref="AnalyzerEntityTreeNode"/>
|
||||
/// per analysed entity and prunes stale entries when the active
|
||||
/// <see cref="AssemblyList"/> changes — either an assembly is closed (matching
|
||||
/// children dropped) or the list itself is swapped out (entire subtree wiped).
|
||||
/// </summary>
|
||||
public sealed class AnalyzerRootNode : AnalyzerTreeNode |
||||
{ |
||||
readonly AssemblyTreeModel? assemblyTreeModel; |
||||
AssemblyList? activeList; |
||||
|
||||
public AnalyzerRootNode() |
||||
: this(TryGetAssemblyTreeModel()) |
||||
{ |
||||
} |
||||
|
||||
internal AnalyzerRootNode(AssemblyTreeModel? assemblyTreeModel) |
||||
{ |
||||
this.assemblyTreeModel = assemblyTreeModel; |
||||
if (assemblyTreeModel == null) |
||||
return; |
||||
assemblyTreeModel.PropertyChanged += OnModelPropertyChanged; |
||||
AttachToActiveList(assemblyTreeModel.AssemblyList); |
||||
} |
||||
|
||||
static AssemblyTreeModel? TryGetAssemblyTreeModel() |
||||
{ |
||||
try |
||||
{ |
||||
return AppComposition.Current.GetExport<AssemblyTreeModel>(); |
||||
} |
||||
catch |
||||
{ |
||||
// Design-time previews / minimal tests boot without the full composition.
|
||||
// The pane still works as a passive container in that case.
|
||||
return null; |
||||
} |
||||
} |
||||
|
||||
void OnModelPropertyChanged(object? sender, PropertyChangedEventArgs e) |
||||
{ |
||||
if (e.PropertyName != nameof(AssemblyTreeModel.AssemblyList)) |
||||
return; |
||||
AttachToActiveList(assemblyTreeModel?.AssemblyList); |
||||
// A new active list means every analysed entity references a module that may
|
||||
// not be reachable any more — wipe the tree the same way a Reset on the old
|
||||
// list would have.
|
||||
this.Children.Clear(); |
||||
} |
||||
|
||||
void AttachToActiveList(AssemblyList? list) |
||||
{ |
||||
if (ReferenceEquals(activeList, list)) |
||||
return; |
||||
if (activeList != null) |
||||
activeList.CollectionChanged -= OnAssemblyListCollectionChanged; |
||||
activeList = list; |
||||
if (activeList != null) |
||||
activeList.CollectionChanged += OnAssemblyListCollectionChanged; |
||||
} |
||||
|
||||
void OnAssemblyListCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) |
||||
{ |
||||
if (e.Action == NotifyCollectionChangedAction.Reset) |
||||
{ |
||||
this.Children.Clear(); |
||||
return; |
||||
} |
||||
var removed = e.OldItems?.Cast<LoadedAssembly>().ToArray() ?? System.Array.Empty<LoadedAssembly>(); |
||||
var added = e.NewItems?.Cast<LoadedAssembly>().ToArray() ?? System.Array.Empty<LoadedAssembly>(); |
||||
HandleAssemblyListChanged(removed, added); |
||||
} |
||||
|
||||
public override bool HandleAssemblyListChanged( |
||||
ICollection<LoadedAssembly> removedAssemblies, |
||||
ICollection<LoadedAssembly> addedAssemblies) |
||||
{ |
||||
this.Children.RemoveAll(node => |
||||
node is not AnalyzerTreeNode an |
||||
|| !an.HandleAssemblyListChanged(removedAssemblies, addedAssemblies)); |
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,73 @@
@@ -0,0 +1,73 @@
|
||||
// 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; |
||||
using System.Collections.Generic; |
||||
|
||||
using ICSharpCode.Decompiler.TypeSystem; |
||||
using ICSharpCode.ILSpyX; |
||||
using ICSharpCode.ILSpyX.Analyzers; |
||||
|
||||
namespace ILSpy.Analyzers |
||||
{ |
||||
/// <summary>
|
||||
/// Row that runs a single <see cref="IAnalyzer"/> against an analysed symbol and
|
||||
/// shows its results as its lazy-loaded children. The shell defined here holds the
|
||||
/// analyser + symbol + header triple; the background-fetch wiring (Task.Run,
|
||||
/// "Loading…" placeholder, count + elapsed-time text update, cancellation) lands in
|
||||
/// a follow-up commit.
|
||||
/// </summary>
|
||||
public class AnalyzerSearchTreeNode : AnalyzerTreeNode |
||||
{ |
||||
readonly ISymbol analyzedSymbol; |
||||
readonly IAnalyzer analyzer; |
||||
|
||||
public AnalyzerSearchTreeNode(ISymbol analyzedSymbol, IAnalyzer analyzer, string? analyzerHeader) |
||||
{ |
||||
this.analyzedSymbol = analyzedSymbol ?? throw new ArgumentNullException(nameof(analyzedSymbol)); |
||||
this.analyzer = analyzer ?? throw new ArgumentNullException(nameof(analyzer)); |
||||
AnalyzerHeader = analyzerHeader ?? string.Empty; |
||||
LazyLoading = true; |
||||
} |
||||
|
||||
/// <summary>The analyser whose <see cref="IAnalyzer.Analyze"/> drives this row.</summary>
|
||||
public IAnalyzer Analyzer => analyzer; |
||||
|
||||
/// <summary>The symbol being analysed (a type, method, field, …).</summary>
|
||||
public ISymbol AnalyzedSymbol => analyzedSymbol; |
||||
|
||||
/// <summary>
|
||||
/// The header string this row started with (e.g. "Used By"). Updated in-place by
|
||||
/// the background-fetch pipeline to include the result count and elapsed time
|
||||
/// once the analyser completes.
|
||||
/// </summary>
|
||||
public string AnalyzerHeader { get; protected set; } |
||||
|
||||
public override object Text => AnalyzerHeader; |
||||
|
||||
public override bool HandleAssemblyListChanged( |
||||
ICollection<LoadedAssembly> removedAssemblies, |
||||
ICollection<LoadedAssembly> addedAssemblies) |
||||
{ |
||||
this.Children.RemoveAll(node => |
||||
node is not AnalyzerTreeNode an |
||||
|| !an.HandleAssemblyListChanged(removedAssemblies, addedAssemblies)); |
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,75 @@
@@ -0,0 +1,75 @@
|
||||
// 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.Collections.Generic; |
||||
using System.Composition; |
||||
|
||||
using ICSharpCode.ILSpyX; |
||||
using ICSharpCode.ILSpyX.Analyzers; |
||||
using ICSharpCode.ILSpyX.TreeView; |
||||
|
||||
using ILSpy.AppEnv; |
||||
using ILSpy.Languages; |
||||
|
||||
namespace ILSpy.Analyzers |
||||
{ |
||||
/// <summary>
|
||||
/// Base for every node in the analyzer pane. The pane root, the per-entity wrappers
|
||||
/// (<see cref="AnalyzerEntityTreeNode"/>), and the per-analyzer "search" headers
|
||||
/// (<see cref="AnalyzerSearchTreeNode"/>) all derive from this so the root's
|
||||
/// <c>HandleAssemblyListChanged</c> sweep can recurse uniformly.
|
||||
/// </summary>
|
||||
public abstract class AnalyzerTreeNode : SharpTreeNode |
||||
{ |
||||
static LanguageService? cachedLanguageService; |
||||
static AnalyzerRegistry? cachedRegistry; |
||||
|
||||
/// <summary>
|
||||
/// The active language used to format entity text. Resolved lazily through the
|
||||
/// composition host so design-time previews (no MEF) don't NRE during XAML reload.
|
||||
/// </summary>
|
||||
protected static Languages.Language Language |
||||
=> (cachedLanguageService ??= AppComposition.Current.GetExport<LanguageService>()).CurrentLanguage; |
||||
|
||||
/// <summary>
|
||||
/// All MEF-registered <see cref="IAnalyzer"/> exports, ordered by their declared
|
||||
/// <see cref="AnalyzerMetadata.Order"/>. Each <see cref="AnalyzerEntityTreeNode"/>
|
||||
/// walks this list on <c>LoadChildren</c> and instantiates an
|
||||
/// <see cref="AnalyzerSearchTreeNode"/> for every entry whose
|
||||
/// <see cref="IAnalyzer.Show"/> returns true for the wrapped entity.
|
||||
/// </summary>
|
||||
public static IReadOnlyList<ExportFactory<IAnalyzer, AnalyzerMetadata>> Analyzers |
||||
=> (cachedRegistry ??= AppComposition.Current.GetExport<AnalyzerRegistry>()).Analyzers; |
||||
|
||||
public override bool CanDelete() => Parent is { IsRoot: true }; |
||||
|
||||
public override void DeleteCore() => Parent?.Children.Remove(this); |
||||
|
||||
public override void Delete() => DeleteCore(); |
||||
|
||||
/// <summary>
|
||||
/// Reacts to add / remove events on the active <see cref="AssemblyList"/>. Each
|
||||
/// node decides whether it (and its subtree) is still relevant — typically by
|
||||
/// checking the removed list against its source module — and returns <c>false</c>
|
||||
/// to ask its parent to drop it.
|
||||
/// </summary>
|
||||
public abstract bool HandleAssemblyListChanged( |
||||
ICollection<LoadedAssembly> removedAssemblies, |
||||
ICollection<LoadedAssembly> addedAssemblies); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue