With Roslyn, Find Calling Method from String Literal Parameter

I'm trying to use Roslyn to replace some old, slow code in a utility that searches our source code for string literals which are not wrapped in an X() function call (things wrapped in X() will be be translated).

I was able to use the Syntax Tree to get the string literals quite easily, and identified most of the places where they were wrapped in X(). What I was doing: given an LiteralExpressionSyntax object, I found that this gave me the function call and I could match it with a regular expression.

s.Parent.Parent.Parent.ToFullString()

I quickly ran into a problem when the string literal was split between two lines. At that point I realized my means of checking if it was in an X() call was poor, because I'd have to keep adding .Parent to the chain. While I could write something to crawl backwards up the tree, that didn't seem like it was the right way (and probably wouldn't perform very well).

I've been trying to find a way, given a string literal syntax node, to determine if that is an argument in a method call. I haven't been able to find a decent way of going from the Syntax Tree to the Semantic Model to find what I'm looking for. I'm not even sure if that's the right approach, or if I'm missing something obvious.

I was able to use SymbolFinder.FindSymbolAtPositionAsync to get to the symbol, but that suffers from the same issue - I can't just pass the position of the string literal argument, I have to pass the position of the (correct) parent and I'm back where I started.

I'm hoping to avoid having to loop through the syntax tree multiple times, because that slows things way down. I can parse 553 files in about 1 second, but as soon as I try to loop to account for these multi-line situations, I'm up to about 12-13 seconds.

Just in case I've lost you in this novella (sorry), here's what I'm hoping to figure out: for a string literal being passed as an argument to a method, is there an easy way to determine what that method is?

Here is some example code - I've replaced calls to my X() function with Convert.ToString just to simulate the code I'm searching (I had to add references to one of our DLLs, so I switched calls to Convert.ToString() so I could just reference mscorlib for this example.

static void TestAttempt()
{

  string source = @"
  Imports System
  Namespace Exceptions
    Public NotInheritable Class ExampleException
      Inherits Validation

      Public Sub New()
        Convert.ToString(""Ignore me 1"")
        Console.WriteLine(""Report me"")
        Console.WriteLine(Convert.ToString(""Ignore me 2""))

        MyBase.New(Convert.ToString(""Ignore me 3, "" & _
                                    ""Because I'm already translated.""))
      End Sub
    End Class
  End Namespace";

  var tree = VisualBasicSyntaxTree.ParseText(source);

  var syntaxRoot = tree.GetRoot();
  int i = 0;

  foreach (var s in syntaxRoot.DescendantNodes().OfType<LiteralExpressionSyntax>())
  {
    // things to skip:
    if (s == null) { continue; }
    if (s.Kind() != SyntaxKind.StringLiteralExpression) { continue; }

    var Mscorlib = PortableExecutableReference.CreateFromFile(@"C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscorlib.dll");
    var compilation = VisualBasicCompilation.Create("MyCompilation", syntaxTrees: new[] { tree }, references: new[] { Mscorlib });
    var model = compilation.GetSemanticModel(tree);

    var symbol = SymbolFinder.FindSymbolAtPositionAsync(model, s.Parent.Parent.Parent.Span.Start, new AdhocWorkspace()).Result;

    if (symbol.ToDisplayString().EndsWith("Convert")) { continue; }

    Console.WriteLine(symbol);
    Console.WriteLine($"  Reported: {s.ToString()}");
    i++;
  }

  Console.WriteLine();
  Console.WriteLine($"Total: {i}");
}
4

1 Answer

With a warning that I have never used Roslyn before, and I normally code in VB.NET, I think I hacked together something that seems to do what you want (using LINQPad and lots of Dump() calls helped to find out what was going on).

void TestAttempt()
{

    string source = @"
  Imports System
  Namespace Exceptions
    Public NotInheritable Class ExampleException
      Inherits Validation

      Public Sub New()
        X(""Ignore me 1"")
        Console.WriteLine(""Report me"")
        Console.WriteLine(X(""Ignore me 2""))

        MyBase.New(X(""Ignore me 3, "" & _
                   ""Because I'm already translated.""))
      End Sub
    End Class
  End Namespace";

    var tree = VisualBasicSyntaxTree.ParseText(source);

    var syntaxRoot = tree.GetRoot();
    int i = 0, notWrapped = 0;

    foreach (var s in syntaxRoot.DescendantNodes().OfType<LiteralExpressionSyntax>())
    {
        // things to skip:
        if (s == null) { continue; }
        if (s.Kind() != SyntaxKind.StringLiteralExpression) { continue; }

        if (!IsWrappedInCallToX(s))
        {
            Console.WriteLine($"  Reported: {s.ToString()}");
            notWrapped++;
        }
        i++;
    }

    Console.WriteLine();
    Console.WriteLine($"Total: {i}, Not Wrapped In X: {notWrapped}");
}

bool IsWrappedInCallToX(SyntaxNode node)
{
    var invocation = node as InvocationExpressionSyntax;
    if (invocation != null)
    {
        var exp = invocation.Expression as IdentifierNameSyntax;
        if (exp != null && exp.ToString() == "X")
        {
            return true;
        }
    }
    if (node.Parent != null)
    {
        return IsWrappedInCallToX(node.Parent);
    }
    return false;
}

This results in:

  Reported: "Report me"

Total: 5, Not Wrapped In X: 1

The IsWrappedInCallToX function just recurses up the tree looking for an InvocationExpressionSyntax for the X function. I know you said "While I could write something to crawl backwards up the tree, that didn't seem like it was the right way (and probably wouldn't perform very well)", but to me it seems like this is the right way - if the performance is horrible on your code base, maybe not!

Again, I know nothing about Roslyn (this just sounded interesting), so this is very likely a terrible solution! :-)

2

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Robert Thorne

Robert Thorne

Automotive & Future Transportation Editor

Robert Thorne covers electric vehicle innovations, autonomous driving systems, global mobility trends, and automotive engineering developments.

Share this article
Twitter Facebook Pinterest