Почему этот код исправляет мои пустяки? - PullRequest
0 голосов
/ 24 августа 2018

Я разрабатываю codefix, используя roslyn, чтобы исправить недопустимые выражения броска.Кодфикс в целом делает то, что должен делать - однако он искажает мой код, который будет виден на следующих скриншотах

Формат до кодфикса

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 голосов
/ 26 августа 2018

Изменение некоторого кода, как показано ниже, работает, но мне это не нравится. Кажется странным, что правильное сохранение пустяков будет работать только так.

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();
}
...