如何用多个语法标记替换一个语法标记?

时间:2017-08-18 14:31:02

标签: roslyn

我编写了一个分析器来强制库使用GetAwaiter()。GetResult()而不是使用.Result / .Wait()来阻塞线程

几个小时后,我试图找到一种方法来生成一个涵盖我所有测试用例的syntaxtree,我试图使用ReplaceToken方法。

SyntaxNodeExtensions.cs

public static TRoot ReplaceToken<TRoot>(this TRoot root, SyntaxToken tokenInList, IEnumerable<SyntaxToken> newTokens) where TRoot : SyntaxNode
{
  return (TRoot) root.ReplaceTokenInListCore(tokenInList, newTokens);
}

似乎是完美的方法 - 但是我从来没有使用这种方法。

我最终这样做了(对我来说,这看起来很懒):

我的工作代码

private async Task<Document> ReplaceWithGetAwaiterGetResultAsync(Document document, IdentifierNameSyntax declaration, CancellationToken cancellationToken)
{
    var source = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
    if(declaration.Identifier.ValueText == "Result")
        return document.WithText(source.Replace(declaration.Span, "GetAwaiter().GetResult()"));
    if(declaration.Identifier.ValueText == "Wait")
        return document.WithText(source.Replace(new TextSpan(declaration.SpanStart, 6), "GetAwaiter().GetResult()"));

    return document;
}

有没有人知道如何改变像

这样的更好方法
Task.Run(() => 42).Result

进入

Task.Run(() => 42).GetAwaiter.GetResult()

E.g。这个版本:

var tokens = SyntaxFactory.ParseTokens("GetAwaiter().GetResult()");
var root = await document.GetSyntaxRootAsync(cancellationToken);
var replaced = root.ReplaceToken(declaration.Identifier, tokens);

return document.WithSyntaxRoot(replaced.WithAdditionalAnnotations());

不会崩溃。

1 个答案:

答案 0 :(得分:2)

实施解决方案时遇到的问题是您尝试更换令牌。但是,结果属性的访问权限不是令牌,而是SyntaxNode

代码的结构由一个包含MethodInvocationExpression(Task.Run())的MemberAccessExpression(Task.Run()。Result)组成。

您要实现的是删除MemberAccessExpression,创建一个新的MethodInvocationExpression(调用GetAwaiter())并将原始InvocationExpression插入到新的Expression中,以便在原始表达式上调用GetAwaiter()。

然后你需要创建另一个 Incovation Expression来调用GetResult()。

为此,您需要创建一个这样的新节点,并使用SyntaxRoot替换旧节点:

private async Task<Document> ReplaceWithAwaiter(Document document, 
        IdentifierNameSyntax nameSyntax, CancellationToken cancellationToken)
{
    var memberAccess = nameSyntax.Ancestors().OfType<MemberAccessExpressionSyntax>()
        .First();
    // Create .GetAwaiter()
    var invocationOfAwaiter = SyntaxFactory.InvocationExpression(
        SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
        memberAccess.Expression, SyntaxFactory.IdentifierName("GetAwaiter")));
    // Create .GetResult()
    var invocationOfGetResult = SyntaxFactory.InvocationExpression(
        SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
        invocationOfAwaiter, SyntaxFactory.IdentifierName("GetResult")));
    // Replace .Result by .GetAwaiter().GetResult()
    var syntaxRoot = await document.GetSyntaxRootAsync();
    syntaxRoot = syntaxRoot.ReplaceNode(memberAccess, invocationOfGetResult);
    return document.WithSyntaxRoot(syntaxRoot);
}

对于.Wait(),代码有点不同,但同样的原则适用。在这种情况下,您希望替换InvocationExpression而不是MemberAccessExpression。

private async Task<Document> ReplaceWithAwaiterForWait(Document document, 
        IdentifierNameSyntax nameSyntax, CancellationToken cancellationToken)
{
    // Get the Invocation Task.Run().Wait()
    var invocationOfWait = nameSyntax.Ancestors().OfType<InvocationExpressionSyntax>()
        .First();
    // Get the Access for Task.Run().Wait
    var memberAccessOfWait = (MemberAccessExpressionSyntax)invocationOfWait.Expression;
    // Get the Invocation Task.Run()
    var invocationOfTaskRun = memberAccessOfWait.Expression;
    // Create new Expressions
    var invocationOfAwaiter = SyntaxFactory.InvocationExpression(
        SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
        invocationOfTaskRun, SyntaxFactory.IdentifierName("GetAwaiter")));
    var invocationOfGetResult = SyntaxFactory.InvocationExpression(
        SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
        invocationOfAwaiter, SyntaxFactory.IdentifierName("GetResult")));
    var syntaxRoot = await document.GetSyntaxRootAsync();
    // Replace the old expression
    syntaxRoot = syntaxRoot.ReplaceNode(invocationOfWait, invocationOfGetResult);
    return document.WithSyntaxRoot(syntaxRoot);
}

如果您不习惯使用单个节点的生成,您也可以使用SyntaxFactory.ParseExpression()生成一个新表达式并替换旧表达式:

private async Task<Document> ReplaceWithAwaiterWithParse(Document document, 
                    IdentifierNameSyntax nameSyntax, CancellationToken cancellationToken)
{
    var invocationOfWait = nameSyntax.Ancestors().OfType<InvocationExpressionSyntax>()
                    .First();
    var memberAccessOfWait = (MemberAccessExpressionSyntax)invocationOfWait.Expression;
    var invocationOfBoth = SyntaxFactory.ParseExpression(
                    memberAccessOfWait.Expression.ToFullString() 
                    + ".GetAwaiter().GetResult()");
    var syntaxRoot = await document.GetSyntaxRootAsync();
    syntaxRoot = syntaxRoot.ReplaceNode(invocationOfWait, invocationOfBoth);
    return document.WithSyntaxRoot(syntaxRoot);
}

虽然这个解决方案更短更简洁,但您不会处理字符串而不是SyntaxTree,这可能会产生各种问题。此外,性能比使用SyntaxFactory创建单个对象要差一些。但是,您不必自己创建调用的每个部分。