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

namespace Reacher;

public static class SolutionExtensions
{
    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
        );
    }

    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
        );
    }

    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);
}