Commit: 08c3ed7
Parent: a1f390f

Probably reach implementations of used interfaces

Mårten Åsberg committed on 2026-03-14 at 18:58
Core/ISymbolExtensions.cs +46 -0
diff --git a/Core/ISymbolExtensions.cs b/Core/ISymbolExtensions.cs
new file mode 100644
index 0000000..d151ab7
@@ -0,0 +1,46 @@
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
namespace Reacher;
internal static class ISymbolExtensions
{
public static HashSet<ISymbol> ExplicitOrImplicitInterfaceImplementations(this ISymbol member) =>
[.. member.ExplicitInterfaceImplementations(), .. member.ImplicitInterfaceImplementations()];
public static HashSet<ISymbol> ExplicitInterfaceImplementations(this ISymbol member) =>
member switch
{
IMethodSymbol method => [.. method.ExplicitInterfaceImplementations],
IPropertySymbol property => [.. property.ExplicitInterfaceImplementations],
_ => [],
};
public static HashSet<ISymbol> ImplicitInterfaceImplementations(this ISymbol member)
{
var interfaceMembers = new HashSet<ISymbol>(SymbolEqualityComparer.Default);
foreach (var interfaceType in member.ContainingType.AllInterfaces)
{
foreach (var interfaceMember in interfaceType.GetMembers())
{
var implementation = member.ContainingType.FindImplementationForInterfaceMember(interfaceMember);
if (SymbolEqualityComparer.Default.Equals(member, implementation))
{
interfaceMembers.Add(interfaceMember);
}
}
}
return interfaceMembers;
}
public static ISymbol? GetOverriddenMember(this ISymbol member) =>
member switch
{
IMethodSymbol method => method.OverriddenMethod,
IPropertySymbol property => property.OverriddenProperty,
IEventSymbol @event => @event.OverriddenEvent,
_ => null,
};
}
Core/ReachabilityAnalysis.cs +28 -2
diff --git a/Core/ReachabilityAnalysis.cs b/Core/ReachabilityAnalysis.cs
index d6fb220..e492dfe 100644
@@ -11,6 +11,32 @@ namespace Reacher;
/// The members the analysis deemed unreachable from the original starting members.
/// </param>
public sealed record ReachabilityAnalysis(
IReadOnlySet<ISymbol> ReachableMembers,
IReadOnlySet<ISymbol> UnreachableMembers
IReadOnlyCollection<ReachableMember> ReachableMembers,
IReadOnlyCollection<ISymbol> UnreachableMembers
);
/// <summary>
/// Describes a reachable member.
/// </summary>
/// <param name="Member">The reachable member.</param>
/// <param name="ReachabilityConfidence">The confidence for this member being reachable.</param>
public sealed record ReachableMember(ISymbol Member, ReachabilityConfidence ReachabilityConfidence);
/// <summary>
/// The confidence level of reachability.
/// </summary>
/// <remarks>
/// The backing values of the enum values are considered private and might change without notice.
/// </remarks>
public enum ReachabilityConfidence
{
/// <summary>
/// The confidence is absolute.
/// </summary>
DefinitelyReachable = 2,
/// <summary>
/// The reachability is probable, but cannot be proven.
/// </summary>
ProbablyReachable = 1,
}
Core/ReachabilityAnalyzer.cs +65 -16
diff --git a/Core/ReachabilityAnalyzer.cs b/Core/ReachabilityAnalyzer.cs
index 4d350de..5e6cd33 100644
@@ -6,30 +6,47 @@ using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Reacher.MemberCollectors;
namespace Reacher;
internal sealed class ReachabilityAnalyzer(IReadOnlyCollection<Compilation> compilations)
{
private readonly HashSet<ISymbol> reachableMembers = [];
private readonly Dictionary<ISymbol, ReachabilityConfidence> reachableMembers = [];
public void Analyze(ISymbol member, CancellationToken cancellationToken)
private readonly HashSet<ISymbol> implementableMembers = [];
public void Analyze(ISymbol member, CancellationToken cancellationToken) =>
Analyze(member, ReachabilityConfidence.DefinitelyReachable, cancellationToken);
private void Analyze(
ISymbol member,
ReachabilityConfidence reachabilityConfidence,
CancellationToken cancellationToken
)
{
member = GetCanonicalMemberSymbol(member);
if (
!IsUserDefined(member)
|| !TryGetAssociatedCompilation(member, out var compilation)
|| !reachableMembers.Add(member)
|| reachableMembers.TryGetValue(member, out var existingConfidence)
|| reachabilityConfidence < existingConfidence
)
{
return;
}
reachableMembers.Add(member, reachabilityConfidence);
foreach (var declaration in member.DeclaringSyntaxReferences)
{
AnalyzeDeclaration(compilation, declaration, cancellationToken);
AnalyzeDeclaration(compilation, declaration, reachabilityConfidence, cancellationToken);
}
if (member.IsAbstract || member.IsVirtual)
{
implementableMembers.Add(member);
}
}
@@ -54,6 +71,7 @@ internal sealed class ReachabilityAnalyzer(IReadOnlyCollection<Compilation> comp
private void AnalyzeDeclaration(
Compilation compilation,
SyntaxReference declaration,
ReachabilityConfidence reachabilityConfidence,
CancellationToken cancellationToken
)
{
@@ -80,25 +98,56 @@ internal sealed class ReachabilityAnalyzer(IReadOnlyCollection<Compilation> comp
{
continue;
}
Analyze(member, cancellationToken);
Analyze(member, reachabilityConfidence, cancellationToken);
}
}
private static Func<SyntaxNode, bool> ShouldDescendInto(SyntaxNode originalNode) =>
syntaxNode =>
syntaxNode == originalNode
|| !(
syntaxNode.IsKind(SyntaxKind.LocalFunctionStatement)
|| syntaxNode.IsKind(SyntaxKind.SimpleLambdaExpression)
|| syntaxNode.IsKind(SyntaxKind.ParenthesizedLambdaExpression)
);
syntaxNode => syntaxNode == originalNode || !syntaxNode.IsKind(SyntaxKind.LocalFunctionStatement);
public ReachabilityAnalysis GetAnalysis() => new(reachableMembers, GetUnreachableMembers());
public ReachabilityAnalysis GetAnalysis(CancellationToken cancellationToken)
{
var unreachableMembers = AnalyzeUnreachableMembersMembers(cancellationToken);
unreachableMembers = AnalyzeUnreachableMembersMembers(cancellationToken);
return new([.. reachableMembers.Select(kvp => new ReachableMember(kvp.Key, kvp.Value))], unreachableMembers);
}
private IReadOnlySet<ISymbol> GetUnreachableMembers()
private HashSet<ISymbol> AnalyzeUnreachableMembersMembers(CancellationToken cancellationToken)
{
var collector = new PredicateMembersCollector(m => IsUserDefined(m) && !reachableMembers.Contains(m));
var unreachableMembers = GetUnreachableMembers();
AnalyzeProbablyReachableMembers(unreachableMembers, cancellationToken);
unreachableMembers.ExceptWith(reachableMembers.Keys);
return unreachableMembers;
}
private HashSet<ISymbol> GetUnreachableMembers()
{
var collector = new PredicateMembersCollector(m => IsUserDefined(m) && !reachableMembers.ContainsKey(m));
compilations.Accept(collector);
return collector.Members;
return [.. collector.Members];
}
private void AnalyzeProbablyReachableMembers(
IReadOnlySet<ISymbol> unreachableMembers,
CancellationToken cancellationToken
)
{
foreach (var member in unreachableMembers)
{
if (IsProbablyReachable(member))
{
Analyze(member, ReachabilityConfidence.ProbablyReachable, cancellationToken);
}
}
}
private bool IsProbablyReachable(ISymbol member)
{
var possibleTransientTargets = member.ExplicitOrImplicitInterfaceImplementations();
if (member.GetOverriddenMember() is { } overriddenMember)
{
possibleTransientTargets.Add(overriddenMember);
}
return possibleTransientTargets.Any(reachableMembers.ContainsKey);
}
}
Core/SolutionExtensions.cs +1 -1
diff --git a/Core/SolutionExtensions.cs b/Core/SolutionExtensions.cs
index a568d08..38ca9f8 100644
@@ -79,7 +79,7 @@ public static class SolutionExtensions
analyzer.Analyze(entryPoint, cancellationToken);
}
return analyzer.GetAnalysis();
return analyzer.GetAnalysis(cancellationToken);
}
private static async Task<Compilation[]> GetCompilations(
Core/packages.lock.json +114 -0
diff --git a/Core/packages.lock.json b/Core/packages.lock.json
new file mode 100644
index 0000000..be5aba0
@@ -0,0 +1,114 @@
{
"version": 2,
"dependencies": {
"net10.0": {
"CSharpier.MsBuild": {
"type": "Direct",
"requested": "[1.2.6, )",
"resolved": "1.2.6",
"contentHash": "KMSJG+jfk7vjP52QkWB99qWespXCPAzG/IaMCMRHYWumJEAGKQYm2HtyWG6eqnOwDitH96i1cqq5EVesyOtPmg=="
},
"Microsoft.CodeAnalysis.CSharp.Workspaces": {
"type": "Direct",
"requested": "[5.0.0, )",
"resolved": "5.0.0",
"contentHash": "Al/Q8B+yO8odSqGVpSvrShMFDvlQdIBU//F3E6Rb0YdiLSALE9wh/pvozPNnfmh5HDnvU+mkmSjpz4hQO++jaA==",
"dependencies": {
"Humanizer.Core": "2.14.1",
"Microsoft.CodeAnalysis.Analyzers": "3.11.0",
"Microsoft.CodeAnalysis.CSharp": "[5.0.0]",
"Microsoft.CodeAnalysis.Common": "[5.0.0]",
"Microsoft.CodeAnalysis.Workspaces.Common": "[5.0.0]",
"System.Composition": "9.0.0"
}
},
"Humanizer.Core": {
"type": "Transitive",
"resolved": "2.14.1",
"contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw=="
},
"Microsoft.CodeAnalysis.Analyzers": {
"type": "Transitive",
"resolved": "3.11.0",
"contentHash": "v/EW3UE8/lbEYHoC2Qq7AR/DnmvpgdtAMndfQNmpuIMx/Mto8L5JnuCfdBYtgvalQOtfNCnxFejxuRrryvUTsg=="
},
"Microsoft.CodeAnalysis.Common": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "ZXRAdvH6GiDeHRyd3q/km8Z44RoM6FBWHd+gen/la81mVnAdHTEsEkO5J0TCNXBymAcx5UYKt5TvgKBhaLJEow==",
"dependencies": {
"Microsoft.CodeAnalysis.Analyzers": "3.11.0"
}
},
"Microsoft.CodeAnalysis.Workspaces.Common": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "ZbUmIvT6lqTNKiv06Jl5wf0MTMi1vQ1oH7ou4CLcs2C/no/L7EhP3T8y3XXvn9VbqMcJaJnEsNA1jwYUMgc5jg==",
"dependencies": {
"Humanizer.Core": "2.14.1",
"Microsoft.CodeAnalysis.Analyzers": "3.11.0",
"Microsoft.CodeAnalysis.Common": "[5.0.0]",
"System.Composition": "9.0.0"
}
},
"System.Composition": {
"type": "Transitive",
"resolved": "9.0.0",
"contentHash": "3Djj70fFTraOarSKmRnmRy/zm4YurICm+kiCtI0dYRqGJnLX6nJ+G3WYuFJ173cAPax/gh96REcbNiVqcrypFQ==",
"dependencies": {
"System.Composition.AttributedModel": "9.0.0",
"System.Composition.Convention": "9.0.0",
"System.Composition.Hosting": "9.0.0",
"System.Composition.Runtime": "9.0.0",
"System.Composition.TypedParts": "9.0.0"
}
},
"System.Composition.AttributedModel": {
"type": "Transitive",
"resolved": "9.0.0",
"contentHash": "iri00l/zIX9g4lHMY+Nz0qV1n40+jFYAmgsaiNn16xvt2RDwlqByNG4wgblagnDYxm3YSQQ0jLlC/7Xlk9CzyA=="
},
"System.Composition.Convention": {
"type": "Transitive",
"resolved": "9.0.0",
"contentHash": "+vuqVP6xpi582XIjJi6OCsIxuoTZfR0M7WWufk3uGDeCl3wGW6KnpylUJ3iiXdPByPE0vR5TjJgR6hDLez4FQg==",
"dependencies": {
"System.Composition.AttributedModel": "9.0.0"
}
},
"System.Composition.Hosting": {
"type": "Transitive",
"resolved": "9.0.0",
"contentHash": "OFqSeFeJYr7kHxDfaViGM1ymk7d4JxK//VSoNF9Ux0gpqkLsauDZpu89kTHHNdCWfSljbFcvAafGyBoY094btQ==",
"dependencies": {
"System.Composition.Runtime": "9.0.0"
}
},
"System.Composition.Runtime": {
"type": "Transitive",
"resolved": "9.0.0",
"contentHash": "w1HOlQY1zsOWYussjFGZCEYF2UZXgvoYnS94NIu2CBnAGMbXFAX8PY8c92KwUItPmowal68jnVLBCzdrWLeEKA=="
},
"System.Composition.TypedParts": {
"type": "Transitive",
"resolved": "9.0.0",
"contentHash": "aRZlojCCGEHDKqh43jaDgaVpYETsgd7Nx4g1zwLKMtv4iTo0627715ajEFNpEEBTgLmvZuv8K0EVxc3sM4NWJA==",
"dependencies": {
"System.Composition.AttributedModel": "9.0.0",
"System.Composition.Hosting": "9.0.0",
"System.Composition.Runtime": "9.0.0"
}
},
"Microsoft.CodeAnalysis.CSharp": {
"type": "CentralTransitive",
"requested": "[5.0.0, )",
"resolved": "5.0.0",
"contentHash": "5DSyJ9bk+ATuDy7fp2Zt0mJStDVKbBoiz1DyfAwSa+k4H4IwykAUcV3URelw5b8/iVbfSaOwkwmPUZH6opZKCw==",
"dependencies": {
"Microsoft.CodeAnalysis.Analyzers": "3.11.0",
"Microsoft.CodeAnalysis.Common": "[5.0.0]"
}
}
}
}
}
\ No newline at end of file
Poc/ITestInterface.cs +16 -0
diff --git a/Poc/ITestInterface.cs b/Poc/ITestInterface.cs
new file mode 100644
index 0000000..4ea6c22
@@ -0,0 +1,16 @@
namespace Reacher.Poc;
internal interface ITestInterface
{
void Test();
}
internal sealed class TestImplementation : ITestInterface
{
public void Test()
{
TransientThroughInterfaceImplementation();
}
private static void TransientThroughInterfaceImplementation() { }
}
Poc/Program.cs +18 -2
diff --git a/Poc/Program.cs b/Poc/Program.cs
index 6cdf0ac..871b7d6 100644
@@ -1,15 +1,20 @@
using System;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using Microsoft.Build.Locator;
using Microsoft.CodeAnalysis.MSBuild;
using Reacher;
using Reacher.Poc;
MSBuildLocator.RegisterDefaults();
using var workspace = MSBuildWorkspace.Create();
var solution = await workspace.OpenSolutionAsync(GetSolutionPath());
ITestInterface test = new TestImplementation();
test.Test();
Noop();
var analysis = args is []
@@ -17,9 +22,20 @@ var analysis = args is []
: await solution.AnalyzeReachabilityFromDocumentationCommentIds(args, default);
Console.WriteLine("Reachable members:");
foreach (var member in analysis.ReachableMembers)
foreach (
var r in analysis.ReachableMembers.Where(r =>
r.ReachabilityConfidence is ReachabilityConfidence.DefinitelyReachable
)
)
{
Console.WriteLine($"\t{member.GetDocumentationCommentId()}");
Console.WriteLine($"\t{r.Member.GetDocumentationCommentId()}");
}
Console.WriteLine("Probably reachable members:");
foreach (
var r in analysis.ReachableMembers.Where(r => r.ReachabilityConfidence is ReachabilityConfidence.ProbablyReachable)
)
{
Console.WriteLine($"\t{r.Member.GetDocumentationCommentId()}");
}
Console.WriteLine("Unreachable members:");
foreach (var member in analysis.UnreachableMembers)
Poc/packages.lock.json +207 -0
diff --git a/Poc/packages.lock.json b/Poc/packages.lock.json
new file mode 100644
index 0000000..ba6858b
@@ -0,0 +1,207 @@
{
"version": 2,
"dependencies": {
"net10.0": {
"CSharpier.MsBuild": {
"type": "Direct",
"requested": "[1.2.6, )",
"resolved": "1.2.6",
"contentHash": "KMSJG+jfk7vjP52QkWB99qWespXCPAzG/IaMCMRHYWumJEAGKQYm2HtyWG6eqnOwDitH96i1cqq5EVesyOtPmg=="
},
"Microsoft.Build.Framework": {
"type": "Direct",
"requested": "[18.3.3, )",
"resolved": "18.3.3",
"contentHash": "6lUd+/IQrxKz+++m2ehMnN+83bWrdvLf6S/iNH41d2RhuGE9etoCqosIBzjfkPwBoHih3touW/xl1ev+KUgkjw=="
},
"Microsoft.Build.Locator": {
"type": "Direct",
"requested": "[1.11.2, )",
"resolved": "1.11.2",
"contentHash": "tY+/S54G29CGsbL3slVu4vqtpciwVnb3fKOmrhgzEQmu/VziFaWmD/E1e/2KH7cDucuycGSkWsSXndBs5Uawow=="
},
"Microsoft.CodeAnalysis.CSharp.Workspaces": {
"type": "Direct",
"requested": "[5.0.0, )",
"resolved": "5.0.0",
"contentHash": "Al/Q8B+yO8odSqGVpSvrShMFDvlQdIBU//F3E6Rb0YdiLSALE9wh/pvozPNnfmh5HDnvU+mkmSjpz4hQO++jaA==",
"dependencies": {
"Humanizer.Core": "2.14.1",
"Microsoft.CodeAnalysis.Analyzers": "3.11.0",
"Microsoft.CodeAnalysis.CSharp": "[5.0.0]",
"Microsoft.CodeAnalysis.Common": "[5.0.0]",
"Microsoft.CodeAnalysis.Workspaces.Common": "[5.0.0]",
"System.Composition": "9.0.0"
}
},
"Microsoft.CodeAnalysis.Workspaces.MSBuild": {
"type": "Direct",
"requested": "[5.0.0, )",
"resolved": "5.0.0",
"contentHash": "/G+LVoAGMz6Ae8nm+PGLxSw+F5RjYx/J7irbTO5uKAPw1bxHyQJLc/YOnpDxt+EpPtYxvC9wvBsg/kETZp1F9Q==",
"dependencies": {
"Humanizer.Core": "2.14.1",
"Microsoft.Build.Framework": "17.11.31",
"Microsoft.CodeAnalysis.Analyzers": "3.11.0",
"Microsoft.CodeAnalysis.Workspaces.Common": "[5.0.0]",
"Microsoft.Extensions.DependencyInjection": "9.0.0",
"Microsoft.Extensions.Logging": "9.0.0",
"Microsoft.Extensions.Logging.Abstractions": "9.0.0",
"Microsoft.Extensions.Options": "9.0.0",
"Microsoft.Extensions.Primitives": "9.0.0",
"Microsoft.VisualStudio.SolutionPersistence": "1.0.52",
"Newtonsoft.Json": "13.0.3",
"System.Composition": "9.0.0"
}
},
"Humanizer.Core": {
"type": "Transitive",
"resolved": "2.14.1",
"contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw=="
},
"Microsoft.CodeAnalysis.Analyzers": {
"type": "Transitive",
"resolved": "3.11.0",
"contentHash": "v/EW3UE8/lbEYHoC2Qq7AR/DnmvpgdtAMndfQNmpuIMx/Mto8L5JnuCfdBYtgvalQOtfNCnxFejxuRrryvUTsg=="
},
"Microsoft.CodeAnalysis.Common": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "ZXRAdvH6GiDeHRyd3q/km8Z44RoM6FBWHd+gen/la81mVnAdHTEsEkO5J0TCNXBymAcx5UYKt5TvgKBhaLJEow==",
"dependencies": {
"Microsoft.CodeAnalysis.Analyzers": "3.11.0"
}
},
"Microsoft.CodeAnalysis.Workspaces.Common": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "ZbUmIvT6lqTNKiv06Jl5wf0MTMi1vQ1oH7ou4CLcs2C/no/L7EhP3T8y3XXvn9VbqMcJaJnEsNA1jwYUMgc5jg==",
"dependencies": {
"Humanizer.Core": "2.14.1",
"Microsoft.CodeAnalysis.Analyzers": "3.11.0",
"Microsoft.CodeAnalysis.Common": "[5.0.0]",
"System.Composition": "9.0.0"
}
},
"Microsoft.Extensions.DependencyInjection": {
"type": "Transitive",
"resolved": "9.0.0",
"contentHash": "MCPrg7v3QgNMr0vX4vzRXvkNGgLg8vKWX0nKCWUxu2uPyMsaRgiRc1tHBnbTcfJMhMKj2slE/j2M9oGkd25DNw==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "9.0.0",
"contentHash": "+6f2qv2a3dLwd5w6JanPIPs47CxRbnk+ZocMJUhv9NxP88VlOcJYZs9jY+MYSjxvady08bUZn6qgiNh7DadGgg=="
},
"Microsoft.Extensions.Logging": {
"type": "Transitive",
"resolved": "9.0.0",
"contentHash": "crjWyORoug0kK7RSNJBTeSE6VX8IQgLf3nUpTB9m62bPXp/tzbnOsnbe8TXEG0AASNaKZddnpHKw7fET8E++Pg==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.0",
"Microsoft.Extensions.Logging.Abstractions": "9.0.0",
"Microsoft.Extensions.Options": "9.0.0"
}
},
"Microsoft.Extensions.Logging.Abstractions": {
"type": "Transitive",
"resolved": "9.0.0",
"contentHash": "g0UfujELzlLbHoVG8kPKVBaW470Ewi+jnptGS9KUi6jcb+k2StujtK3m26DFSGGwQ/+bVgZfsWqNzlP6YOejvw==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0"
}
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "9.0.0",
"contentHash": "y2146b3jrPI3Q0lokKXdKLpmXqakYbDIPDV6r3M8SqvSf45WwOTzkyfDpxnZXJsJQEpAsAqjUq5Pu8RCJMjubg==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0",
"Microsoft.Extensions.Primitives": "9.0.0"
}
},
"Microsoft.Extensions.Primitives": {
"type": "Transitive",
"resolved": "9.0.0",
"contentHash": "N3qEBzmLMYiASUlKxxFIISP4AiwuPTHF5uCh+2CWSwwzAJiIYx0kBJsS30cp1nvhSySFAVi30jecD307jV+8Kg=="
},
"Microsoft.VisualStudio.SolutionPersistence": {
"type": "Transitive",
"resolved": "1.0.52",
"contentHash": "oNv2JtYXhpdJrX63nibx1JT3uCESOBQ1LAk7Dtz/sr0+laW0KRM6eKp4CZ3MHDR2siIkKsY8MmUkeP5DKkQQ5w=="
},
"Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.3",
"contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
},
"System.Composition": {
"type": "Transitive",
"resolved": "9.0.0",
"contentHash": "3Djj70fFTraOarSKmRnmRy/zm4YurICm+kiCtI0dYRqGJnLX6nJ+G3WYuFJ173cAPax/gh96REcbNiVqcrypFQ==",
"dependencies": {
"System.Composition.AttributedModel": "9.0.0",
"System.Composition.Convention": "9.0.0",
"System.Composition.Hosting": "9.0.0",
"System.Composition.Runtime": "9.0.0",
"System.Composition.TypedParts": "9.0.0"
}
},
"System.Composition.AttributedModel": {
"type": "Transitive",
"resolved": "9.0.0",
"contentHash": "iri00l/zIX9g4lHMY+Nz0qV1n40+jFYAmgsaiNn16xvt2RDwlqByNG4wgblagnDYxm3YSQQ0jLlC/7Xlk9CzyA=="
},
"System.Composition.Convention": {
"type": "Transitive",
"resolved": "9.0.0",
"contentHash": "+vuqVP6xpi582XIjJi6OCsIxuoTZfR0M7WWufk3uGDeCl3wGW6KnpylUJ3iiXdPByPE0vR5TjJgR6hDLez4FQg==",
"dependencies": {
"System.Composition.AttributedModel": "9.0.0"
}
},
"System.Composition.Hosting": {
"type": "Transitive",
"resolved": "9.0.0",
"contentHash": "OFqSeFeJYr7kHxDfaViGM1ymk7d4JxK//VSoNF9Ux0gpqkLsauDZpu89kTHHNdCWfSljbFcvAafGyBoY094btQ==",
"dependencies": {
"System.Composition.Runtime": "9.0.0"
}
},
"System.Composition.Runtime": {
"type": "Transitive",
"resolved": "9.0.0",
"contentHash": "w1HOlQY1zsOWYussjFGZCEYF2UZXgvoYnS94NIu2CBnAGMbXFAX8PY8c92KwUItPmowal68jnVLBCzdrWLeEKA=="
},
"System.Composition.TypedParts": {
"type": "Transitive",
"resolved": "9.0.0",
"contentHash": "aRZlojCCGEHDKqh43jaDgaVpYETsgd7Nx4g1zwLKMtv4iTo0627715ajEFNpEEBTgLmvZuv8K0EVxc3sM4NWJA==",
"dependencies": {
"System.Composition.AttributedModel": "9.0.0",
"System.Composition.Hosting": "9.0.0",
"System.Composition.Runtime": "9.0.0"
}
},
"Reacher": {
"type": "Project",
"dependencies": {
"Microsoft.CodeAnalysis.CSharp.Workspaces": "[5.0.0, )"
}
},
"Microsoft.CodeAnalysis.CSharp": {
"type": "CentralTransitive",
"requested": "[5.0.0, )",
"resolved": "5.0.0",
"contentHash": "5DSyJ9bk+ATuDy7fp2Zt0mJStDVKbBoiz1DyfAwSa+k4H4IwykAUcV3URelw5b8/iVbfSaOwkwmPUZH6opZKCw==",
"dependencies": {
"Microsoft.CodeAnalysis.Analyzers": "3.11.0",
"Microsoft.CodeAnalysis.Common": "[5.0.0]"
}
}
}
}
}
\ No newline at end of file