Suppressing Roslyn Analyzer Warnings Programmatically using DiagnosticSuppressor

 
 
  • Gérald Barré

Roslyn analyzers are great for enforcing coding standards and finding bugs. However, they can sometimes report false positives or warnings you want to ignore in specific contexts. You can suppress these warnings using #pragma directives or [SuppressMessage] attributes, but both approaches require modifying the source code.

A DiagnosticSuppressor is a Roslyn component that suppresses diagnostics reported by other analyzers or the compiler. Unlike a regular analyzer, it does not report new diagnostics; instead, it inspects existing ones and decides whether to suppress them. This is especially useful when you want context-aware suppression without cluttering your code with inline suppressions.

#Implementing a DiagnosticSuppressor

Implementing a DiagnosticSuppressor is very similar to a regular diagnostic analyzer.

To implement a suppressor, you need to:

  1. Create a class that inherits from DiagnosticSuppressor
  2. Decorate the class with [DiagnosticAnalyzer(LanguageNames.CSharp)]
  3. Define a SuppressionDescriptor for each rule you want to suppress
  4. Override SupportedSuppressions to return the supported descriptors
  5. Override ReportSuppressions to implement the suppression logic

#Example: Suppressing CA1507 for JSON Properties

Let's say you are using Newtonsoft.Json to serialize your classes. The CA1507 analyzer rule suggests replacing string literals with nameof expressions for better refactoring safety. However, when you use the [JsonProperty("propertyName")] attribute, the analyzer flags the string literal as a violation of CA1507.

In this scenario, you might want to keep the string literal instead of using nameof because:

  • The JSON property name is part of your API contract and should remain stable even if you rename the C# property
  • Using nameof would couple the JSON serialization format to your C# naming, which could break existing API consumers
  • You want to be explicit about the exact JSON property name in the serialized output

Here is how you can implement a suppressor for this scenario:

C#
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using System.Collections.Immutable;

namespace Meziantou.Analyzer.Suppressors;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class CA1507SerializationPropertyNameSuppressor : DiagnosticSuppressor
{
    // Define the suppression rule
    private static readonly SuppressionDescriptor RuleJsonProperty = new(
        id: "EXAMPLE0001", // Your unique suppression ID
        suppressedDiagnosticId: "CA1507", // The ID of the diagnostic to suppress
        justification: "Suppress CA1507 on methods decorated with a [Newtonsoft.Json.JsonPropertyAttribute]."
    );

    // Register the supported suppression
    public override ImmutableArray<SuppressionDescriptor> SupportedSuppressions => ImmutableArray.Create(RuleJsonProperty);

    public override void ReportSuppressions(SuppressionAnalysisContext context)
    {
        // Iterate over all reported diagnostics that match the suppressed ID
        foreach (var diagnostic in context.ReportedDiagnostics)
        {
            // Get the syntax node associated with the diagnostic
            var node = diagnostic.TryFindNode(context.CancellationToken);
            if (node is null)
                return;

            // Find the attribute containing the node
            var parent = node.FirstAncestorOrSelf<AttributeSyntax>();
            if (parent is null)
                return;

            // Get the semantic model to resolve symbols
            var semanticModel = context.GetSemanticModel(node.SyntaxTree);
            var info = semanticModel.GetSymbolInfo(parent, context.CancellationToken);

            // Check if the attribute is a constructor of JsonPropertyAttribute
            if (info.Symbol is not IMethodSymbol methodSymbol)
                return;

            // Check if the attribute type is Newtonsoft.Json.JsonPropertyAttribute
            var jsonPropertyAttributeType = context.Compilation.GetTypeByMetadataName("Newtonsoft.Json.JsonPropertyAttribute");
            if (methodSymbol.ContainingType.Equals(jsonPropertyAttributeType, SymbolEqualityComparer.Default))
            {
                // Create the suppression
                var suppression = Suppression.Create(RuleJsonProperty, diagnostic);

                // Report the suppression
                context.ReportSuppression(suppression);
            }
        }
    }
}

#Publishing as a NuGet Package

Packaging a DiagnosticSuppressor into a NuGet package allows you to easily distribute and reuse it across multiple projects. The publishing process is the same as for regular Roslyn analyzers. I've written a detailed guide on creating and publishing Roslyn analyzers as NuGet packages, which you can follow: Writing a Roslyn analyzer

#Conclusion

The DiagnosticSuppressor API provides a powerful way to programmatically suppress analyzer warnings. It lets you define precise rules for when a warning should be ignored, keeping your code free of #pragma directives and [SuppressMessage] attributes. This is particularly valuable in large codebases or when working with third-party analyzers that are too aggressive in certain contexts.

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?