📄 Core/SolutionExtensions.cs
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;

namespace Reacher;

/// <summary></summary>
public static class SolutionExtensions
{
    /// <summary>
    /// Runs a reachability analysis from all entrypoints of all projects in the solution.
    /// </summary>
    public static async Task<ReachabilityAnalysis> AnalyzeReachabilityFromEntryPoints(
        this Solution solution,
        CancellationToken cancellationToken
    )
    {
        var compilations = await solution.GetCompilations(cancellationToken);
        return AnalyzeReachability(
            compilations,
            [.. compilations.Select(c => c.GetEntryPoint(cancellationToken)).OfType<IMethodSymbol>()],
            cancellationToken
        );
    }

    /// <summary>
    /// Runs a reachability analysis from all public members of public types of all projects in the solution.
    /// </summary>
    public static async Task<ReachabilityAnalysis> AnalyzeReachabilityFromPublicMembers(
        this Solution solution,
        CancellationToken cancellationToken
    )
    {
        var compilations = await solution.GetCompilations(cancellationToken);
        return AnalyzeReachability(
            compilations,
            [.. compilations.SelectMany(CompilationExtensions.GetPublicMembers)],
            cancellationToken
        );
    }

    /// <summary>
    /// Runs a reachability analysis from the provided Documentation Comment IDs for members.
    /// </summary>
    /// <remarks>
    /// Documentation Comment IDs is usually the kind of member (M for method, P for property), the full namespace, the
    /// type (and any nested types), and the member name. If it is a method (with one or more parameters) or an indexer,
    /// the types of the parameters are listed too, with full namespace and type name.
    ///
    /// For example, this method would be called <c>M:Reacher.SolutionExtensions.AnalyzeReachabilityFromDocumentationCommentIds(Microsoft.CodeAnalysis.Solution,System.Collections.Generic.IEnumerable{System.String},System.Threading.CancellationToken)</c>.
    /// </remarks>
    public static async Task<ReachabilityAnalysis> AnalyzeReachabilityFromDocumentationCommentIds(
        this Solution solution,
        IEnumerable<string> documentationIds,
        CancellationToken cancellationToken
    )
    {
        var compilations = await solution.GetCompilations(cancellationToken);
        return AnalyzeReachability(
            compilations,
            DocumentationCommentIdMemberCollector.CollectMembers(documentationIds, compilations),
            cancellationToken
        );
    }

    private static ReachabilityAnalysis AnalyzeReachability(
        IReadOnlyCollection<Compilation> compilations,
        IEnumerable<IMethodSymbol> entryPoints,
        CancellationToken cancellationToken
    )
    {
        var analysis = new ReachabilityAnalysis(compilations);

        foreach (var entryPoint in entryPoints)
        {
            analysis.Analyze(entryPoint, cancellationToken);
        }

        return analysis;
    }

    private static async Task<Compilation[]> GetCompilations(
        this Solution solution,
        CancellationToken cancellationToken
    ) =>
        await solution
            .Projects.ToAsyncEnumerable()
            .Select(async (p, ct) => await p.GetCompilationAsync(ct))
            .OfType<Compilation>()
            .ToArrayAsync(cancellationToken);
}