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:
- Create a class that inherits from
DiagnosticSuppressor - Decorate the class with
[DiagnosticAnalyzer(LanguageNames.CSharp)] - Define a
SuppressionDescriptor for each rule you want to suppress - Override
SupportedSuppressions to return the supported descriptors - 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!