-
Notifications
You must be signed in to change notification settings - Fork 71
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add analyzers to suggest (v2)/require (v3) not using 'async void' in …
…test method signature
- Loading branch information
1 parent
0d61dbf
commit 2397d14
Showing
6 changed files
with
368 additions
and
2 deletions.
There are no files selected for viewing
58 changes: 58 additions & 0 deletions
58
src/xunit.analyzers.fixes/X1000/DoNotUseAsyncVoidForTestMethodsFixer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
using System.Composition; | ||
using System.Threading.Tasks; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CodeActions; | ||
using Microsoft.CodeAnalysis.CodeFixes; | ||
|
||
namespace Xunit.Analyzers.Fixes; | ||
|
||
[ExportCodeFixProvider(LanguageNames.CSharp), Shared] | ||
public class DoNotUseAsyncVoidForTestMethodsFixer : BatchedMemberFixProvider | ||
{ | ||
public const string Key_ConvertToTask = "xUnit1048_xUnit1049_ConvertToTask"; | ||
public const string Key_ConvertToValueTask = "xUnit1049_ConvertToValueTask"; | ||
|
||
public DoNotUseAsyncVoidForTestMethodsFixer() : | ||
base( | ||
Descriptors.X1048_DoNotUseAsyncVoidForTestMethods_V2.Id, | ||
Descriptors.X1049_DoNotUseAsyncVoidForTestMethods_V3.Id | ||
) | ||
{ } | ||
|
||
public override async Task RegisterCodeFixesAsync( | ||
CodeFixContext context, | ||
ISymbol member) | ||
{ | ||
var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); | ||
if (semanticModel is null) | ||
return; | ||
|
||
var taskReturnType = TypeSymbolFactory.Task(semanticModel.Compilation); | ||
if (taskReturnType is null) | ||
return; | ||
|
||
var valueTaskReturnType = TypeSymbolFactory.ValueTask(semanticModel.Compilation); | ||
|
||
foreach (var diagnostic in context.Diagnostics) | ||
{ | ||
context.RegisterCodeFix( | ||
CodeAction.Create( | ||
"Change return type to Task", | ||
ct => context.Document.Project.Solution.ChangeMemberType(member, taskReturnType, ct), | ||
Key_ConvertToTask | ||
), | ||
diagnostic | ||
); | ||
|
||
if (valueTaskReturnType is not null && diagnostic.Id == Descriptors.X1049_DoNotUseAsyncVoidForTestMethods_V3.Id) | ||
context.RegisterCodeFix( | ||
CodeAction.Create( | ||
"Change return type to ValueTask", | ||
ct => context.Document.Project.Solution.ChangeMemberType(member, valueTaskReturnType, ct), | ||
Key_ConvertToValueTask | ||
), | ||
diagnostic | ||
); | ||
} | ||
} | ||
} |
83 changes: 83 additions & 0 deletions
83
src/xunit.analyzers.tests/Analyzers/X1000/DoNotUseAsyncVoidForTestMethodsTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
using System.Threading.Tasks; | ||
using Xunit; | ||
using Verify = CSharpVerifier<Xunit.Analyzers.DoNotUseAsyncVoidForTestMethods>; | ||
|
||
public class DoNotUseAsyncVoidForTestMethodsTests | ||
{ | ||
[Fact] | ||
public async Task NonTestMethod_DoesNotTrigger() | ||
{ | ||
var source = @" | ||
using System.Threading.Tasks; | ||
public class MyClass { | ||
public async void MyMethod() { | ||
await Task.Yield(); | ||
} | ||
}"; | ||
|
||
await Verify.VerifyAnalyzer(source); | ||
} | ||
|
||
[Fact] | ||
public async Task AsyncTaskMethod_DoesNotTrigger() | ||
{ | ||
var source = @" | ||
using System.Threading.Tasks; | ||
using Xunit; | ||
public class TestClass { | ||
[Fact] | ||
public async Task TestMethod() { | ||
await Task.Yield(); | ||
} | ||
}"; | ||
|
||
await Verify.VerifyAnalyzer(source); | ||
} | ||
|
||
[Fact] | ||
public async Task AsyncValueTaskMethod_V3_DoesNotTrigger() | ||
{ | ||
var source = @" | ||
using System.Threading.Tasks; | ||
using Xunit; | ||
public class TestClass { | ||
[Fact] | ||
public async ValueTask TestMethod() { | ||
await Task.Yield(); | ||
} | ||
}"; | ||
|
||
await Verify.VerifyAnalyzerV3(source); | ||
} | ||
|
||
|
||
[Fact] | ||
public async Task AsyncVoidMethod_Triggers() | ||
{ | ||
var source = @" | ||
using System.Threading.Tasks; | ||
using Xunit; | ||
public class TestClass { | ||
[Fact] | ||
public async void TestMethod() { | ||
await Task.Yield(); | ||
} | ||
}"; | ||
|
||
var expectedV2 = | ||
Verify | ||
.Diagnostic("xUnit1048") | ||
.WithSpan(7, 23, 7, 33); | ||
var expectedV3 = | ||
Verify | ||
.Diagnostic("xUnit1049") | ||
.WithSpan(7, 23, 7, 33); | ||
|
||
await Verify.VerifyAnalyzerV2(source, expectedV2); | ||
await Verify.VerifyAnalyzerV3(source, expectedV3); | ||
} | ||
} |
133 changes: 133 additions & 0 deletions
133
src/xunit.analyzers.tests/Fixes/X1000/DoNotUseAsyncVoidForTestMethodsFixerTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
using System.Threading.Tasks; | ||
using Xunit.Analyzers.Fixes; | ||
using Verify = CSharpVerifier<Xunit.Analyzers.DoNotUseAsyncVoidForTestMethods>; | ||
|
||
namespace Xunit.Analyzers; | ||
|
||
public class DoNotUseAsyncVoidForTestMethodsFixerTests | ||
{ | ||
[Fact] | ||
public async Task WithoutNamespace_ConvertsToTask() | ||
{ | ||
var beforeV2 = @" | ||
using Xunit; | ||
public class TestClass { | ||
[Fact] | ||
public async void {|xUnit1048:TestMethod|}() { | ||
await System.Threading.Tasks.Task.Yield(); | ||
} | ||
}"; | ||
var beforeV3 = @" | ||
using Xunit; | ||
public class TestClass { | ||
[Fact] | ||
public async void {|xUnit1049:TestMethod|}() { | ||
await System.Threading.Tasks.Task.Yield(); | ||
} | ||
}"; | ||
var after = @" | ||
using Xunit; | ||
public class TestClass { | ||
[Fact] | ||
public async System.Threading.Tasks.Task TestMethod() { | ||
await System.Threading.Tasks.Task.Yield(); | ||
} | ||
}"; | ||
|
||
await Verify.VerifyCodeFixV2(beforeV2, after, DoNotUseAsyncVoidForTestMethodsFixer.Key_ConvertToTask); | ||
await Verify.VerifyCodeFixV3(beforeV3, after, DoNotUseAsyncVoidForTestMethodsFixer.Key_ConvertToTask); | ||
} | ||
|
||
[Fact] | ||
public async Task WithNamespace_ConvertsToTask() | ||
{ | ||
var beforeV2 = @" | ||
using System.Threading.Tasks; | ||
using Xunit; | ||
public class TestClass { | ||
[Fact] | ||
public async void {|xUnit1048:TestMethod|}() { | ||
await Task.Yield(); | ||
} | ||
}"; | ||
var beforeV3 = @" | ||
using System.Threading.Tasks; | ||
using Xunit; | ||
public class TestClass { | ||
[Fact] | ||
public async void {|xUnit1049:TestMethod|}() { | ||
await Task.Yield(); | ||
} | ||
}"; | ||
var after = @" | ||
using System.Threading.Tasks; | ||
using Xunit; | ||
public class TestClass { | ||
[Fact] | ||
public async Task TestMethod() { | ||
await Task.Yield(); | ||
} | ||
}"; | ||
|
||
await Verify.VerifyCodeFixV2(beforeV2, after, DoNotUseAsyncVoidForTestMethodsFixer.Key_ConvertToTask); | ||
await Verify.VerifyCodeFixV3(beforeV3, after, DoNotUseAsyncVoidForTestMethodsFixer.Key_ConvertToTask); | ||
} | ||
|
||
[Fact] | ||
public async Task WithoutNamespace_ConvertsToValueTask() | ||
{ | ||
var before = @" | ||
using Xunit; | ||
public class TestClass { | ||
[Fact] | ||
public async void {|xUnit1049:TestMethod|}() { | ||
await System.Threading.Tasks.Task.Yield(); | ||
} | ||
}"; | ||
var after = @" | ||
using Xunit; | ||
public class TestClass { | ||
[Fact] | ||
public async System.Threading.Tasks.ValueTask TestMethod() { | ||
await System.Threading.Tasks.Task.Yield(); | ||
} | ||
}"; | ||
|
||
await Verify.VerifyCodeFixV3(before, after, DoNotUseAsyncVoidForTestMethodsFixer.Key_ConvertToValueTask); | ||
} | ||
|
||
[Fact] | ||
public async Task WithNamespace_ConvertsToValueTask() | ||
{ | ||
var before = @" | ||
using System.Threading.Tasks; | ||
using Xunit; | ||
public class TestClass { | ||
[Fact] | ||
public async void {|xUnit1049:TestMethod|}() { | ||
await Task.Yield(); | ||
} | ||
}"; | ||
var after = @" | ||
using System.Threading.Tasks; | ||
using Xunit; | ||
public class TestClass { | ||
[Fact] | ||
public async ValueTask TestMethod() { | ||
await Task.Yield(); | ||
} | ||
}"; | ||
|
||
await Verify.VerifyCodeFixV3(before, after, DoNotUseAsyncVoidForTestMethodsFixer.Key_ConvertToValueTask); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
55 changes: 55 additions & 0 deletions
55
src/xunit.analyzers/X1000/DoNotUseAsyncVoidForTestMethods.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
using System.Collections.Immutable; | ||
using System.Linq; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
|
||
namespace Xunit.Analyzers; | ||
|
||
[DiagnosticAnalyzer(LanguageNames.CSharp)] | ||
public class DoNotUseAsyncVoidForTestMethods : XunitDiagnosticAnalyzer | ||
{ | ||
public DoNotUseAsyncVoidForTestMethods() : | ||
base([ | ||
Descriptors.X1048_DoNotUseAsyncVoidForTestMethods_V2, | ||
Descriptors.X1049_DoNotUseAsyncVoidForTestMethods_V3, | ||
]) | ||
{ } | ||
|
||
public override void AnalyzeCompilation( | ||
CompilationStartAnalysisContext context, | ||
XunitContext xunitContext) | ||
{ | ||
Guard.ArgumentNotNull(context); | ||
Guard.ArgumentNotNull(xunitContext); | ||
|
||
var attributeUsageType = TypeSymbolFactory.AttributeUsageAttribute(context.Compilation); | ||
if (attributeUsageType is null) | ||
return; | ||
|
||
context.RegisterSymbolAction(context => | ||
{ | ||
if (context.Symbol is not IMethodSymbol method) | ||
return; | ||
|
||
if (!method.IsTestMethod(xunitContext, attributeUsageType, strict: true)) | ||
return; | ||
|
||
if (!method.ReturnsVoid) | ||
return; | ||
|
||
var location = context.Symbol.Locations.FirstOrDefault(); | ||
if (location is null) | ||
return; | ||
|
||
var propertiesBuilder = ImmutableDictionary.CreateBuilder<string, string?>(); | ||
propertiesBuilder.Add(Constants.Properties.DeclaringType, method.ContainingType.ToDisplayString()); | ||
propertiesBuilder.Add(Constants.Properties.MemberName, method.Name); | ||
var properties = propertiesBuilder.ToImmutableDictionary(); | ||
|
||
if (xunitContext.HasV3References) | ||
context.ReportDiagnostic(Diagnostic.Create(Descriptors.X1049_DoNotUseAsyncVoidForTestMethods_V3, location, properties)); | ||
else | ||
context.ReportDiagnostic(Diagnostic.Create(Descriptors.X1048_DoNotUseAsyncVoidForTestMethods_V2, location, properties)); | ||
}, SymbolKind.Method); | ||
} | ||
} |