From 7452c02b119c4c87621569893a1cc927b7b6f75e Mon Sep 17 00:00:00 2001 From: Stuart Turner Date: Mon, 29 Jul 2024 13:40:07 -0500 Subject: [PATCH] Add CA1036 Suppressor --- .../Suppressors/CA1036ComparisonSuppressor.cs | 57 +++++++++++++++++ tests/AnalyzerTests/CA1036SuppressionTests.cs | 64 +++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 src/Vogen/Suppressors/CA1036ComparisonSuppressor.cs create mode 100644 tests/AnalyzerTests/CA1036SuppressionTests.cs diff --git a/src/Vogen/Suppressors/CA1036ComparisonSuppressor.cs b/src/Vogen/Suppressors/CA1036ComparisonSuppressor.cs new file mode 100644 index 0000000000..da5858b8b3 --- /dev/null +++ b/src/Vogen/Suppressors/CA1036ComparisonSuppressor.cs @@ -0,0 +1,57 @@ +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Vogen.Suppressors; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +[SuppressMessage("ReSharper", "InconsistentNaming")] +public sealed class CA1036ComparisonSuppressor : DiagnosticSuppressor +{ + private static readonly SuppressionDescriptor _suppressIComparableWarning = new( + id: "VOGS0003", + suppressedDiagnosticId: "CA1036", + justification: "Suppress CA1036 on value objects."); + + // ReSharper disable once UseCollectionExpression + public override ImmutableArray SupportedSuppressions => + ImmutableArray.Create(_suppressIComparableWarning); + + public override void ReportSuppressions(SuppressionAnalysisContext context) + { + var attributeSymbol = context.Compilation.GetBestTypeByMetadataName("Vogen.ValueObjectAttribute"); + + if (attributeSymbol is null) + { + return; + } + + foreach (var diagnostic in context.ReportedDiagnostics) + { + ProcessDiagnostic(context, diagnostic); + } + } + + private static void ProcessDiagnostic(SuppressionAnalysisContext context, Diagnostic diagnostic) + { + var node = diagnostic.TryFindNode(context.CancellationToken); + + if (node is null) + { + return; + } + + var semanticModel = context.GetSemanticModel(node.SyntaxTree); + + var valueObjectSymbol = semanticModel.GetDeclaredSymbol(node, context.CancellationToken) as INamedTypeSymbol; + + if (!VoFilter.IsTarget(valueObjectSymbol)) + { + return; + } + + var suppression = Suppression.Create(_suppressIComparableWarning, diagnostic); + context.ReportSuppression(suppression); + } +} \ No newline at end of file diff --git a/tests/AnalyzerTests/CA1036SuppressionTests.cs b/tests/AnalyzerTests/CA1036SuppressionTests.cs new file mode 100644 index 0000000000..75e982ac26 --- /dev/null +++ b/tests/AnalyzerTests/CA1036SuppressionTests.cs @@ -0,0 +1,64 @@ +using System.Collections.Immutable; +using System.Threading.Tasks; +using FluentAssertions; +using Microsoft.CodeAnalysis; +using Shared; +using Vogen; +using Vogen.Suppressors; + +namespace AnalyzerTests; + +public class CA1036SuppressionTests +{ + [Fact] + public async Task Suppressed_for_value_object() + { + var source = $$""" +using System; +using Vogen; + +namespace Whatever; + +[ValueObject] +public partial class CustomerId : IComparable +{ +} +"""; + + (ImmutableArray Diagnostics, SyntaxTree[] GeneratedSource) r = + await CreateBuilder(source).GetGeneratedOutput(ignoreInitialCompilationErrors: false); + + r.Diagnostics.Should().HaveCount(0); + } + + [Fact] + public async Task Does_not_suppress_other_types() + { + var source = $$""" +using System; +using Vogen; + +namespace Whatever; + +public partial class CustomerId : IComparable +{ + public int CompareTo(CustomerId other) + { + return 1; + } +} +"""; + + (ImmutableArray Diagnostics, SyntaxTree[] GeneratedSource) r = + await CreateBuilder(source).GetGeneratedOutput(ignoreInitialCompilationErrors: false); + + r.Diagnostics.Should().HaveCount(1); + r.Diagnostics[0].Id.Should().Be("CA1036"); + } + + private static ProjectBuilder CreateBuilder(string source) => new ProjectBuilder() + .WithUserSource(source) + .WithAnalyzer() + .WithTargetFramework(TargetFramework.Net8_0) + .WithMicrosoftCodeAnalysisNetAnalyzers("CA1036"); +} \ No newline at end of file