File: src\Analyzers\CSharp\Tests\ImplementInterface\ImplementInterfaceCodeFixTests_Razor.cs
Web Access
Project: src\src\CodeStyle\CSharp\Tests\Microsoft.CodeAnalysis.CSharp.CodeStyle.UnitTests.csproj (Microsoft.CodeAnalysis.CSharp.CodeStyle.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.ImplementInterface;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
using Xunit.Abstractions;
 
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ImplementInterface;
 
[Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)]
public sealed class ImplementInterfaceCodeFixTests_Razor(ITestOutputHelper logger)
    : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest_NoEditor(logger)
{
    private const string CS0535 = nameof(CS0535);
    private const string RazorSourceGeneratorTypeName = "Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator";
 
    private sealed record GeneratedDocumentData(string HintName, string GeneratedCode, bool IsRazor);
 
    internal override (DiagnosticAnalyzer?, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace)
        => (null, new CSharpImplementInterfaceCodeFixProvider());
 
    protected override void InitializeWorkspace(TestWorkspace workspace, TestParameters parameters)
    {
        if (parameters.fixProviderData is not GeneratedDocumentData generatedDocument)
        {
            return;
        }
 
        var analyzerReference = generatedDocument.IsRazor
            ? new TestGeneratorReference(
                new Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator(
                    context => context.AddSource(generatedDocument.HintName, generatedDocument.GeneratedCode)))
            : new TestGeneratorReference(new TestSourceGenerator(generatedDocument.HintName, generatedDocument.GeneratedCode));
 
        var project = workspace.CurrentSolution.Projects.Single();
        var updatedProject = project.AddAnalyzerReference(analyzerReference);
        Assert.True(workspace.TryApplyChanges(updatedProject.Solution));
    }
 
    private static TestParameters WithRazorGeneratedDocument(string generatedCode, string hintName = "Component.razor.g.cs")
        => new(fixProviderData: new GeneratedDocumentData(hintName, generatedCode, IsRazor: true));
 
    private static TestParameters WithGeneratedDocument(string generatedCode, string hintName = "Component.g.cs")
        => new(fixProviderData: new GeneratedDocumentData(hintName, generatedCode, IsRazor: false));
 
    [Fact]
    public Task TestImplementInterfaceInRazorSourceGeneratedDocument()
        => TestImplementingInterfaceInGeneratedDocumentAsync(
            workspaceMarkup: """
                <Workspace>
                    <Project Language="C#" AssemblyName="ClassLibrary1" CommonReferences="true">
                        <Document>
                interface IMyInterface
                {
                    void Method1();
                }
                        </Document>
                    </Project>
                </Workspace>
                """,
            generatedCode: """
                // <auto-generated />
 
                public partial class Component : IMyInterface
                {
                }
                """,
            expectedGeneratedCode: """
                // <auto-generated />
 
                public partial class Component : IMyInterface
                {
                    public void Method1()
                    {
                        throw new System.NotImplementedException();
                    }
                }
                """,
            actionTitle: CodeFixesResources.Implement_interface,
            isRazorSourceGeneratedDocument: true);
 
    [Fact]
    public Task TestImplementInterfaceInHiddenRazorSourceGeneratedDocument()
        => TestImplementingInterfaceInGeneratedDocumentAsync(
            workspaceMarkup: """
                <Workspace>
                    <Project Language="C#" AssemblyName="ClassLibrary1" CommonReferences="true">
                        <Document>
                interface IMyInterface
                {
                    void Method1();
                }
                        </Document>
                    </Project>
                </Workspace>
                """,
            generatedCode: """
                // <auto-generated />
 
                #line hidden
                public partial class Component : IMyInterface
                {
                }
                #line default
                """,
            expectedGeneratedCode: """
                // <auto-generated />
 
                #line hidden
                public partial class Component : IMyInterface
                {
                    public void Method1()
                    {
                        throw new System.NotImplementedException();
                    }
                }
                #line default
                """,
            actionTitle: CodeFixesResources.Implement_interface,
            isRazorSourceGeneratedDocument: true);
 
    [Fact]
    public Task TestImplementInterfaceExplicitlyInRazorSourceGeneratedDocument()
        => TestImplementingInterfaceInGeneratedDocumentAsync(
            workspaceMarkup: """
                <Workspace>
                    <Project Language="C#" AssemblyName="ClassLibrary1" CommonReferences="true">
                        <Document>
                interface IMyInterface
                {
                    void Method1();
                }
                        </Document>
                    </Project>
                </Workspace>
                """,
            generatedCode: """
                // <auto-generated />
 
                public partial class Component : IMyInterface
                {
                }
                """,
            expectedGeneratedCode: """
                // <auto-generated />
 
                public partial class Component : IMyInterface
                {
                    void IMyInterface.Method1()
                    {
                        throw new System.NotImplementedException();
                    }
                }
                """,
            actionTitle: CodeFixesResources.Implement_all_members_explicitly,
            isRazorSourceGeneratedDocument: true);
 
    [Fact]
    public async Task TestDoesNotImplementInterfaceInNonRazorSourceGeneratedDocument()
    {
        using var workspace = CreateWorkspaceFromOptions(
            """
            <Workspace>
                <Project Language="C#" AssemblyName="ClassLibrary1" CommonReferences="true">
                    <Document>
            interface IMyInterface
            {
                void Method1();
            }
                    </Document>
                </Project>
            </Workspace>
            """,
            WithGeneratedDocument(
                """
                // <auto-generated />
 
                public partial class Component : IMyInterface
                {
                }
                """));
 
        var project = workspace.CurrentSolution.Projects.Single();
        var sourceGeneratedDocument = Assert.Single(await project.GetSourceGeneratedDocumentsAsync(CancellationToken.None));
        Assert.DoesNotContain(RazorSourceGeneratorTypeName, sourceGeneratedDocument.FilePath);
 
        var codeActions = await GetCodeActionsAsync(project, sourceGeneratedDocument, CancellationToken.None);
        Assert.Empty(codeActions);
    }
 
    private async Task TestImplementingInterfaceInGeneratedDocumentAsync(
        string workspaceMarkup,
        string generatedCode,
        string expectedGeneratedCode,
        string actionTitle,
        bool isRazorSourceGeneratedDocument)
    {
        using var workspace = CreateWorkspaceFromOptions(
            workspaceMarkup,
            isRazorSourceGeneratedDocument ? WithRazorGeneratedDocument(generatedCode) : WithGeneratedDocument(generatedCode));
 
        var project = workspace.CurrentSolution.Projects.Single();
        var sourceGeneratedDocument = Assert.Single(await project.GetSourceGeneratedDocumentsAsync(CancellationToken.None));
 
        if (isRazorSourceGeneratedDocument)
        {
            Assert.Contains(RazorSourceGeneratorTypeName, sourceGeneratedDocument.FilePath);
        }
        else
        {
            Assert.DoesNotContain(RazorSourceGeneratorTypeName, sourceGeneratedDocument.FilePath);
        }
 
        Assert.StartsWith(
            "// <auto-generated />",
            (await sourceGeneratedDocument.GetTextAsync(CancellationToken.None)).ToString());
 
        var codeActions = await GetCodeActionsAsync(project, sourceGeneratedDocument, CancellationToken.None);
        var codeAction = Assert.Single(codeActions.Where(action => action.Title == actionTitle));
        var operations = await codeAction.GetOperationsAsync(CancellationToken.None);
        var changedSolutions = await ApplyOperationsAndGetSolutionAsync(workspace, operations);
        var newSourceGeneratedDocument = await changedSolutions.Item2.GetSourceGeneratedDocumentAsync(sourceGeneratedDocument.Id, CancellationToken.None);
 
        Assert.NotNull(newSourceGeneratedDocument);
        AssertEx.EqualOrDiff(
            expectedGeneratedCode,
            (await newSourceGeneratedDocument!.GetTextAsync(CancellationToken.None)).ToString());
    }
 
    private static async Task<ImmutableArray<CodeAction>> GetCodeActionsAsync(Project project, Document requestDocument, CancellationToken cancellationToken)
    {
        var requestTree = await requestDocument.GetSyntaxTreeAsync(cancellationToken);
        var compilation = await project.GetCompilationAsync(cancellationToken);
        Assert.NotNull(requestTree);
        Assert.NotNull(compilation);
 
        var diagnostic = Assert.Single(
            compilation!.GetDiagnostics(cancellationToken).Where(
                diagnostic => diagnostic.Id == CS0535 &&
                    diagnostic.Severity == DiagnosticSeverity.Error &&
                    diagnostic.Location.SourceTree == requestTree));
 
        var codeActions = new List<CodeAction>();
        var context = new CodeFixContext(
            requestDocument,
            diagnostic.Location.SourceSpan,
            [diagnostic],
            (action, _) => codeActions.Add(action),
            cancellationToken);
 
        var provider = new CSharpImplementInterfaceCodeFixProvider();
        await provider.RegisterCodeFixesAsync(context);
 
        return FlattenActions([.. codeActions]);
    }
 
#pragma warning disable RS1042 // Do not implement
    private sealed class TestSourceGenerator(string hintName, string generatedCode) : ISourceGenerator
#pragma warning restore RS1042 // Do not implement
    {
        public void Initialize(GeneratorInitializationContext context)
        {
        }
 
        public void Execute(GeneratorExecutionContext context)
            => context.AddSource(hintName, generatedCode);
    }
}