为什么此代码修复会破坏我的琐事?

时间:2018-08-24 09:31:38

标签: formatting roslyn

我正在使用roslyn开发一个代码修复程序,以修复无效的throw表达式。一般而言,该代码修补程序会执行它应该做的事情-但是它使我的代码格式错误,将在下一个屏幕截图中显示

在代码修复之前设置格式

Format prior to codefix

cedefix之后的格式

enter image description here

我试图格式化生成的语法的语法可视化

Syntax Tree Visualizer

codefix

[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(DoNotRethrowCodeFixProvider)), Shared]
public class DoNotRethrowCodeFixProvider : CodeFixProvider
{
    public sealed override ImmutableArray<string> FixableDiagnosticIds
    {
        get { return ImmutableArray.Create(DoNotRethrowAnalyzer.DiagnosticId); }
    }

    public sealed override FixAllProvider GetFixAllProvider()
    {
        return WellKnownFixAllProviders.BatchFixer;
    }

    public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
    {
        foreach (var diagnostic in context.Diagnostics)
        {
            context.RegisterCodeFix(
                CodeAction.Create(
                    Resources.DoNotRethrowTitle, c => FixDoNotRethrowRule(context, diagnostic, c), Resources.DoNotRethrowTitle),
                diagnostic);
        }
    }

    private async Task<Document> FixDoNotRethrowRule(CodeFixContext context, Diagnostic diagnostic, CancellationToken cancellationToken)
    {
        var root = await context.Document.GetSyntaxRootAsync(cancellationToken);
        if (root.FindNode(diagnostic.Location.SourceSpan) is ThrowStatementSyntax throwSyntax)
        {
            var newThrowStatement = SyntaxFactory.ThrowStatement()
                .WithLeadingTrivia(throwSyntax.ThrowKeyword.LeadingTrivia)
                .WithTrailingTrivia(throwSyntax.SemicolonToken.TrailingTrivia);

            var rewritten = root.ReplaceNode(throwSyntax, newThrowStatement);

            return context.Document.WithSyntaxRoot(rewritten);
        }

        return context.Document;
    }
}

分析器

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class DoNotRethrowAnalyzer : DiagnosticAnalyzer
{
    public const string DiagnosticId = DiagnosticIds.DoNotRethrowAnalyzer.DoNotRethrowRule;

    // You can change these strings in the Resources.resx file. If you do not want your analyzer to be localize-able, you can use regular strings for Title and MessageFormat.
    // See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/Localizing%20Analyzers.md for more on localization
    private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.DoNotRethrowTitle), Resources.ResourceManager, typeof(Resources));
    private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.DoNotRethrowMessageFormat), Resources.ResourceManager, typeof(Resources));
    private static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.DoNotRethrowDescription), Resources.ResourceManager, typeof(Resources));
    private const string Category = "Debuggability";

    private static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning,
        isEnabledByDefault: true,
        description: Description);

    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } }

    public override void Initialize(AnalysisContext context)
    {
        context.RegisterSyntaxNodeAction(AnalyzeThrowExpression, SyntaxKind.ThrowStatement);
    }

    private void AnalyzeThrowExpression(SyntaxNodeAnalysisContext context)
    {
        if (context.Node is ThrowStatementSyntax throwSyntax)
        {
            if (throwSyntax.Expression == null)
                return;

            if(throwSyntax.Expression is IdentifierNameSyntax throwIdentifier 
                && ThrowIdentifierIsIdentifierOfParentClause(throwIdentifier))
                context.ReportDiagnostic(Diagnostic.Create(DoNotRethrowAnalyzer.Rule, throwSyntax.ThrowKeyword.GetLocation()));
        }
    }

    private bool ThrowIdentifierIsIdentifierOfParentClause(IdentifierNameSyntax throwIdentifier)
    {
        var parentCatch = throwIdentifier.NextParentOfType<CatchClauseSyntax>();
        if (parentCatch == null || parentCatch.Declaration == null)
            return false;

        return parentCatch.Declaration.Identifier.ValueText == throwIdentifier.Identifier.ValueText;
    }
}

单元测试

[TestClass]
public class DoNotRethrowTests : CodeFixVerifier
{
    //No diagnostics expected to show up
    [TestMethod]
    public void TestEmptyFile()
    {
        var test = @"";

        VerifyCSharpDiagnostic(test);
    }

    [TestMethod]
    public void InvalidRethrow()
    {
        var test = @"
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;

namespace ConsoleApplication1
{
    class TYPENAME
    {  
        void Offender(){
            throw new Exception(""testa"");
        } 

        void Process(Exception e){
            throw new Exception(""testa"");
        } 

        void Source()
        {
            try
            {
                Offender();
            }
            catch (Exception e)
            {
                Process(e);
                throw e;
            }
        }
    }
}";
        var expected = new[]
        {
            new DiagnosticResult
            {
                Id = DoNotRethrowAnalyzer.DiagnosticId,
                Message = Resources.DoNotRethrowMessageFormat,
                Severity = DiagnosticSeverity.Warning,
                Locations =
                    new[]
                    {
                        new DiagnosticResultLocation("Test0.cs", 30, 6)
                    }
            }
        };

        VerifyCSharpDiagnostic(test, expected);

        var expectedFix = @"
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;

namespace ConsoleApplication1
{
    class TYPENAME
    {  
        void Offender(){
            throw new Exception(""testa"");
        } 

        void Process(Exception e){
            throw new Exception(""testa"");
        } 

        void Source()
        {
            try
            {
                Offender();
            }
            catch (Exception e)
            {
                Process(e);
                throw;
            }
        }
    }
}";
        VerifyCSharpFix(test, expectedFix);
    }

    class TYPENAME
    {
        void Offender()
        {
            throw new Exception("testa");
        }

        void Process(Exception e)
        {
            throw new Exception("testa");
        }

        void Source()
        {
            try
            {
                Offender();
            }
            catch (Exception e)
            {
                Process(e);
                throw;
            }
        }
    }

    //Diagnostic and CodeFix both triggered and checked for
    [TestMethod]
    public void ValidRethrow()
    {
        var test = @"
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;

namespace ConsoleApplication1
{
    class TYPENAME
    {  
        void Offender(){
            throw new Exception(""testa"");
        } 

        void Source()
        {
            try
            {
                Offender();
            }
            catch (Exception e)
            {
                throw new Exception(""test"", e);
            }
        }
    }
}";
        var expected = new DiagnosticResult[0];

        VerifyCSharpDiagnostic(test, expected);
    }

    protected override CodeFixProvider GetCSharpCodeFixProvider()
    {
        return new DoNotRethrowCodeFixProvider();
    }

    protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer()
    {
        return new DoNotRethrowAnalyzer();
    }
}

有人知道我在做什么错吗?我曾尝试使用DocumentEditor进行此代码修复(通常不会像这样给我带来麻烦),但是在那里我遇到了同样的问题。

1 个答案:

答案 0 :(得分:0)

更改一些类似于以下代码的方法,但是我不喜欢它。似乎奇怪的是,正确保存琐事只会这样工作。

if (root.FindNode(diagnostic.Location.SourceSpan) is ThrowStatementSyntax throwSyntax)
{
    var newThrowStatement = throwSyntax
        .WithExpression(SyntaxFactory.ParseExpression(""));

    var editor = await DocumentEditor.CreateAsync(context.Document, context.CancellationToken);
    editor.ReplaceNode(throwSyntax, newThrowStatement);

    return editor.GetChangedDocument();
}