Roslyn 分析器 + 代码修复 - 用后代节点替换节点

问题描述 投票:0回答:1

我编写了一个分析器,如果某些方法没有标记为

async
,它将对其进行标记。

我现在正在实现一个代码修复程序,使它们异步。

它还会添加一个await表达式,或者删除一个

Task.FromResult
调用并直接返回对象。

为了实现后者,我正在这样做:

var expressionSyntax = returnStatement.ChildNodes().OfType<ExpressionSyntax>().First()!;

var firstInnerExpression = expressionSyntax.ChildNodes().OfType<ArgumentListSyntax>().First().Arguments.First().Expression;

var newReturnStatement = returnStatement.ReplaceNode(expressionSyntax, firstInnerExpression);
                
newMethodDeclarationSyntax = newMethodDeclarationSyntax.ReplaceNode(returnStatement, newReturnStatement);

在调试器中,我可以看到我的旧返回语句是:

ReturnStatementSyntax ReturnStatement return Task.FromResult<string?>("Foo!");

我的新退货声明是:

ReturnStatementSyntax ReturnStatement return "Foo!";

但是,当我调用

method.ReplaceNode
时,它不会出错,但它也不会更新到新的 return 语句。

有谁知道为什么吗?

谢谢

c# .net roslyn
1个回答
0
投票

听起来您在代码中进行了多个节点替换。就在代码片段中,已经有两次对

SyntaxNode.ReplaceNode
方法的调用。

Roslyn 中的公共数据结构是不可变的。与字符串类似,当您通过节点替换修改语法树时,您将收到一个具有新的不同

SyntaxTree
的新节点。如果在第一次替换之前找到了一些语法节点,那么这些节点是旧语法树的一部分。

为了使这一点更加明显,我通常在根节点上调用替换并将结果存储在新变量中,如下所示:

    var newRoot = oldRoot.ReplaceNode(oldNode, newGeneratedNode);

尝试将具有不同语法树的节点组合起来不会成功。第二次调用

ReplaceNode
时,将无法在另一个节点的语法树中找到其中一个节点。没有找到要替换的节点,没有错误,从 API 的角度来看一切正常。

解决这个问题有几种方法。我通常使用以下两个:

  1. 最简单的方法是尝试将拨打
    ReplaceNode
    的次数减少到一次。尽可能多地准备新节点,添加所需的所有内容。也许替换的不是您最初要替换的节点,而是它的祖先。
  2. 以前的方法经常就足够了,但有时你必须多次调用
    ReplaceNode
    。在这种情况下,我通常使用
    TrackNodes
    功能。这个想法是,您可以在任何修改之前找到一些节点,明确告诉 Roslyn 标记它们,以便可以在新修改的语法树中找到它们。这是一个简单的例子:
SyntaxNode someNode, anotherNode; //assume that you have two nodes to modify

//This is also a modification, the trackingRoot has different syntax tree
var trackingRoot = root.TrackNodes(someNode, anotherNode); 
var someNode = trackingRoot.GetCurrentNode(someNode); // node has to be found in the new syntax tree 

if (someNode != null)
{
   var newSomeNode = Modify(someNode);
   trackingRoot = trackingRoot.ReplaceNode(someNode, newSomeNode);
}

var anotherNode= trackingRoot.GetCurrentNode(anotherNode); // node has to be found in the new syntax tree 

if (anotherNode!= null)
{
   var newAnotherNode = Modify(anotherNode);
   trackingRoot = trackingRoot.ReplaceNode(anotherNode, newAnotherNode);
}

您可以在我维护的开源项目中找到此类代码修复的示例: https://github.com/Acumatica/Acuminator/blob/dev/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/NonPublicExtensions/NonPublicExtensionFix.cs#L66-L74

对于许多编辑的情况,有更复杂的 API,如

CSharpSyntaxRewriter
DocumentEditor
。将它们全部描述出来将使这个答案变得巨大。如果您有兴趣,这里有一个描述它们的优秀博客文章系列的链接: https://joshvarty.com/2015/08/18/learn-roslyn-now-part-12-the-documenteditor/

© www.soinside.com 2019 - 2024. All rights reserved.