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();
}
}
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.
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!"
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
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 ISymbol
s, rather than SyntaxNode
s.
ISymbol
already contains the SyntaxReference
s 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With