Как получить тело метода из MemberAccessExpressionSyntax? - PullRequest
3 голосов
/ 02 апреля 2019

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

Мне неизвестно, как войти в MemberAccessExpression (который указывает на метод), чтобы я мог начать просмотр файла, содержащего метод.

Я пробовал перебирать ChildNodes и ChildTokens, я также просматривал объект в отладчике и не вижу свойства, которое можно было бы использовать для загрузки класса / метода, на который ссылается MemberAccessExpression.

Пройдя по дереву, чтобы найти интересующий меня метод, я делаю следующее:

private void AnalyzeSyntaxNode(SyntaxNode syntaxNode)
{
    if (this.foundMethod)
    {
        return;
    }

    if (syntaxNode is InvocationExpressionSyntax invocationExpressionSyntax)
    {
        this.foundMethod = true;
        PrintNodeAndChildren(invocationExpressionSyntax);
    }

    var children = syntaxNode.ChildNodes();

    foreach (var child in children)
    {
        AnalyzeSyntaxNode(child);
    }
}

private void PrintNodeAndChildren(SyntaxNode node)
{
    Console.WriteLine($"Child: {node}");

    var children = node.ChildNodes();

    if (children.Any())
    {
        foreach (var child in children)
        {
            PrintNodeAndChildren(child);
        }
    }
}

Я хочу получить доступ к классу, который содержит метод, а также к телу самого метода.

В следующем примере я начинаю обход синтаксического дерева Caller и хочу получить доступ к телу Callee.DoSomethingElse. В действительности, моя цель - заменить _visited = true на _visited = 99 в Callee.DoSomethingElse, однако, если я могу просто выяснить, как получить доступ к этой части дерева, я чувствую, что могу сам заменить узел (ы).

Caller.cs

public class Caller
{
    private Callee _callee = new Callee();
    public void DoSomething()
    {
        _callee.DoSomethingElse();
    }
}

Callee.cs (отличается от Caller.cs)

public class Callee
{
    private int _visited = 0;
    public void DoSomethingElse()
    {
        _visited = 1;
    }
}

Это произвольный, бессмысленный пример, но я чувствую, что он все понял.

1 Ответ

0 голосов
/ 03 апреля 2019

Оказывается, что правильный класс для этого - CSharpSyntaxRewriter, подкласс CSharpSyntaxVisitor, который может значительно облегчить навигацию по деревьям синтаксиса.

Он функционально работает так же, как CSharpSyntaxVisitor, но позволяет изменять синтаксические узлы.

Вот моя реализация класса:

public class AssignmentReplacer : CSharpSyntaxRewriter
{
    private string inMethod;

    public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node)
    {
        inMethod = node.Identifier.ValueText;

        return base.VisitMethodDeclaration(node);
    }

    public override SyntaxNode VisitAssignmentExpression(AssignmentExpressionSyntax node)
    {
        if (inMethod == "DoSomethingElse")
        {
            if (node.Left is IdentifierNameSyntax name &&
               name.Identifier.Text == "_visited")
            {

                return node.Update(node.Left,
                                   node.OperatorToken,
                                   SyntaxFactory.LiteralExpression(SyntaxKind.TrueLiteralExpression));
            }
        }

        return base.VisitAssignmentExpression(node);
    }
}

По мере того, как он обходит дерево в глубинуон отслеживает свое текущее местоположение метода из VisitMethodDeclaration в приватном поле.Он использует это в VisitAssignmentExpression, чтобы убедиться, что он используется в правильном методе при изменении узла.

Как только он определил узел AssignmentExpressionSyntax внутри правильного метода, он создает измененную копию узлас правой стороной, измененной на новую LiteralExpression.

Возможно, вам придется добавить дополнительные проверки, чтобы убедиться, что вы модифицируете правильный узел, но это работает для ваших простых примеров данных.

Вы можете использовать класс следующим образом:

var programText =
@"public class Callee
{
    private bool _visited = false;
    public void DoSomethingElse()
    {
        _visited = false;
    }
}";

var tree = CSharpSyntaxTree.ParseText(programText);
var root = tree.GetRoot();

var visitor = new AssignmentReplacer();
var updated = visitor.Visit(root);

После запуска посетителя updated будет содержать измененный код:

public class Callee
{
    private bool _visited = true;
    public void DoSomethingElse()
    {
        _visited = true;
    }
}

Простов качестве предостережения, я впервые делаю что-либо из этого, так что это может быть не самый эффективный или лучший способ сделать это.

Спасибо за интересный вызов!

Полностью работающий пример LINQPad можно найти здесь .

...