Add MergeUsageAnalyzer to detect legacy merge service usage
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
- Implemented MergeUsageAnalyzer to flag usage of AdvisoryMergeService and AddMergeModule. - Created AnalyzerReleases.Shipped.md and AnalyzerReleases.Unshipped.md for release documentation. - Added tests for MergeUsageAnalyzer to ensure correct diagnostics for various scenarios. - Updated project files for analyzers and tests to include necessary dependencies and configurations. - Introduced a sample report structure for scanner output.
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
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;
|
||||
|
||||
namespace StellaOps.Concelier.Merge.Analyzers.Tests;
|
||||
|
||||
public sealed class MergeUsageAnalyzerTests
|
||||
{
|
||||
[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));
|
||||
}
|
||||
|
||||
[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));
|
||||
}
|
||||
|
||||
[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);
|
||||
}
|
||||
|
||||
[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);
|
||||
}
|
||||
|
||||
[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<ImmutableArray<Diagnostic>> 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<DiagnosticAnalyzer>(analyzer));
|
||||
return await compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync();
|
||||
}
|
||||
|
||||
private static IEnumerable<MetadataReference> 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
}
|
||||
Reference in New Issue
Block a user