我编写了一个分析器,如果某些方法没有标记为
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 语句。
有谁知道为什么吗?
谢谢
听起来您在代码中进行了多个节点替换。就在代码片段中,已经有两次对
SyntaxNode.ReplaceNode
方法的调用。
Roslyn 中的公共数据结构是不可变的。与字符串类似,当您通过节点替换修改语法树时,您将收到一个具有新的不同
SyntaxTree
的新节点。如果在第一次替换之前找到了一些语法节点,那么这些节点是旧语法树的一部分。
为了使这一点更加明显,我通常在根节点上调用替换并将结果存储在新变量中,如下所示:
var newRoot = oldRoot.ReplaceNode(oldNode, newGeneratedNode);
尝试将具有不同语法树的节点组合起来不会成功。第二次调用
ReplaceNode
时,将无法在另一个节点的语法树中找到其中一个节点。没有找到要替换的节点,没有错误,从 API 的角度来看一切正常。
解决这个问题有几种方法。我通常使用以下两个:
ReplaceNode
的次数减少到一次。尽可能多地准备新节点,添加所需的所有内容。也许替换的不是您最初要替换的节点,而是它的祖先。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/