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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\\..\\__Analyzers\\StellaOps.Concelier.Merge.Analyzers\\StellaOps.Concelier.Merge.Analyzers.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user