Я пытаюсь использовать 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}");
}
С предупреждением, что я никогда раньше не использовал 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 (это просто звучало интересно), так что это, скорее всего, ужасное решение! :-)
Dim str = "Ignore me?"\n Convert.ToString(str)
?X()
(которой в этом примере выступаетConvert.ToString()
). Любую строку, которая уже завернута, я просто проигнорирую.