使用Roslyn CodeAnalyzer为类添加命名空间

3

借鉴Add a parameter to a method with a Roslyn CodeFixProvider的想法,我正在开发一个CodeFixProvider,以确保所有异步方法都使用CancellationToken

//Before Code Fix:
public async Task Example(){}

//After Code Fix
public async Task Example(CancellationToken token){}

我能够在方法中添加参数,但我必须使用Type.FullName来完成。相反,我想在类文件的顶部添加一个System.Threading的using语句,这样该方法就不需要使用完整的命名空间。换句话说:

// What I have thus far:
public class AClass{
  public async Task Example(System.Threading.CancellationToken token){}
}

// What I want:
using System.Threading;

public class AClass{
  public async Task Example(CancellationToken token){}
}

我该如何向一个Document添加using语句?
我尝试了几种方法,但似乎在替换SyntaxTree中的多个节点时会丢失引用(因为树是不可变的,并且在每次更改时都会重建)。
我使用下面的代码部分地解决了这个问题,但这仅适用于CompilationUnitSyntax.Using属性已经被填充的情况,而当使用语句在命名空间之后时,这并不是情况。这还依赖于文件中至少有一个using语句。
有更好的方法吗?
private async Task<Document> HaveMethodTakeACancellationTokenParameter(
        Document document, SyntaxNode syntaxNode, CancellationToken cancellationToken)
    {
        var syntaxTree = 
            (await document.GetSyntaxTreeAsync(cancellationToken))
                .GetRoot(cancellationToken);

        var method = syntaxNode as MethodDeclarationSyntax;

        #region Add Parameter
        var newParameter =
            SyntaxFactory.Parameter(
                SyntaxFactory.Identifier("cancellationToken")
            )
            .WithType(
                SyntaxFactory.ParseTypeName(
                    typeof(CancellationToken).FullName));

        var updatedMethod = method.AddParameterListParameters(newParameter);

        syntaxTree = syntaxTree.ReplaceNode(method, updatedMethod);

        #endregion

        #region Add Using Statements

        var compilation =
            syntaxTree as CompilationUnitSyntax;                    

        var systemThreadingUsingName =
            SyntaxFactory.QualifiedName(
                SyntaxFactory.IdentifierName("System"),
                SyntaxFactory.IdentifierName("Threading"));

        if (compilation.Usings.All(u => u.Name.GetText().ToString() != typeof(CancellationToken).Namespace))
        {
            syntaxTree = syntaxTree.InsertNodesAfter(compilation.Usings.Last(), new[]
            {
                SyntaxFactory.UsingDirective(
                        systemThreadingUsingName)
            });
        }

        #endregion

        return document.WithSyntaxRoot(syntaxTree);
    }

2
使用 DocumentEditor 对单个文档进行多次更改。 - Jeroen Vannevel
@JeroenVannevel - 有没有关于如何使用DocumentEditor的教程? - Philip Pittle
https://joshvarty.wordpress.com/2015/08/18/learn-roslyn-now-part-12-the-documenteditor/ - Jeroen Vannevel
1个回答

1

一种选择是使用注释标记所有方法,添加使用语句,查找带有注释的方法,更改所有方法,并删除注释。

正如您所说,树是不可变的,但在修改过程中注释不会丢失。因此,您需要类似以下的东西:

var annotation = new SyntaxAnnotation();
var newRoot = root.ReplaceNode(
     method,
     method.WithAdditionalAnnotations(annotation));
newRoot = AddUsing(newRoot);
method = newRoot.GetAnnotatedNodes(annotation).First();
var newMethod = ChangeParameters(method);
newRoot = root.ReplaceNode(method, newMethod.WithoutAnnotations(annotation));

完全实现:
 private async Task<Document> HaveMethodTakeACancellationTokenParameter(
        Document document, SyntaxNode syntaxNode, CancellationToken cancellationToken)
    {
        var method = syntaxNode as MethodDeclarationSyntax;

        var cancellationTokenParameter =
           SyntaxFactory.Parameter(
               SyntaxFactory.Identifier("cancellationToken")
           )
           .WithType(
               SyntaxFactory.ParseTypeName(
                   typeof(CancellationToken).Name));

        var root = 
           (await document.GetSyntaxTreeAsync(cancellationToken))
               .GetRoot(cancellationToken);

        var annotation = new SyntaxAnnotation();
        var newRoot = root.ReplaceNode(
             method,
             method.WithAdditionalAnnotations(annotation));

        #region Add Using Statements

        var systemThreadingUsingStatement =
            SyntaxFactory.UsingDirective(
                SyntaxFactory.QualifiedName(
                    SyntaxFactory.IdentifierName("System"),
                    SyntaxFactory.IdentifierName("Threading")));

        var compilation =
            newRoot as CompilationUnitSyntax;

        if (null == compilation)
        {
            newRoot =
                newRoot.InsertNodesBefore(
                    newRoot.ChildNodes().First(),
                    new[] {systemThreadingUsingStatement});
        }
        else if (compilation.Usings.All(u => u.Name.GetText().ToString() != typeof(CancellationToken).Namespace))
        {
            newRoot = 
                newRoot.InsertNodesAfter(compilation.Usings.Last(), 
                new[]{ systemThreadingUsingStatement });
        }

        #endregion

        method = (MethodDeclarationSyntax)newRoot.GetAnnotatedNodes(annotation).First();

        var updatedMethod = method.AddParameterListParameters(cancellationTokenParameter);
        newRoot = newRoot.ReplaceNode(method, updatedMethod.WithoutAnnotations(annotation));

        return document.WithSyntaxRoot(newRoot);
    }

为什么不先修改方法,然后再添加 using 呢? - svick
好的观点。在这种情况下,这也可以起作用。如果您需要同时对树进行多个更改,则上述概述的解决方案可作为标记待修改节点的一般方法。 - Tamas
感谢@Tamas-SonarSourceTeam。我根据您的建议更新了您的答案,并提供了完整的实现,以便将来有需要的人参考。感谢您的帮助,很抱歉让您等这么久。 - Philip Pittle
对我来说,如果没有使用 newRoot = newRoot.InsertNodesBeforenewRoot = newRoot.InsertNodesAfter,是行不通的。我使用了 newRoot = compilationUnit.AddUsings(usingDirective); - Marcel Perju

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接