diff --git a/src/xunit.analyzers.tests/Utility/CSharpVerifier.Suppressors.cs b/src/xunit.analyzers.tests/Utility/CSharpVerifier.Suppressors.cs new file mode 100644 index 00000000..b6a556bb --- /dev/null +++ b/src/xunit.analyzers.tests/Utility/CSharpVerifier.Suppressors.cs @@ -0,0 +1,177 @@ +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; + +public partial class CSharpVerifier +{ + // ----- Multi-version ----- + + /// + /// Verify that an analyzer was used to suppress another analyzers. Runs against + /// xUnit.net v2 and v3, using C# 6. + /// + /// The code to verify + /// The analyzer that is expected to be suppressed + /// Any expected diagnostics that still exist after the suppression + public static async Task VerifySuppressor( + string source, + DiagnosticAnalyzer suppressedAnalyzer, + params DiagnosticResult[] diagnostics) + { + await VerifySuppressorV2(LanguageVersion.CSharp6, [source], [suppressedAnalyzer], diagnostics); + await VerifySuppressorV3(LanguageVersion.CSharp6, [source], [suppressedAnalyzer], diagnostics); + } + + /// + /// Verify that an analyzer was used to suppress another analyzers. Runs against + /// xUnit.net v2 and v3, using the provided version of C#. + /// + /// The code to verify + /// The analyzer that is expected to be suppressed + /// Any expected diagnostics that still exist after the suppression + public static async Task VerifySuppressor( + LanguageVersion languageVersion, + string source, + DiagnosticAnalyzer suppressedAnalyzer, + params DiagnosticResult[] diagnostics) + { + await VerifySuppressorV2(languageVersion, [source], [suppressedAnalyzer], diagnostics); + await VerifySuppressorV3(languageVersion, [source], [suppressedAnalyzer], diagnostics); + } + + /// + /// Verify that an analyzer was used to suppress one or more other analyzers. Runs against + /// xUnit.net v2 and v3, using the provided version of C#. + /// + /// The language version to compile with + /// The code to verify + /// The analyzer(s) that are expected to be suppressed + /// Any expected diagnostics that still exist after the suppression + public static async Task VerifySuppressor( + LanguageVersion languageVersion, + string[] sources, + DiagnosticAnalyzer[] suppressedAnalyzers, + params DiagnosticResult[] diagnostics) + { + await VerifySuppressorV2(languageVersion, sources, suppressedAnalyzers, diagnostics); + await VerifySuppressorV3(languageVersion, sources, suppressedAnalyzers, diagnostics); + } + + // ----- v2 ----- + + /// + /// Verify that an analyzer was used to suppress another analyzers. Runs against + /// xUnit.net v2, using C# 6. + /// + /// The code to verify + /// The analyzer that is expected to be suppressed + /// Any expected diagnostics that still exist after the suppression + public static Task VerifySuppressorV2( + string source, + DiagnosticAnalyzer suppressedAnalyzer, + params DiagnosticResult[] diagnostics) => + VerifySuppressorV2(LanguageVersion.CSharp6, [source], [suppressedAnalyzer], diagnostics); + + /// + /// Verify that an analyzer was used to suppress another analyzers. Runs against + /// xUnit.net v2, using the provided version of C#. + /// + /// The language version to compile with + /// The code to verify + /// The analyzer that is expected to be suppressed + /// Any expected diagnostics that still exist after the suppression + public static Task VerifySuppressorV2( + LanguageVersion languageVersion, + string source, + DiagnosticAnalyzer suppressedAnalyzer, + params DiagnosticResult[] diagnostics) => + VerifySuppressorV2(languageVersion, [source], [suppressedAnalyzer], diagnostics); + + /// + /// Verify that an analyzer was used to suppress one or more other analyzers. Runs against + /// xUnit.net v2, using the provided version of C#. + /// + /// The language version to compile with + /// The code to verify + /// The analyzer(s) that are expected to be suppressed + /// Any expected diagnostics that still exist after the suppression + public static Task VerifySuppressorV2( + LanguageVersion languageVersion, + string[] sources, + DiagnosticAnalyzer[] suppressedAnalyzers, + params DiagnosticResult[] diagnostics) + { + var test = new TestV2(languageVersion); + + foreach (var suppressedAnalyzer in suppressedAnalyzers) + test.AddDiagnosticAnalyzer(suppressedAnalyzer); + + foreach (var source in sources) + test.TestState.Sources.Add(source); + + test.TestState.ExpectedDiagnostics.AddRange(diagnostics); + test.TestState.OutputKind = OutputKind.ConsoleApplication; + test.TestState.Sources.Add("internal class Program { public static void Main() { } }"); + return test.RunAsync(); + } + + // ----- v3 ----- + + /// + /// Verify that an analyzer was used to suppress another analyzers. Runs against + /// xUnit.net v3, using C# 6. + /// + /// The code to verify + /// The analyzer that is expected to be suppressed + /// Any expected diagnostics that still exist after the suppression + public static Task VerifySuppressorV3( + string source, + DiagnosticAnalyzer suppressedAnalyzer, + params DiagnosticResult[] diagnostics) => + VerifySuppressorV3(LanguageVersion.CSharp6, [source], [suppressedAnalyzer], diagnostics); + + /// + /// Verify that an analyzer was used to suppress another analyzers. Runs against + /// xUnit.net v3, using the provided version of C#. + /// + /// The language version to compile with + /// The code to verify + /// The analyzer that is expected to be suppressed + /// Any expected diagnostics that still exist after the suppression + public static Task VerifySuppressorV3( + LanguageVersion languageVersion, + string source, + DiagnosticAnalyzer suppressedAnalyzer, + params DiagnosticResult[] diagnostics) => + VerifySuppressorV3(languageVersion, [source], [suppressedAnalyzer], diagnostics); + + /// + /// Verify that an analyzer was used to suppress one or more other analyzers. Runs against + /// xUnit.net v3, using the provided version of C#. + /// + /// The language version to compile with + /// The code to verify + /// The analyzer(s) that are expected to be suppressed + /// Any expected diagnostics that still exist after the suppression + public static Task VerifySuppressorV3( + LanguageVersion languageVersion, + string[] sources, + DiagnosticAnalyzer[] suppressedAnalyzers, + params DiagnosticResult[] diagnostics) + { + var test = new TestV3(languageVersion); + + foreach (var suppressedAnalyzer in suppressedAnalyzers) + test.AddDiagnosticAnalyzer(suppressedAnalyzer); + + foreach (var source in sources) + test.TestState.Sources.Add(source); + + test.TestState.ExpectedDiagnostics.AddRange(diagnostics); + test.TestState.OutputKind = OutputKind.ConsoleApplication; + test.TestState.Sources.Add("internal class Program { public static void Main() { } }"); + return test.RunAsync(); + } +} diff --git a/src/xunit.analyzers/Utility/XunitDiagnosticSuppressor.cs b/src/xunit.analyzers/Utility/XunitDiagnosticSuppressor.cs new file mode 100644 index 00000000..08bf80d1 --- /dev/null +++ b/src/xunit.analyzers/Utility/XunitDiagnosticSuppressor.cs @@ -0,0 +1,58 @@ +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Xunit.Analyzers; + +/// +/// Base class for diagnostic suppressors which support xUnit.net v2 and v3. +/// +public abstract class XunitDiagnosticSuppressor : DiagnosticSuppressor +{ + protected XunitDiagnosticSuppressor(SuppressionDescriptor descriptor) => + SupportedSuppressions = new[] { descriptor }.ToImmutableArray(); + + protected SuppressionDescriptor Descriptor => SupportedSuppressions[0]; + + /// + public override ImmutableArray SupportedSuppressions { get; } + + /// + /// Override this factory method to influence the creation of . + /// Typically used by derived classes wanting to provide version overrides for specific + /// references. + /// + /// The Roslyn compilation context + protected virtual XunitContext CreateXunitContext(Compilation compilation) => + new(compilation); + + /// + public sealed override void ReportSuppressions(SuppressionAnalysisContext context) + { + var xunitContext = CreateXunitContext(context.Compilation); + + if (ShouldAnalyze(xunitContext)) + foreach (var diagnostic in context.ReportedDiagnostics) + if (ShouldSuppress(diagnostic, context, xunitContext)) + context.ReportSuppression(Suppression.Create(Descriptor, diagnostic)); + } + + /// + /// Override this method to influence when we should consider diagnostic analysis. By + /// default analyzes all assemblies that have a reference to xUnit.net v2 or v3. + /// + /// The xUnit.net context + /// Return true to analyze source; return false to skip analysis + protected virtual bool ShouldAnalyze(XunitContext xunitContext) => + Guard.ArgumentNotNull(xunitContext).HasV2References || xunitContext.HasV3References; + + /// + /// Analyzes the given diagnostic to determine if it should be suppressed. + /// + /// The Roslyn supression analysis context + /// The xUnit.net context + protected abstract bool ShouldSuppress( + Diagnostic diagnostic, + SuppressionAnalysisContext context, + XunitContext xunitContext); +} diff --git a/src/xunit.analyzers/Utility/XunitV2DiagnosticSuppressor.cs b/src/xunit.analyzers/Utility/XunitV2DiagnosticSuppressor.cs new file mode 100644 index 00000000..4ed60126 --- /dev/null +++ b/src/xunit.analyzers/Utility/XunitV2DiagnosticSuppressor.cs @@ -0,0 +1,16 @@ +using Microsoft.CodeAnalysis; + +namespace Xunit.Analyzers; + +/// +/// Base class for diagnostic suppressors which support xUnit.net v2 only. +/// +public abstract class XunitV2DiagnosticSuppressor : XunitDiagnosticSuppressor +{ + protected XunitV2DiagnosticSuppressor(SuppressionDescriptor descriptor) : + base(descriptor) + { } + + protected override bool ShouldAnalyze(XunitContext xunitContext) => + Guard.ArgumentNotNull(xunitContext).HasV2References; +} diff --git a/src/xunit.analyzers/Utility/XunitV3DiagnosticSuppressor.cs b/src/xunit.analyzers/Utility/XunitV3DiagnosticSuppressor.cs new file mode 100644 index 00000000..581b81ef --- /dev/null +++ b/src/xunit.analyzers/Utility/XunitV3DiagnosticSuppressor.cs @@ -0,0 +1,16 @@ +using Microsoft.CodeAnalysis; + +namespace Xunit.Analyzers; + +/// +/// Base class for diagnostic suppressors which support xUnit.net v3 only. +/// +public abstract class XunitV3DiagnosticSuppressor : XunitDiagnosticSuppressor +{ + protected XunitV3DiagnosticSuppressor(SuppressionDescriptor descriptor) : + base(descriptor) + { } + + protected override bool ShouldAnalyze(XunitContext xunitContext) => + Guard.ArgumentNotNull(xunitContext).HasV3References; +}