XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEExtension.NetCore.XUnit

【DLL】提供方便快捷的测试,无需编写Main方法,可直接添加特性在类或方法上进行测试

公开
关注 0 Fork 0 Star 0
UTF-8
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using XFEExtension.NetCore.XUnit.Analyzer.Diagnostics;

namespace XFEExtension.NetCore.XUnit.Analyzer.CodeFix;

/// <summary>
/// 为不可等待的 <see langword="async void"/> 方法和 3.x 旧特性提供迁移代码修复。
/// </summary>
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(XUnitCodeFixProvider))]
public sealed class XUnitCodeFixProvider : CodeFixProvider
{
    /// <summary>
    /// 获取该提供程序能够修复的诊断标识符。
    /// </summary>
    public override ImmutableArray<string> FixableDiagnosticIds => [XUnitCodeAnalyzer.AsyncVoidId, XUnitCodeAnalyzer.LegacyId];

    /// <summary>
    /// 获取支持文档、项目和解决方案范围批量修复的提供程序。
    /// </summary>
    /// <returns>Roslyn 标准批量修复提供程序。</returns>
    public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;

    /// <summary>
    /// 根据上下文中的诊断注册“返回 Task”或替换旧特性的代码操作。
    /// </summary>
    /// <param name="context">包含文档、诊断和代码操作注册回调的上下文。</param>
    /// <returns>已完成的注册任务。</returns>
    public override Task RegisterCodeFixesAsync(CodeFixContext context)
    {
        foreach (var diagnostic in context.Diagnostics)
        {
            if (diagnostic.Id == XUnitCodeAnalyzer.AsyncVoidId)
                context.RegisterCodeFix(CodeAction.Create("Return Task", token => ChangeAsyncVoidAsync(context.Document, diagnostic, token), "XFE.ReturnTask"), diagnostic);
            else if (diagnostic.Id == XUnitCodeAnalyzer.LegacyId && diagnostic.Properties.TryGetValue("Replacement", out var replacement) && replacement is not null)
                context.RegisterCodeFix(CodeAction.Create($"Use {replacement}", token => ReplaceAttributeAsync(context.Document, diagnostic, replacement, token), "XFE.ReplaceLegacyAttribute"), diagnostic);
        }
        return Task.CompletedTask;
    }

    private static async Task<Document> ChangeAsyncVoidAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
    {
        var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
        if (root?.FindNode(diagnostic.Location.SourceSpan).FirstAncestorOrSelf<MethodDeclarationSyntax>() is not { } method)
            return document;
        var updated = method.WithReturnType(SyntaxFactory.ParseTypeName("global::System.Threading.Tasks.Task").WithTriviaFrom(method.ReturnType));
        return document.WithSyntaxRoot(root.ReplaceNode(method, updated));
    }

    private static async Task<Document> ReplaceAttributeAsync(Document document, Diagnostic diagnostic, string replacement, CancellationToken cancellationToken)
    {
        var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
        if (root?.FindNode(diagnostic.Location.SourceSpan).FirstAncestorOrSelf<AttributeSyntax>() is not { } attribute)
            return document;
        var arguments = replacement is "Benchmark" or "TestFixture" or "BeforeEach" ? null : attribute.ArgumentList;
        var updated = attribute.WithName(SyntaxFactory.IdentifierName(replacement)).WithArgumentList(arguments).WithTriviaFrom(attribute);
        return document.WithSyntaxRoot(root.ReplaceNode(attribute, updated));
    }
}