Skip to content

Commit

Permalink
xunit/xunit#2978: Add a diagnostic suppressor for VSTHRD200
Browse files Browse the repository at this point in the history
  • Loading branch information
bradwilson committed Jul 22, 2024
1 parent b404e78 commit 4f2297b
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#if NETCOREAPP // System.Collections.Immutable 1.6.0 conflicts with 6.0.0 in NetFx

using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Testing;
using Xunit;
using Xunit.Analyzers;
using Verify = CSharpVerifier<Xunit.Suppressors.UseAsyncSuffixForAsyncMethodsSuppressor>;

public class UseAsyncSuffixForAsyncMethodsSuppressorTests
{
[Fact]
public async Task AcceptanceTest()
{
var code = /* lang=c#-test */ """
using System.Threading.Tasks;
using Xunit;

public class NonTestClass {
public async Task {|#0:NonTestMethod|}() { }
}

public class TestClass {
[Fact]
public async Task {|#1:TestMethod|]() { }
}
""";
var expected = new[]
{
new DiagnosticResult("VSTHRD200", DiagnosticSeverity.Warning).WithLocation(0),
new DiagnosticResult("VSTHRD200", DiagnosticSeverity.Warning).WithLocation(1).WithIsSuppressed(true),
};

await Verify.VerifySuppressor(code, VsThreadingAnalyzers.VSTHRD200(), expected);
}
}

#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#if NETCOREAPP // System.Collections.Immutable 1.6.0 conflicts with 6.0.0 in NetFx

using System;
using System.IO;
using System.Reflection;
using Microsoft.CodeAnalysis.Diagnostics;

namespace Xunit.Analyzers;

public class VsThreadingAnalyzers : AnalyzerLoaderBase
{
static readonly Lazy<Assembly> assemblyVsThreading = new(LoadVsThreadingAnalyzers, isThreadSafe: true);
static readonly Lazy<Type> typeVSTHRD200 = new(() => FindType(assemblyVsThreading, "Microsoft.VisualStudio.Threading.Analyzers.VSTHRD200UseAsyncNamingConventionAnalyzer"), isThreadSafe: true);

public static DiagnosticAnalyzer VSTHRD200() =>
Activator.CreateInstance(typeVSTHRD200.Value) as DiagnosticAnalyzer ?? throw new InvalidOperationException($"Could not create instance of '{typeVSTHRD200.Value.FullName}'");

static Assembly LoadVsThreadingAnalyzers()
{
LoadAssembly(Path.Combine(NuGetPackagesFolder, "system.collections.immutable", "6.0.0", "lib", "net6.0", "System.Collections.Immutable.dll"));
LoadAssembly(Path.Combine(NuGetPackagesFolder, "microsoft.codeanalysis.workspaces.common", "3.11.0", "lib", "netcoreapp3.1", "Microsoft.CodeAnalysis.Workspaces.dll"));
return LoadAssembly(Path.Combine(NuGetPackagesFolder, "microsoft.visualstudio.threading.analyzers", "17.10.48", "analyzers", "cs", "Microsoft.VisualStudio.Threading.Analyzers.dll"));
}
}

#endif
4 changes: 4 additions & 0 deletions src/xunit.analyzers.tests/xunit.analyzers.tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
<!-- Download packages referenced by CodeAnalysisNetAnalyzers -->
<PackageDownload Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="[9.0.0-preview.24216.2]" />
<PackageDownload Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="[3.11.0]" />

<!-- Download packages referenced by VsThreadingAnalyzers -->
<PackageDownload Include="Microsoft.VisualStudio.Threading.Analyzers" Version="[17.10.48]" />
<PackageDownload Include="System.Collections.Immutable" Version="[6.0.0]" Condition=" '$(TargetFramework)' == 'net8.0' " />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Xunit.Analyzers;

namespace Xunit.Suppressors;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class UseAsyncSuffixForAsyncMethodsSuppressor : XunitDiagnosticSuppressor
{
public UseAsyncSuffixForAsyncMethodsSuppressor() :
base(Descriptors.VSTHRD200_Suppression)
{ }

protected override bool ShouldSuppress(
Diagnostic diagnostic,
SuppressionAnalysisContext context,
XunitContext xunitContext)
{
var attributeUsageType = TypeSymbolFactory.AttributeUsageAttribute(context.Compilation);
if (attributeUsageType is null)
return false;

if (diagnostic?.Location.SourceTree is null)
return false;

if (diagnostic.Location.SourceTree.GetRoot().FindNode(diagnostic.Location.SourceSpan) is not MethodDeclarationSyntax methodDeclaration)
return false;

var semanticModel = context.GetSemanticModel(diagnostic.Location.SourceTree);
var methodSymbol = semanticModel.GetDeclaredSymbol(methodDeclaration) as IMethodSymbol;
return methodSymbol.IsTestMethod(xunitContext, attributeUsageType, strict: false);
}
}
7 changes: 4 additions & 3 deletions src/xunit.analyzers/Utility/CodeAnalysisExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Runtime.CompilerServices;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Operations;
Expand Down Expand Up @@ -167,14 +166,16 @@ static bool IsTestClassStrict(
}

public static bool IsTestMethod(
this IMethodSymbol method,
this IMethodSymbol? method,
XunitContext xunitContext,
ITypeSymbol attributeUsageType,
bool strict)
{
Guard.ArgumentNotNull(method);
Guard.ArgumentNotNull(xunitContext);

if (method is null)
return false;

var factAttributeType = xunitContext.Core.FactAttributeType;
var theoryAttributeType = xunitContext.Core.TheoryAttributeType;
if (factAttributeType is null || theoryAttributeType is null)
Expand Down
3 changes: 3 additions & 0 deletions src/xunit.analyzers/Utility/Descriptors.Suppressors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@ public static partial class Descriptors

public static SuppressionDescriptor CA2007_Suppression { get; } =
Suppression("CA2007", "xUnit.net test methods should not call ConfigureAwait");

public static SuppressionDescriptor VSTHRD200_Suppression { get; } =
Suppression("VSTHRD200", "xUnit.net test methods are not directly callable and do not benefit from this naming rule");
}

0 comments on commit 4f2297b

Please sign in to comment.