首页 > 解决方案 > DocumentEditor InsertBefore 似乎无法在文档中找到节点

问题描述

我正在使用 roslyn 为 Visual Studio 开发代码重构。

我正在使用 DocumentEditor 类来删除一些 SyntaxNode,在删除它们之后,我正在插入一些新的。我正在使用我知道我没有删除的语法节点作为我插入新语法节点的位置的参考。但是我用的时候编辑器好像找不到

foreach (var nodeToRemove in nodesToRemove)
{
    editor.RemoveNode(directive);
}

editor.InsertBefore(untouchedNode, newNodesToAdd); // No exception here, only when getting the new document

为了调试它,我试图找到参考节点,然后像编辑器那样继续

var ns = editor.OriginalRoot.DescendantNodesAndSelf().OfType<NamespaceDeclarationSyntax>().FirstOrDefault(); // ns is not null
var IshouldExist = editor.OriginalRoot.GetCurrentNode(ns); //it is null, weird

我不知道为什么 GetCurrentNode() 在这里返回 null。

在堆栈跟踪中,我注意到插入是在更改的文档中完成的,我认为这应该不是问题,因为如果我不尝试插入新节点,我用作参考的 SytaxNode 是仍在更改的文档中。

And throws an exception with the following stacktrace:
System.InvalidCastException : Unable to cast object of type 'Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax' to type 'Microsoft.CodeAnalysis.CSharp.Syntax.MemberDeclarationSyntax'.
   at System.Linq.Enumerable.<CastIterator>d__97`1.MoveNext()
   at System.Collections.Generic.List`1.InsertRange(Int32 index,IEnumerable`1 collection)
   at Microsoft.CodeAnalysis.SyntaxList`1.InsertRange(Int32 index,IEnumerable`1 nodes)
   at Microsoft.CodeAnalysis.CSharp.Syntax.SyntaxReplacer.NodeListEditor.VisitList[TNode](SyntaxList`1 list)
   at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitCompilationUnit(CompilationUnitSyntax node)
   at Microsoft.CodeAnalysis.CSharp.Syntax.CompilationUnitSyntax.Accept[TResult](CSharpSyntaxVisitor`1 visitor)
   at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.Visit(SyntaxNode node)
   at Microsoft.CodeAnalysis.CSharp.Syntax.SyntaxReplacer.BaseListEditor.Visit(SyntaxNode node)
   at Microsoft.CodeAnalysis.CSharp.Syntax.SyntaxReplacer.NodeListEditor.Visit(SyntaxNode node)
   at Microsoft.CodeAnalysis.CSharp.Syntax.SyntaxReplacer.InsertNodeInList(SyntaxNode root,SyntaxNode nodeInList,IEnumerable`1 nodesToInsert,Boolean insertBefore)
   at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxNode.InsertNodesInListCore(SyntaxNode nodeInList,IEnumerable`1 nodesToInsert,Boolean insertBefore)
   at Microsoft.CodeAnalysis.SyntaxNodeExtensions.InsertNodesBefore[TRoot](TRoot root,SyntaxNode nodeInList,IEnumerable`1 newNodes)
   at Microsoft.CodeAnalysis.CSharp.CodeGeneration.CSharpSyntaxGenerator.InsertNodesBeforeInternal(SyntaxNode root,SyntaxNode declaration,IEnumerable`1 newDeclarations)
   at Microsoft.CodeAnalysis.CSharp.CodeGeneration.CSharpSyntaxGenerator.<>c__DisplayClass183_0.<InsertNodesBefore>b__0(SyntaxNode r)
   at Microsoft.CodeAnalysis.Editing.SyntaxGenerator.PreserveTrivia[TNode](TNode node,Func`2 nodeChanger)
   at Microsoft.CodeAnalysis.CSharp.CodeGeneration.CSharpSyntaxGenerator.InsertNodesBefore(SyntaxNode root,SyntaxNode declaration,IEnumerable`1 newDeclarations)
   at Microsoft.CodeAnalysis.Editing.SyntaxEditor.InsertChange.Apply(SyntaxNode root,SyntaxGenerator generator)
   at Microsoft.CodeAnalysis.Editing.SyntaxEditor.GetChangedRoot()
   at Microsoft.CodeAnalysis.Editing.DocumentEditor.GetChangedDocument()

我究竟做错了什么?

标签: c#visual-studioroslyn

解决方案


今天早些时候,我自己也遇到了类似的问题。我没有使用 DocumentEditor,而是在 Document 对象本身上以更“原始”的性质执行操作,但是,当我尝试通过自定义谓词对 Using Directives 进行排序时,我注意到如果我清除了所有旧的在插入新的 using 之前使用指令,使用 *.InsertBefore 或 *.InsertAfter,当引用节点不是Using 指令时,整个系统就会爆炸。(似乎在内部它可能试图将引用节点视为 UsingDirectiveSyntax 对象,然后无法对其进行比较,但这是我的纯粹猜想,基于异常详细信息......)

如果您正在使用不再具有 Using 语句的 Document 对象,您将需要考虑使用不同的方法来插入 using 语句:

List<UsingDirectiveSyntax> myNewUsingList = /* Construct Using List somehow */
NameSpaceDeclarationSyntax newNameSpace = myOldNameSpace.AddUsings(myUsingList);
CompilationUnitSyntax newCompileRootSyntax = (documentRoot as CompilationUnitSyntax).ReplaceNode(myOldNameSpace, newNameSpace);
return document.WithSyntaxRoot(newCompileRootSyntax);

这将与使用 DocumentEditor 实例时使用的语义不同,但由于我对正在修改的当前文档上的节点遍历非常不熟悉,因此我不知道在将这种单独的方法转换为您的需求时我能提供多少帮助。

这对我来说有点烦人的代码,因为这种方法适用于不可变对象,所以上面调用的所有方法都返回一个被操作对象的新实例,并通过实现方法参数的定向修改。上面的代码部分,

document.WithSyntaxRoot(/*variable*/);

创建一个全新的 Document() 对象,其内部数据表示从旧文档到新文档的更改,以及您使用先前代码发起的所有替换、插入、删除等。

基于对基于 DocumentEditor 的方法的一些初步讨论,使用 VS 2019 的 SDK 工具创建 CodeRefactoring 项目,DocumentEditor 方法可能看起来更像这样:

DocumentEditor ed = await DocumentEditor.CreateAsync(document, cancellationToken);
NamespaceDeclarationSyntax nameSpace = ed.GetChangedRoot()
  .DescendantNodes()
  .FirstOrDefault(t => t.GetType() == typeof(NamespaceDeclarationSyntax)) as NamespaceDeclarationSyntax;

if (nameSpace != null)
{
  NamespaceDeclarationSyntax modifiedWithMyUsings = nameSpace.AddUsings(myUsingLists);
  ed.ReplaceNode(nameSpace, modifiedWithMyUsings);
}

推荐阅读