Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to collect all MethodDeclarationSyntax transitively with Roslyn?

Tags:

c#

roslyn

Given a list of MethodDeclarationSyntax I would like to collect all the methods in a solution that are calling this method transitively.

I have been using the following code:

        var methods = new Stack<MethodDeclarationSyntax>();
        ... // fill methods with original method to start from
        var visited = new HashSet<MethodDeclarationSyntax>();
        while (methods.Count > 0)
        {
            var method = methods.Pop();
            if (!visited.Add(method))
            {
                continue;
            }

            var methodSymbol = (await solution.GetDocument(method.SyntaxTree).GetSemanticModelAsync()).GetDeclaredSymbol(method);
            foreach (var referencer in await SymbolFinder.FindCallersAsync(methodSymbol, solution))
            {
                var callingMethod = (MethodDeclarationSyntax) referencer.CallingSymbol.DeclaringSyntaxReferences[0].GetSyntax();
                methods.Push(callingMethod);
            }
        }

The problem is that MethodDeclarationSyntax doesn't seem to be a singleton, so this loop is running forever, visiting the same methods again and again.

What is the proper way to uniquely identify a MethodDeclarationSyntax in a Dictionary/Hashset?

Edit 1)

As a workaround, I'm using the following MethodDeclarationSyntaxComparer to initialize my HashSet, but it looks very fragile:

    private class MethodDeclarationSyntaxComparer: IEqualityComparer<MethodDeclarationSyntax>
    {
        public bool Equals(MethodDeclarationSyntax x, MethodDeclarationSyntax y)
        {
            var xloc = x.GetLocation();
            var yloc = y.GetLocation();
            return xloc.SourceTree.FilePath == yloc.SourceTree.FilePath &&
                   xloc.SourceSpan == yloc.SourceSpan;
        }

        public int GetHashCode(MethodDeclarationSyntax obj)
        {
            var loc = obj.GetLocation();
            return (loc.SourceTree.FilePath.GetHashCode() * 307) ^ loc.SourceSpan.GetHashCode();
        }
    }
like image 344
xoofx Avatar asked Dec 20 '18 10:12

xoofx


People also ask

How do I use commonsyntaxreferences?

CommonSyntaxReferences can be used to regain access to a syntax node without keeping the entire tree and source text in memory. Gets the full text of this node as a new SourceText instance. The list of trivia that appears after this node in the source code. Determines whether this node has the specific annotation.

How many methods does a class declaration contain?

The class declaration contains one method declaration. The Syntax API creates a tree structure with the root representing the compilation unit. Nodes in the tree represent the using directives, namespace declaration and all the other elements of the program. The tree structure continues down to the lowest levels: the string "Hello World!"

How can I learn more about a syntax tree?

The sample uses WriteLinestatements to display information about the syntax trees as they are traversed. You can also learn much more by running the finished program under the debugger. You can examine more of the properties and methods that are part of the syntax tree created for the hello world program. Syntax walkers


1 Answers

I'm wondering whether using SyntaxNode here is the right way to go.

Since you're already using SymbolFinder and you're using the semantic model, maybe the right way to go is to actually use ISymbols, rather than SyntaxNodes.

ISymbol already contains the SyntaxReferences you are using, so:

   var methods = new Stack<IMethodSymbol>();
    ... // fill methods with original method to start from
    ... // convert methods to symbols via semanticModel.GetDeclaredSymbol (node);
    var visited = new HashSet<IMethodSymbol>();
    while (methods.Count > 0)
    {
        var method = methods.Pop();
        if (!visited.Add(method))
        {
            continue;
        }

        foreach (var referencer in await SymbolFinder.FindCallersAsync(method, solution))
        {
            var callingMethod = (MethodDeclarationSyntax) referencer.CallingSymbol.DeclaringSyntaxReferences[0].GetSyntax();
            methods.Push(callingMethod);
        }
    }

You could possibly make the visited hashset into a Dictionary<IMethodSymbol, IEnumerable<Location>>, and concat all the locations, and thus reconstruct the syntaxes from that result.

like image 120
Marius Ungureanu Avatar answered Nov 15 '22 00:11

Marius Ungureanu