【问题标题】:With Roslyn, find calling method from string literal parameter使用 Roslyn,从字符串字面量参数中查找调用方法
【发布时间】:2017-04-28 11:58:17
【问题描述】:

我正在尝试使用 Roslyn 替换实用程序中的一些旧的、缓慢的代码,该实用程序在源代码中搜索未包含在 X() 函数调用中的字符串文字(包含在 X() 中的内容将被翻译)。

我能够使用语法树很容易地获取字符串文字,并确定了 大多数它们被包裹在 X() 中的位置。我在做什么:给定一个LiteralExpressionSyntax 对象,我发现这给了我函数调用,我可以用正则表达式匹配它。

s.Parent.Parent.Parent.ToFullString()

当字符串文字分成两行时,我很快遇到了问题。那时我意识到我检查它是否在X() 调用中的方法很差,因为我必须继续将.Parent 添加到链中。虽然我可以写一些东西在树上向后爬,但这似乎不是正确的方法(而且可能不会表现得很好)。

我一直在尝试寻找一种方法,给定一个字符串文字语法节点,以确定它是否是方法调用中的参数。我还没有找到一种从语法树到语义模型的好方法来找到我正在寻找的东西。我什至不确定这是否是正确的方法,或者我是否遗漏了一些明显的东西。

我能够使用SymbolFinder.FindSymbolAtPositionAsync 来获取符号,但这也存在同样的问题 - 我不能只传递字符串文字参数的位置,我必须传递(正确)的位置父母,我又回到了我开始的地方。

我希望避免多次循环遍历语法树,因为这会减慢速度。我可以在大约 1 秒内解析 553 个文件,但一旦我尝试循环处理这些多行情况,我最多需要 12-13 秒。

以防万一我在这部中篇小说中失去了你(对不起),这是我希望弄清楚的:对于作为参数传递给方法的字符串文字,是否存在确定该方法是什么的简单方法?

这里是一些示例代码 - 我已经用 Convert.ToString 替换了对我的 X() 函数的调用,只是为了模拟我正在搜索的代码(我必须添加对我们的一个 DLL 的引用,所以我将调用切换到Convert.ToString() 所以我可以在这个例子中引用 mscorlib。

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}");
}

【问题讨论】:

  • 你想用Dim str = "Ignore me?"\n Convert.ToString(str)做什么?
  • 我正在尝试识别尚未包含在 X() 函数中的任何字符串文字(在此示例中 Convert.ToString() 代表它)。任何已经被包装的字符串我都会忽略。
  • 所以在我上面的例子中,这意味着你确实想要报告字符串文字。你想报告X(("str"))吗?注意额外的括号。 "str" 没有直接用在X 中,你会举报吗?我不这么认为。极限在哪里?
  • 啊,抱歉 - 我误解了您在评论中提出的问题!根据系统代码的工作方式,我想要捕获字符串Dim str = "Ignore me?",因为根据标准,文字应该包装在X() 中,而不是将变量传递给该函数。并且X(("str")) 也可以被举报。

标签: c# vb.net roslyn


【解决方案1】:

警告说我以前从未使用过 Roslyn,而且我通常在 VB.NET 中编写代码,我想我拼凑了一些似乎可以做你想做的事情(使用 LINQPad 和大量 Dump() 调用有助于找出发生了什么)。

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;
}

这会导致:

举报:“举报我” 总计:5,未包含在 X 中:1

IsWrappedInCallToX 函数只是在树上递归,为X 函数寻找InvocationExpressionSyntax。我知道你说过“虽然我可以写一些东西在树上向后爬,但这似乎不是正确的方式(并且可能表现不佳)”,但对我来说,这似乎是正确的方式 - 如果您的代码库的性能很糟糕,也许不会!

再说一次,我对 Roslyn 一无所知(这听起来很有趣),所以这很可能是一个糟糕的解决方案! :-)

【讨论】:

  • 好吧,我不知道这是否是一个糟糕的解决方案,但它绝对帮助了我!我用来爬上树的逻辑很糟糕,我想我每次都爬到树上,因为我使用的是 Parent。我不得不稍微修改您的解决方案,因为我发现了我需要忽略的其他边缘情况(属性、最终转换字符串的某些构造函数等),但这是一个很好的基础。哦,它也仍然非常快。旧的实用程序需要 30 多秒来解析这个项目——我的原型只用了 0.44 秒。
  • 对 Roslyn 新手的另一个提示 - 查看 Syntax Visualizer 窗口(如果您在 VS2015 中安装了 .NET Compiler Platform SDK 扩展,则可用)。我真希望我在本周早些时候知道这一点,这样可以节省大量时间并帮助我理解语法树,而无需单步执行代码并查看监视窗口。
猜你喜欢
  • 1970-01-01
  • 2012-05-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-24
  • 2016-06-07
相关资源
最近更新 更多