📄 core/src/Scanner.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.MSBuild;

namespace Cfa;

public sealed class Scanner : IDisposable
{
    public static async Task<Scanner> Create(FileInfo solutionFile, CancellationToken cancellationToken = default)
    {
        var workspace = MSBuildWorkspace.Create();
        var solution = await workspace.OpenSolutionAsync(solutionFile.FullName, cancellationToken: cancellationToken);
        return new Scanner(workspace, solution);
    }

    private readonly Workspace workspace;
    private readonly Solution solution;

    private Scanner(Workspace workspace, Solution solution)
    {
        this.workspace = workspace;
        this.solution = solution;
    }

    public async IAsyncEnumerable<IMethodSymbol> FindMethods(
        [EnumeratorCancellation] CancellationToken cancellationToken = default
    )
    {
        foreach (var project in solution.Projects)
        {
            var compilation = await project.GetCompilationAsync(cancellationToken);
            if (compilation == null)
            {
                continue;
            }

            foreach (var syntaxTree in compilation.SyntaxTrees)
            {
                var semanticModel = compilation.GetSemanticModel(syntaxTree);
                var methodDeclarations = syntaxTree
                    .GetRoot(cancellationToken)
                    .DescendantNodes()
                    .OfType<MethodDeclarationSyntax>();
                foreach (var methodDeclaration in methodDeclarations)
                {
                    if (
                        semanticModel.GetDeclaredSymbol(methodDeclaration, cancellationToken: cancellationToken)
                        is IMethodSymbol methodSymbol
                    )
                    {
                        yield return methodSymbol;
                    }
                }
            }
        }
    }

    public void Dispose()
    {
        workspace.Dispose();
    }
}