File: MetaAnalyzers\CompilerExtensionTargetFrameworkAnalyzerTests.cs
Web Access
Project: src\src\RoslynAnalyzers\Microsoft.CodeAnalysis.Analyzers\UnitTests\Microsoft.CodeAnalysis.Analyzers.UnitTests.csproj (Microsoft.CodeAnalysis.Analyzers.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;
using System.Collections.Immutable;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Testing;
using Xunit;
using VerifyCS = Test.Utilities.CSharpCodeFixVerifier<
    Microsoft.CodeAnalysis.Analyzers.MetaAnalyzers.CompilerExtensionTargetFrameworkAnalyzer,
    Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider>;
using VerifyVB = Test.Utilities.VisualBasicCodeFixVerifier<
    Microsoft.CodeAnalysis.Analyzers.MetaAnalyzers.CompilerExtensionTargetFrameworkAnalyzer,
    Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider>;
 
namespace Microsoft.CodeAnalysis.Analyzers.UnitTests.MetaAnalyzers
{
    public class CompilerExtensionTargetFrameworkAnalyzerTests
    {
        public enum ImplementationLanguage
        {
            CSharp,
            VisualBasic,
        }
 
        public enum SupportedLanguage
        {
            CSharp,
            VisualBasic,
            CSharpAndVisualBasic,
        }
 
        public enum CompilerFeature
        {
            DiagnosticAnalyzer,
            DiagnosticSuppressor,
            ISourceGenerator,
            IIncrementalGenerator,
        }
 
        public enum SupportedTargetFramework
        {
            // Excluding theoretically-supported frameworks that we can't test using Microsoft.CodeAnalysis.Testing.
            //NetStandard1_0,
            //NetStandard1_1,
            //NetStandard1_2,
            NetStandard1_3,
            NetStandard1_4,
            NetStandard1_5,
            NetStandard1_6,
            NetStandard2_0,
        }
 
        public enum UnsupportedTargetFramework
        {
            NetFramework4_7_2,
            NetStandard2_1,
            Net6_0,
            Net7_0,
            Net8_0,
        }
 
        [Theory]
        [CombinatorialData]
        public async Task CSharpAnalyzerDefinedWithSupportedFramework(SupportedTargetFramework supportedFramework)
        {
            await new VerifyCS.Test
            {
                ReferenceAssemblies = GetReferenceAssembliesForTargetFramework(supportedFramework).AddPackages(ImmutableArray.Create(
                    new PackageIdentity("Microsoft.CodeAnalysis.Common", GetCodeAnalysisPackageVersion(supportedFramework)))),
                TestState =
                {
                    Sources =
                    {
                        DefineFeature(ImplementationLanguage.CSharp, CompilerFeature.DiagnosticAnalyzer, SupportedLanguage.CSharp),
                        GetTargetFrameworkAttribute(ImplementationLanguage.CSharp, supportedFramework),
                    },
                },
            }.RunAsync();
        }
 
        [Theory]
        [CombinatorialData]
        public async Task CSharpFeatureDefinedWithSupportedFramework(CompilerFeature feature)
        {
            await new VerifyCS.Test
            {
                ReferenceAssemblies = GetReferenceAssembliesForTargetFramework(SupportedTargetFramework.NetStandard2_0).AddPackages(ImmutableArray.Create(
                    new PackageIdentity("Microsoft.CodeAnalysis.Common", GetCodeAnalysisPackageVersion(SupportedTargetFramework.NetStandard2_0)))),
                TestState =
                {
                    Sources =
                    {
                        DefineFeature(ImplementationLanguage.CSharp, feature, SupportedLanguage.CSharp),
                        GetTargetFrameworkAttribute(ImplementationLanguage.CSharp, SupportedTargetFramework.NetStandard2_0),
                    },
                },
            }.RunAsync();
        }
 
        [Theory]
        [CombinatorialData]
        public async Task CSharpFeatureDefinedWithUnsupportedFramework(CompilerFeature feature, UnsupportedTargetFramework framework)
        {
            await new VerifyCS.Test
            {
                ReferenceAssemblies = GetReferenceAssembliesForTargetFramework(framework).AddPackages(ImmutableArray.Create(
                    new PackageIdentity("Microsoft.CodeAnalysis.Common", GetCodeAnalysisPackageVersion(framework)))),
                TestState =
                {
                    Sources =
                    {
                        DefineFeature(ImplementationLanguage.CSharp, feature, SupportedLanguage.CSharp),
                        GetTargetFrameworkAttribute(ImplementationLanguage.CSharp, framework),
                    },
                },
                ExpectedDiagnostics =
                {
                    VerifyCS.Diagnostic().WithLocation(0).WithArguments(GetDisplayName(framework)),
                },
            }.RunAsync();
        }
 
        [Theory]
        [CombinatorialData]
        public async Task VisualBasicAnalyzerDefinedWithSupportedFramework(SupportedTargetFramework supportedFramework)
        {
            await new VerifyVB.Test
            {
                ReferenceAssemblies = GetReferenceAssembliesForTargetFramework(supportedFramework).AddPackages(ImmutableArray.Create(
                    new PackageIdentity("Microsoft.CodeAnalysis.Common", GetCodeAnalysisPackageVersion(supportedFramework)))),
                TestState =
                {
                    Sources =
                    {
                        DefineFeature(ImplementationLanguage.VisualBasic, CompilerFeature.DiagnosticAnalyzer, SupportedLanguage.VisualBasic),
                        GetTargetFrameworkAttribute(ImplementationLanguage.VisualBasic, supportedFramework),
                    },
                },
            }.RunAsync();
        }
 
        [Theory]
        [CombinatorialData]
        public async Task VisualBasicFeatureDefinedWithSupportedFramework(CompilerFeature feature)
        {
            await new VerifyVB.Test
            {
                ReferenceAssemblies = GetReferenceAssembliesForTargetFramework(SupportedTargetFramework.NetStandard2_0).AddPackages(ImmutableArray.Create(
                    new PackageIdentity("Microsoft.CodeAnalysis.Common", GetCodeAnalysisPackageVersion(SupportedTargetFramework.NetStandard2_0)))),
                TestState =
                {
                    Sources =
                    {
                        DefineFeature(ImplementationLanguage.VisualBasic, feature, SupportedLanguage.VisualBasic),
                        GetTargetFrameworkAttribute(ImplementationLanguage.VisualBasic, SupportedTargetFramework.NetStandard2_0),
                    },
                },
            }.RunAsync();
        }
 
        [Theory]
        [CombinatorialData]
        public async Task VisualBasicFeatureDefinedWithUnsupportedFramework(CompilerFeature feature, UnsupportedTargetFramework framework)
        {
            await new VerifyVB.Test
            {
                ReferenceAssemblies = GetReferenceAssembliesForTargetFramework(framework).AddPackages(ImmutableArray.Create(
                    new PackageIdentity("Microsoft.CodeAnalysis.Common", GetCodeAnalysisPackageVersion(framework)))),
                TestState =
                {
                    Sources =
                    {
                        DefineFeature(ImplementationLanguage.VisualBasic, feature, SupportedLanguage.VisualBasic),
                        GetTargetFrameworkAttribute(ImplementationLanguage.VisualBasic, framework),
                    },
                },
                ExpectedDiagnostics =
                {
                    VerifyVB.Diagnostic().WithLocation(0).WithArguments(GetDisplayName(framework)),
                },
            }.RunAsync();
        }
 
        private static ReferenceAssemblies GetReferenceAssembliesForTargetFramework(SupportedTargetFramework framework)
        {
            return framework switch
            {
                //SupportedTargetFramework.NetStandard1_0 => ReferenceAssemblies.NetStandard.NetStandard10,
                //SupportedTargetFramework.NetStandard1_1 => ReferenceAssemblies.NetStandard.NetStandard11,
                //SupportedTargetFramework.NetStandard1_2 => ReferenceAssemblies.NetStandard.NetStandard12,
                SupportedTargetFramework.NetStandard1_3 => ReferenceAssemblies.NetStandard.NetStandard13,
                SupportedTargetFramework.NetStandard1_4 => ReferenceAssemblies.NetStandard.NetStandard14,
                SupportedTargetFramework.NetStandard1_5 => ReferenceAssemblies.NetStandard.NetStandard15,
                SupportedTargetFramework.NetStandard1_6 => ReferenceAssemblies.NetStandard.NetStandard16,
                SupportedTargetFramework.NetStandard2_0 => ReferenceAssemblies.NetStandard.NetStandard20,
                _ => throw new ArgumentException("Unknown target framework"),
            };
        }
 
        private static ReferenceAssemblies GetReferenceAssembliesForTargetFramework(UnsupportedTargetFramework framework)
        {
            return framework switch
            {
                UnsupportedTargetFramework.NetFramework4_7_2 => ReferenceAssemblies.NetFramework.Net472.Default,
                UnsupportedTargetFramework.NetStandard2_1 => ReferenceAssemblies.NetStandard.NetStandard21,
                //UnsupportedTargetFramework.NetCoreApp3_1 => ReferenceAssemblies.NetCore.NetCoreApp31,
                UnsupportedTargetFramework.Net6_0 => ReferenceAssemblies.Net.Net60,
                UnsupportedTargetFramework.Net7_0 => ReferenceAssemblies.Net.Net70,
                UnsupportedTargetFramework.Net8_0 => ReferenceAssemblies.Net.Net80,
                _ => throw new ArgumentException("Unknown target framework"),
            };
        }
 
        private static string GetCodeAnalysisPackageVersion(SupportedTargetFramework framework)
        {
            return framework switch
            {
                //SupportedTargetFramework.NetStandard1_0 => throw new NotImplementedException(),
                //SupportedTargetFramework.NetStandard1_1 => throw new NotImplementedException(),
                //SupportedTargetFramework.NetStandard1_2 => throw new NotImplementedException(),
                SupportedTargetFramework.NetStandard1_3 => "2.8.2",
                SupportedTargetFramework.NetStandard1_4 => "2.8.2",
                SupportedTargetFramework.NetStandard1_5 => "2.8.2",
                SupportedTargetFramework.NetStandard1_6 => "2.8.2",
                SupportedTargetFramework.NetStandard2_0 => "4.6.0",
                _ => throw new ArgumentException("Unknown target framework"),
            };
        }
 
        private static string GetTargetFrameworkAttribute(ImplementationLanguage language, SupportedTargetFramework framework)
        {
            var (name, displayName) = framework switch
            {
                SupportedTargetFramework.NetStandard1_3 => (".NETStandard,Version=v1.3", ".NET Standard 1.3"),
                SupportedTargetFramework.NetStandard1_4 => (".NETStandard,Version=v1.4", ".NET Standard 1.4"),
                SupportedTargetFramework.NetStandard1_5 => (".NETStandard,Version=v1.5", ".NET Standard 1.5"),
                SupportedTargetFramework.NetStandard1_6 => (".NETStandard,Version=v1.6", ".NET Standard 1.6"),
                SupportedTargetFramework.NetStandard2_0 => (".NETStandard,Version=v2.0", ".NET Standard 2.0"),
                _ => throw new NotImplementedException(),
            };
 
            return language switch
            {
                ImplementationLanguage.CSharp => $"[assembly: System.Runtime.Versioning.TargetFrameworkAttribute(\"{name}\", FrameworkDisplayName = \"{displayName}\")]",
                ImplementationLanguage.VisualBasic => $"<Assembly: Global.System.Runtime.Versioning.TargetFrameworkAttribute(\"{name}\", FrameworkDisplayName:=\"{displayName}\")>",
                _ => throw new NotImplementedException(),
            };
        }
 
        private static string GetTargetFrameworkAttribute(ImplementationLanguage language, UnsupportedTargetFramework framework)
        {
            var (name, displayName) = framework switch
            {
                UnsupportedTargetFramework.NetFramework4_7_2 => (".NETFramework,Version=v4.7.2", ".NET Framework 4.7.2"),
                UnsupportedTargetFramework.NetStandard2_1 => (".NETStandard,Version=v2.1", ".NET Standard 2.1"),
                UnsupportedTargetFramework.Net6_0 => (".NETCoreApp,Version=v6.0", ".NET 6.0"),
                UnsupportedTargetFramework.Net7_0 => (".NETCoreApp,Version=v7.0", ".NET 7.0"),
                UnsupportedTargetFramework.Net8_0 => (".NETCoreApp,Version=v8.0", ".NET 8.0"),
                _ => throw new NotImplementedException(),
            };
 
            return language switch
            {
                ImplementationLanguage.CSharp => $"[assembly: System.Runtime.Versioning.TargetFrameworkAttribute(\"{name}\", FrameworkDisplayName = \"{displayName}\")]",
                ImplementationLanguage.VisualBasic => $"<Assembly: Global.System.Runtime.Versioning.TargetFrameworkAttribute(\"{name}\", FrameworkDisplayName:=\"{displayName}\")>",
                _ => throw new NotImplementedException(),
            };
        }
 
        private static string GetDisplayName(UnsupportedTargetFramework framework)
        {
            return framework switch
            {
                UnsupportedTargetFramework.NetFramework4_7_2 => ".NET Framework 4.7.2",
                UnsupportedTargetFramework.NetStandard2_1 => ".NET Standard 2.1",
                UnsupportedTargetFramework.Net6_0 => ".NET 6.0",
                UnsupportedTargetFramework.Net7_0 => ".NET 7.0",
                UnsupportedTargetFramework.Net8_0 => ".NET 8.0",
                _ => throw new NotImplementedException(),
            };
        }
 
        private static string GetCodeAnalysisPackageVersion(UnsupportedTargetFramework framework)
        {
            return framework switch
            {
                _ => "4.0.0",
            };
        }
 
        private static string DefineFeature(ImplementationLanguage languageName, CompilerFeature feature, SupportedLanguage supportedLanguage)
        {
            var languageApplication = (languageName, supportedLanguage) switch
            {
                (_, SupportedLanguage.CSharp) => "LanguageNames.CSharp",
                (_, SupportedLanguage.VisualBasic) => "LanguageNames.VisualBasic",
                (ImplementationLanguage.CSharp, SupportedLanguage.CSharpAndVisualBasic) => "LanguageNames.CSharp, LanguageNames.VisualBasic",
                (ImplementationLanguage.VisualBasic, SupportedLanguage.CSharpAndVisualBasic) => "LanguageNames.VisualBasic, LanguageNames.CSharp",
                _ => throw new NotImplementedException(),
            };
 
            return (languageName, feature) switch
            {
                (ImplementationLanguage.CSharp, CompilerFeature.DiagnosticAnalyzer) =>
                    $$"""
                    using System.Collections.Immutable;
                    using Microsoft.CodeAnalysis;
                    using Microsoft.CodeAnalysis.Diagnostics;
 
                    [{|#0:DiagnosticAnalyzer({{languageApplication}})|}]
                    public class MyAnalyzer : DiagnosticAnalyzer
                    {
                        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }
                        public override void Initialize(AnalysisContext context) { }
                    }
                    """,
                (ImplementationLanguage.CSharp, CompilerFeature.DiagnosticSuppressor) =>
                    $$"""
                    using System.Collections.Immutable;
                    using Microsoft.CodeAnalysis;
                    using Microsoft.CodeAnalysis.Diagnostics;
 
                    [{|#0:DiagnosticAnalyzer({{languageApplication}})|}]
                    public class MySuppressor : DiagnosticSuppressor
                    {
                        public override ImmutableArray<SuppressionDescriptor> SupportedSuppressions { get; }
                        public override void ReportSuppressions(SuppressionAnalysisContext context) { }
                    }
                    """,
                (ImplementationLanguage.CSharp, CompilerFeature.ISourceGenerator) =>
                    $$"""
                    using Microsoft.CodeAnalysis;
 
                    [{|#0:Generator({{languageApplication}})|}]
                    public class MyGenerator : ISourceGenerator
                    {
                        public void Initialize(GeneratorInitializationContext context) { }
                        public void Execute(GeneratorExecutionContext context) { }
                    }
                    """,
                (ImplementationLanguage.CSharp, CompilerFeature.IIncrementalGenerator) =>
                    $$"""
                    using Microsoft.CodeAnalysis;
 
                    [{|#0:Generator({{languageApplication}})|}]
                    public class MyGenerator : IIncrementalGenerator
                    {
                        public void Initialize(IncrementalGeneratorInitializationContext context) { }
                    }
                    """,
                (ImplementationLanguage.VisualBasic, CompilerFeature.DiagnosticAnalyzer) =>
                    $$"""
                    Imports System.Collections.Immutable
                    Imports Microsoft.CodeAnalysis
                    Imports Microsoft.CodeAnalysis.Diagnostics
 
                    <{|#0:DiagnosticAnalyzer({{languageApplication}})|}>
                    Public Class MyAnalyzer
                        Inherits DiagnosticAnalyzer
 
                        Public Overrides ReadOnly Property SupportedDiagnostics As ImmutableArray(Of DiagnosticDescriptor)
                        Public Overrides Sub Initialize(context As AnalysisContext)
                        End Sub
                    End Class
                    """,
                (ImplementationLanguage.VisualBasic, CompilerFeature.DiagnosticSuppressor) =>
                    $$"""
                    Imports System.Collections.Immutable
                    Imports Microsoft.CodeAnalysis
                    Imports Microsoft.CodeAnalysis.Diagnostics
 
                    <{|#0:DiagnosticAnalyzer({{languageApplication}})|}>
                    Public Class MySuppressor
                        Inherits DiagnosticSuppressor
 
                        Public Overrides ReadOnly Property SupportedSuppressions As ImmutableArray(Of SuppressionDescriptor)
                        Public Overrides Sub ReportSuppressions(context As SuppressionAnalysisContext)
                        End Sub
                    End Class
                    """,
                (ImplementationLanguage.VisualBasic, CompilerFeature.ISourceGenerator) =>
                    $$"""
                    Imports Microsoft.CodeAnalysis
 
                    <{|#0:Generator({{languageApplication}})|}>
                    Public Class MyGenerator
                        Implements ISourceGenerator
 
                        Public Sub Initialize(context As GeneratorInitializationContext) Implements ISourceGenerator.Initialize
                        End Sub
 
                        Public Sub Execute(context As GeneratorExecutionContext) Implements ISourceGenerator.Execute
                        End Sub
                    End Class
                    """,
                (ImplementationLanguage.VisualBasic, CompilerFeature.IIncrementalGenerator) =>
                    $$"""
                    Imports Microsoft.CodeAnalysis
 
                    <{|#0:Generator({{languageApplication}})|}>
                    Public Class MyGenerator
                        Implements IIncrementalGenerator
 
                        Public Sub Initialize(context As IncrementalGeneratorInitializationContext) Implements IIncrementalGenerator.Initialize
                        End Sub
                    End Class
                    """,
                _ => throw new NotImplementedException(),
            };
        }
    }
}