using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Reflection; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Diagnostics; using StellaOps.TestKit; namespace StellaOps.Concelier.Merge.Analyzers.Tests; public sealed class MergeUsageAnalyzerTests { [Trait("Category", TestCategories.Unit)] [Fact] public async Task ReportsDiagnostic_ForAdvisoryMergeServiceInstantiation() { const string source = """ using StellaOps.Concelier.Merge.Services; namespace Sample.App; public sealed class Demo { public void Run() { var merge = new AdvisoryMergeService(); } } """; var diagnostics = await AnalyzeAsync(source, "Sample.App"); Assert.Contains(diagnostics, d => d.Id == MergeUsageAnalyzer.DiagnosticId && d.GetMessage().Contains("AdvisoryMergeService", StringComparison.Ordinal)); } [Trait("Category", TestCategories.Unit)] [Fact] public async Task ReportsDiagnostic_ForAddMergeModuleInvocation() { const string source = """ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using StellaOps.Concelier.Merge; namespace Sample.Services; public static class Installer { public static void Configure(IServiceCollection services, IConfiguration configuration) { services.AddMergeModule(configuration); } } """; var diagnostics = await AnalyzeAsync(source, "Sample.Services"); Assert.Contains(diagnostics, d => d.Id == MergeUsageAnalyzer.DiagnosticId && d.GetMessage().Contains("AddMergeModule", StringComparison.Ordinal)); } [Trait("Category", TestCategories.Unit)] [Fact] public async Task ReportsDiagnostic_ForFieldDeclaration() { const string source = """ using StellaOps.Concelier.Merge.Services; namespace Sample.Library; public sealed class Demo { private AdvisoryMergeService? _mergeService; } """; var diagnostics = await AnalyzeAsync(source, "Sample.Library"); Assert.Contains(diagnostics, d => d.Id == MergeUsageAnalyzer.DiagnosticId); } [Trait("Category", TestCategories.Unit)] [Fact] public async Task DoesNotReportDiagnostic_InsideMergeAssembly() { const string source = """ using StellaOps.Concelier.Merge.Services; namespace StellaOps.Concelier.Merge.Internal; internal static class MergeDiagnostics { public static AdvisoryMergeService Create() => new AdvisoryMergeService(); } """; var diagnostics = await AnalyzeAsync(source, "StellaOps.Concelier.Merge"); Assert.DoesNotContain(diagnostics, d => d.Id == MergeUsageAnalyzer.DiagnosticId); } [Trait("Category", TestCategories.Unit)] [Fact] public async Task ReportsDiagnostic_ForTypeOfUsage() { const string source = """ using System; using StellaOps.Concelier.Merge.Services; namespace Sample.TypeOf; public static class Demo { public static Type TargetType => typeof(AdvisoryMergeService); } """; var diagnostics = await AnalyzeAsync(source, "Sample.TypeOf"); Assert.Contains(diagnostics, d => d.Id == MergeUsageAnalyzer.DiagnosticId); } private static async Task> AnalyzeAsync(string source, string assemblyName) { var compilation = CSharpCompilation.Create( assemblyName, new[] { CSharpSyntaxTree.ParseText(source), CSharpSyntaxTree.ParseText(Stubs) }, CreateMetadataReferences(), new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); var analyzer = new MergeUsageAnalyzer(); var compilationWithAnalyzers = compilation.WithAnalyzers(ImmutableArray.Create(analyzer)); return await compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync(); } private static IEnumerable CreateMetadataReferences() { yield return MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location); yield return MetadataReference.CreateFromFile(typeof(Enumerable).GetTypeInfo().Assembly.Location); } private const string Stubs = """ namespace Microsoft.Extensions.DependencyInjection { public interface IServiceCollection { } } namespace Microsoft.Extensions.Configuration { public interface IConfiguration { } } namespace StellaOps.Concelier.Merge.Services { public sealed class AdvisoryMergeService { } } namespace StellaOps.Concelier.Merge { public static class MergeServiceCollectionExtensions { public static void AddMergeModule( this Microsoft.Extensions.DependencyInjection.IServiceCollection services, Microsoft.Extensions.Configuration.IConfiguration configuration) { } } } """; }