用私有 const 语句替换字符串文字

Replacing string literals with private const statements

我正在为 C# 代码构建一个分析器,当对某些函数的某些参数使用字符串文字而不是 const 字符串时,它会生成错误。即

class MyClass
{
  private void MyMethod(IWriter writer)
  {
    writer.WriteInteger("NamedValue", 4);
  }
}

应该变成:

class MyClass
{
  private const string IoNamedValueKey = "NamedValue";
  private void MyMethod(IWriter writer)
  {
    writer.WriteInteger(IoNamedValueKey , 4);
  }
}

我已经在显示错误的地方工作了,但我也想提供一个 CodeFixProvider。我 运行 遇到了两个问题:

  1. 我需要添加 private const string IoNamedValueKey = "NamedValue"; 语句,最好就在有问题的方法之上。
  2. 但前提是它不存在。

我不完全确定 CodeFixProvider 的模板方法是否针对我的目的使用了适当的重载(它只是将类型名称替换为大写变体),那么从 RegisterCodeFixesAsync 方法内部前进的最佳方法是什么?

public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
  // ... now what?
}

根据 roslynquoter 的说法,所需的节点可以按如下方式构建,但我仍然对如何将其注入到上下文中有些不知所措。

CompilationUnit()
.WithMembers(
    SingletonList<MemberDeclarationSyntax>(
        FieldDeclaration(
            VariableDeclaration(
                PredefinedType(
                    Token(SyntaxKind.StringKeyword)))
            .WithVariables(
                SingletonSeparatedList<VariableDeclaratorSyntax>(
                    VariableDeclarator(
                        Identifier("IoNamedValueKey"))
                    .WithInitializer(
                        EqualsValueClause(
                            LiteralExpression(
                                SyntaxKind.StringLiteralExpression,
                                Literal("NamedValue")))))))
        .WithModifiers(
            TokenList(
                new []{
                    Token(SyntaxKind.PrivateKeyword),
                    Token(SyntaxKind.ConstKeyword)}))))
.NormalizeWhitespace()

您应该注册一个CodeAction,通过context引入更改的文档。对于

  • 正在生成 SyntaxNodes - 您可以使用 CSharp SyntaxFactory
  • 为您的 consant 获取唯一的名称 - 看看 Roslyn 的 UniqueNameGenerator and NameGenerator,它们没有被 API 公开,但是重新实现它们的一些简化版本会很容易。

这是您的代码可能看起来的示例(已更新):

    public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
    {
        var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);

        var diagnostic = context.Diagnostics.First();
        var diagnosticSpan = diagnostic.Location.SourceSpan;

        var argument = root.FindNode(diagnosticSpan);
        if (!IsBadStringLiteralArgument(argument))
        {
            return;
        }

        // Register a code action that will invoke the fix.
        context.RegisterCodeFix(
            CodeAction.Create(
                title: title,
                createChangedDocument: (ct) => InlineConstField(context.Document, root, argument, ct),
                equivalenceKey: title),
            diagnostic);
    }

    private async Task<Document> InlineConstField(Document document, SyntaxNode root, SyntaxNode argument, CancellationToken cancellationToken)
    {
        var stringLiteral = (argument as ArgumentSyntax).Expression as LiteralExpressionSyntax;
        string suggestdName = this.GetSuggestedName(stringLiteral);
        var containingMember = argument.FirstAncestorOrSelf<MemberDeclarationSyntax>();
        var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
        var containingMemberSymbol = semanticModel.GetDeclaredSymbol(containingMember);


        var takenNames = containingMemberSymbol.ContainingType.MemberNames;
        string uniqueName = this.GetUniqueName(suggestdName, takenNames);
        FieldDeclarationSyntax constField = CreateConstFieldDeclaration(uniqueName, stringLiteral).WithAdditionalAnnotations(Formatter.Annotation);

        var newRoot = root.ReplaceNode(containingMember, new[] { constField, containingMember });
        newRoot = Formatter.Format(newRoot, Formatter.Annotation, document.Project.Solution.Workspace);
        return document.WithSyntaxRoot(newRoot);
    }

    private FieldDeclarationSyntax CreateConstFieldDeclaration(string uniqueName, LiteralExpressionSyntax stringLiteral)
    {
        return SyntaxFactory.FieldDeclaration(
            SyntaxFactory.List<AttributeListSyntax>(),
            SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.ConstKeyword)),
            SyntaxFactory.VariableDeclaration(
                SyntaxFactory.ParseTypeName("string"), 
                SyntaxFactory.SingletonSeparatedList(
                    SyntaxFactory.VariableDeclarator(
                        SyntaxFactory.Identifier(uniqueName), 
                        argumentList: null, 
                        initializer: SyntaxFactory.EqualsValueClause(stringLiteral)))));

    }