С Roslyn найдите вызывающий метод из строкового литерала

2

Я пытаюсь использовать Roslyn для замены старого, медленного кода в утилите, которая ищет исходный код для строковых литералов, которые не завернуты в вызов функции X() (вещи, перенесенные в X() будут переведены).

Я смог использовать синтаксическое дерево, чтобы получить строковые литералы довольно легко и определил большинство мест, где они были обернуты в X(). Что я делал: с LiteralExpressionSyntax объекта LiteralExpressionSyntax, я обнаружил, что это дало мне вызов функции, и я мог бы сопоставить его с регулярным выражением.

s.Parent.Parent.Parent.ToFullString()

Я быстро столкнулся с проблемой, когда строковый литерал был разделен между двумя строками. В этот момент я понял, что мои средства проверки, было ли это в вызове X() были плохими, потому что я должен был бы продолжать добавлять .Parent в цепочку. Хотя я мог написать что-то, чтобы проползти назад по дереву, похоже, что это был правильный путь (и, вероятно, не очень хорошо работал).

Я пытался найти способ, заданный узлом синтаксиса строкового литерала, чтобы определить, является ли это аргументом в вызове метода. Мне не удалось найти достойный способ перехода от синтаксического дерева к семантической модели, чтобы найти то, что я ищу. Я даже не уверен, что это правильный подход, или если мне не хватает чего-то очевидного.

Я смог использовать SymbolFinder.FindSymbolAtPositionAsync чтобы перейти к символу, но это страдает от одной и той же проблемы - я не могу просто передать позицию аргумента строкового литерала, мне нужно передать позицию (правильного) родителя, а я Я вернулся, когда начал.

Я надеюсь, что вам не придется циклически перебирать дерево синтаксиса несколько раз, потому что это замедляет работу. Я могу разобрать 553 файла примерно за 1 секунду, но как только я попытаюсь выполнить цикл для учета этих многострочных ситуаций, я до 12-13 секунд.

На всякий случай, когда я потерял вас в этой новелле (извините), вот что я надеюсь выяснить: для строкового литерала, переданного в качестве аргумента методу, есть ли простой способ определить, что это за метод?

Вот пример кода - я заменил вызовы на мою функцию X() с помощью Convert.ToString только для имитации кода, который я ищу (мне пришлось добавить ссылки на одну из наших 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}");
}
  • 0
    Что вы хотите сделать с Dim str = "Ignore me?"\n Convert.ToString(str) ?
  • 0
    Я пытаюсь определить любой строковый литерал, который еще не обернут в функцию X() (которой в этом примере выступает Convert.ToString() ). Любую строку, которая уже завернута, я просто проигнорирую.
Показать ещё 2 комментария
Теги:
roslyn

1 ответ

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

Это приводит к:

  Reported: "Report me"

Total: 5, Not Wrapped In X: 1

Функция IsWrappedInCallToX просто возвращает дерево, ищущее InvocationExpressionSyntax для функции X Я знаю, что ты сказал: "Пока я мог писать что-то, чтобы проползти назад по дереву, похоже, что это был правильный путь (и, вероятно, он не очень хорошо работал)", но для меня кажется, что это правильно путь - если производительность ужасно на вашей базе кода, возможно, нет!

Опять же, я ничего не знаю о Roslyn (это просто звучало интересно), так что это, скорее всего, ужасное решение! :-)

  • 0
    Ну, я не знаю, ужасное решение или нет, но оно определенно помогло мне! Логика, которую я использовал, чтобы ползти по дереву, была просто плохой, и я думаю, что в конечном итоге я ползал весь путь вверх по дереву каждый раз, потому что я использовал Родителя. Мне пришлось немного изменить ваше решение, так как я обнаружил другие крайние случаи, которые мне нужно было игнорировать (атрибуты, определенные конструкторы, которые в конечном итоге переводят строку и т. Д.), Но это была отличная основа. О, и это также все еще очень быстро. Старая утилита занимает более 30 секунд, чтобы разобрать этот проект - мой прототип запустился всего за 0,44 секунды.
  • 1
    Еще один совет для новичков в Roslyn - ознакомьтесь с окном синтаксического визуализатора (доступно, если у вас установлено расширение .NET Compiler Platform SDK в VS2015). Я действительно хотел бы знать об этом ранее на этой неделе, сэкономил бы мне массу времени и помог бы мне понять синтаксическое дерево, не проходя через код и не глядя в окно наблюдения.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню