【问题标题】:Conditionally add "using" statements in Roslyn Code Fix有条件地在 Roslyn 代码修复中添加“使用”语句
【发布时间】:2018-05-27 14:48:46
【问题描述】:

我正在使用 .NET 编译器 API 在 Roslyn 中编写一些代码分析器/代码修复器。我希望代码修复来转换以下代码:

string.Format("{0} {1}", A, B)

StringExtensions.SafeJoin(" ", A, B)

到目前为止,我有这个代码:

private async Task<Document> UseJoinAsync(Document document, InvocationExpressionSyntax invocationExpr, CancellationToken cancellationToken)
{
    var argumentList = invocationExpr.ArgumentList;
    var firstArgument = argumentList.Arguments[1];
    var secondArgument = argumentList.Arguments[2];

    var statement =
        InvocationExpression(
                MemberAccessExpression(
                    SyntaxKind.SimpleMemberAccessExpression,
                    IdentifierName("StringExtensions"), // requires using Trilogy.Miscellaneous
                    IdentifierName("SafeJoin")))
            .WithArgumentList(
                ArgumentList(
                    SeparatedList<ArgumentSyntax>(
                        new SyntaxNodeOrToken[]
                        {
                            Argument(
                                LiteralExpression(
                                    SyntaxKind.StringLiteralExpression,
                                    Literal(" "))),
                            Token(SyntaxKind.CommaToken),
                            firstArgument,
                            Token(SyntaxKind.CommaToken),
                            secondArgument
                        }))).WithLeadingTrivia(invocationExpr.GetLeadingTrivia()).WithTrailingTrivia(invocationExpr.GetTrailingTrivia())
            .WithAdditionalAnnotations(Formatter.Annotation);

    var root = await document.GetSyntaxRootAsync(cancellationToken);

    var newRoot = root.ReplaceNode(invocationExpr, statement);

    var newDocument = document.WithSyntaxRoot(newRoot);

    return newDocument;
}

但是,我有两个悬而未决的问题:

1) 如何将所需的using Trilogy.Miscellaneous 添加到文件顶部。

2) 如何检测我的项目是否引用了所需的程序集。在这种情况下,如果我的程序集 Trilogy.Common 未被引用,我将不提供代码修复,或者我会建议 string.Join(" ", A, B) 而不是我自己的 SafeJoin 实现。

更新

我已经通过如下更新我的代码解决了#1...

var newRoot = root.ReplaceNode(invocationExpr, statement);

// Iterate through our usings to see if we've got what we need...
if (root?.Usings.Any(u => u.Name.ToString() == "Trilogy.Miscellaneous") == false)
{
    // Create and add the using statement...
    var usingStatement = UsingDirective(QualifiedName(IdentifierName("Trilogy"), IdentifierName("Miscellaneous")));
     newRoot = newRoot.AddUsings(usingStatement);
}

var newDocument = document.WithSyntaxRoot(newRoot);

return newDocument;

仍然希望对第 2 项有所帮助。

【问题讨论】:

  • 我只是好奇,但 SafeJoin 的名称有点暗示 string.Join 不安全?真的吗?如果是这样,它有什么问题?
  • 我知道String.Format() 有问题,如果 args 的数量与格式字符串中 {#} 的数量不匹配,但如果它实际上有两个 args,我看不到问题...
  • String.Format() 或 Join 如果参数为 null 或空,或者只是空格本身,则可以为您留下额外或尾随空格。我们不希望那样。 var a = "你好"; var b =“出”;字符串 c = null; // 返回 "Hello out"

标签: c# roslyn roslyn-code-analysis


【解决方案1】:

我最终向 RegisterCodeFixesAsync 方法添加了一些代码。我觉得对我所需的程序集的测试有点像 hack,所以如果有人发布一个问题,我会接受这个问题的更好答案。

   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;

        // Find the type invocation expression identified by the diagnostic.
        var invocationExpr = root.FindToken(
                diagnosticSpan.Start).Parent.AncestorsAndSelf()
            .OfType<InvocationExpressionSyntax>().First();

        // Get the symantec model from the document
        var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false);

        // Check for the assembly we need.  I suspect there is a better way...
        var hasAssembly = semanticModel.Compilation.ExternalReferences.Any(er => er.Properties.Kind == MetadataImageKind.Assembly && er.Display.EndsWith("Trilogy.Common.dll"));

        // Register a code action that will invoke the fix, but only
        // if we have the assembly that we need
        if (hasAssembly)
            context.RegisterCodeFix(
                CodeAction.Create(title, c => UseJoinAsync(
                    context.Document, invocationExpr, c), equivalenceKey: title), diagnostic);
    }

【讨论】:

    猜你喜欢
    • 2020-12-08
    • 1970-01-01
    • 2017-11-15
    • 2013-09-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-11
    • 1970-01-01
    相关资源
    最近更新 更多