📄 ReachabilityAnalysis.cs
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Reacher;

public sealed class ReachabilityAnalysis(IReadOnlyCollection<Compilation> compilations)
{
    private readonly HashSet<IMethodSymbol> reachableMembers = [];
    public IReadOnlySet<IMethodSymbol> ReachableMembers => reachableMembers;

    internal void Analyze(IMethodSymbol member, CancellationToken cancellationToken)
    {
        if (!TryGetAssociatedCompilation(member, out var compilation) || !reachableMembers.Add(member))
        {
            return;
        }

        foreach (var declaration in member.DeclaringSyntaxReferences)
        {
            AnalyzeDeclaration(compilation, declaration, cancellationToken);
        }
    }

    private bool TryGetAssociatedCompilation(IMethodSymbol member, [NotNullWhen(true)] out Compilation? compilation)
    {
        compilation = compilations.FirstOrDefault(c =>
            member.DeclaringSyntaxReferences.Any(d => c.ContainsSyntaxTree(d.SyntaxTree))
        );
        return compilation is not null;
    }

    private void AnalyzeDeclaration(
        Compilation compilation,
        SyntaxReference declaration,
        CancellationToken cancellationToken
    )
    {
        var originalNode = declaration.GetSyntax(cancellationToken);
        var identifiers = originalNode.DescendantNodes(ShouldDescendInto(originalNode)).OfType<IdentifierNameSyntax>();

        var semanticModel = compilation.GetSemanticModel(declaration.SyntaxTree);

        foreach (var identifier in identifiers)
        {
            if (!semanticModel.IsReachable(identifier))
            {
                continue;
            }
            if (semanticModel.GetSymbolInfo(identifier, cancellationToken) is not { Symbol: IMethodSymbol member })
            {
                continue;
            }
            Analyze(member, cancellationToken);
        }
    }

    private static Func<SyntaxNode, bool> ShouldDescendInto(SyntaxNode originalNode) =>
        syntaxNode =>
            syntaxNode == originalNode
            || !(
                syntaxNode.IsKind(SyntaxKind.LocalFunctionStatement)
                || syntaxNode.IsKind(SyntaxKind.SimpleLambdaExpression)
                || syntaxNode.IsKind(SyntaxKind.ParenthesizedLambdaExpression)
            );
}