Skip to content

Commit

Permalink
Async methods should not have a void return type
Browse files Browse the repository at this point in the history
  • Loading branch information
Vannevelj committed Feb 24, 2018
1 parent 10c7f7c commit 41bf011
Show file tree
Hide file tree
Showing 9 changed files with 428 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Currently these diagnostics are implemented:
| Arithmetic | VSD0045 | The operands of a divisive expression are both integers and result in an implicit rounding.
| Async | VSD0001 | Asynchronous methods should end with the -Async suffix.
| Async | VSD0041 | A non-`async`, non-`Task` method should not end with -Async.
| Async | VSD0064 | Async methods should return a `Task` to make them awaitable.
| Attributes | VSD0002 | An attribute should not have an empty argument list.
| Attributes | VSD0003 | Gives an enum the `[Flags]` attribute.
| Attributes | VSD0004 | A `[Flags]` enum its values are not explicit powers of 2
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using RoslynTester.Helpers.CSharp;
using VSDiagnostics.Diagnostics.Async.AsyncMethodWithVoidReturnType;

namespace VSDiagnostics.Test.Tests.Async
{
[TestClass]
public class AsyncMethodWithVoidReturnTypeTests : CSharpCodeFixVerifier
{
protected override DiagnosticAnalyzer DiagnosticAnalyzer => new AsyncMethodWithVoidReturnTypeAnalyzer();
protected override CodeFixProvider CodeFixProvider => new AsyncMethodWithVoidReturnTypeCodeFix();

[TestMethod]
public void AsyncMethodWithVoidReturnType_WithAsyncAndTask()
{
var original = @"
using System;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class MyClass
{
async Task MyMethod()
{
await Task.Run(() => { });
}
}
}";

VerifyDiagnostic(original);
}

[TestMethod]
public void AsyncMethodWithVoidReturnType_NoAsync()
{
var original = @"
using System;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class MyClass
{
void MyMethod()
{
}
}
}";

VerifyDiagnostic(original);
}

[TestMethod]
public void AsyncMethodWithVoidReturnType_WithAsyncAndTaskGeneric()
{
var original = @"
using System;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class MyClass
{
async Task<int> MyMethod()
{
return 32;
}
}
}";

VerifyDiagnostic(original);
}

[TestMethod]
public void AsyncMethodWithVoidReturnType_WithAsyncAndEventHandlerArguments()
{
var original = @"
using System;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class MyClass
{
async void MyHandler(object o, EventArgs e)
{
await Task.Run(() => { });
}
}
}";

VerifyDiagnostic(original);
}

[TestMethod]
public void AsyncMethodWithVoidReturnType_WithAsyncAndEventHandlerSubClassArguments()
{
var original = @"
using System;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class MyClass
{
async void MyHandler(object o, MyEventArgs e)
{
await Task.Run(() => { });
}
}
class MyEventArgs : EventArgs
{
}
}";

VerifyDiagnostic(original);
}

[TestMethod]
public void AsyncMethodWithVoidReturnType_WithAsyncDelegate()
{
var original = @"
using System;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class MyClass
{
public void MyMethod()
{
TestMethod(async () => await Task.Run(() => {}));
}
public void TestMethod(Action callback)
{
callback();
}
}
}";

VerifyDiagnostic(original);
}

[TestMethod]
public void AsyncMethodWithVoidReturnType_WithAsyncVoidAndArbitraryArguments()
{
var original = @"
using System;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class MyClass
{
async void MyHandler(object o, int e)
{
await Task.Run(() => { });
}
}
}";

var result = @"
using System;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class MyClass
{
async Task MyHandler(object o, int e)
{
await Task.Run(() => { });
}
}
}";

VerifyDiagnostic(original, "Method MyHandler is marked as async but has a void return type");
VerifyFix(original, result);
}

[TestMethod]
public void AsyncMethodWithVoidReturnType_WithAsyncAndVoid()
{
var original = @"
using System;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class MyClass
{
async void MyMethod()
{
await Task.Run(() => { });
}
}
}";

var result = @"
using System;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class MyClass
{
async Task MyMethod()
{
await Task.Run(() => { });
}
}
}";

VerifyDiagnostic(original, "Method MyMethod is marked as async but has a void return type");
VerifyFix(original, result);
}

[TestMethod]
public void AsyncMethodWithVoidReturnType_WithPartialMethod()
{
var original = @"
using System;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
partial class A
{
partial void OnSomethingHappened();
}
partial class A
{
async partial void OnSomethingHappened()
{
await Task.Run(() => { });
}
}
}";

VerifyDiagnostic(original);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
<ItemGroup>
<Compile Include="Tests\Arithmetic\DivideIntegerByIntegerTests.cs" />
<Compile Include="Tests\Async\AsyncMethodWithoutAsyncSuffixTests.cs" />
<Compile Include="Tests\Async\AsyncMethodWithVoidReturnTypeTests.cs" />
<Compile Include="Tests\Async\SyncMethodWithAsyncSuffixTests.cs" />
<Compile Include="Tests\Attributes\AttributeWithEmptyArgumentListTests.cs" />
<Compile Include="Tests\Attributes\EnumCanHaveFlagsAttributeTests.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using VSDiagnostics.Utilities;

namespace VSDiagnostics.Diagnostics.Async.AsyncMethodWithVoidReturnType
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class AsyncMethodWithVoidReturnTypeAnalyzer : DiagnosticAnalyzer
{
private const DiagnosticSeverity Severity = DiagnosticSeverity.Warning;

private static readonly string Category = VSDiagnosticsResources.AsyncCategory;
private static readonly string Message = VSDiagnosticsResources.AsyncMethodWithVoidReturnTypeMessage;
private static readonly string Title = VSDiagnosticsResources.AsyncMethodWithVoidReturnTypeTitle;

internal static DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId.AsyncMethodWithVoidReturnType, Title, Message, Category, Severity, isEnabledByDefault: true);

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);

public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeSyntaxNode, SyntaxKind.MethodDeclaration);

private void AnalyzeSyntaxNode(SyntaxNodeAnalysisContext context)
{
var method = (MethodDeclarationSyntax) context.Node;

// Method has to return void
var returnType = context.SemanticModel.GetTypeInfo(method.ReturnType);
if (returnType.Type == null || returnType.Type.SpecialType != SpecialType.System_Void)
{
return;
}

var isAsync = false;
foreach (var modifier in method.Modifiers)
{
// Method has to be marked async
if (modifier.IsKind(SyntaxKind.AsyncKeyword))
{
isAsync = true;
}

// Partial methods can only have a void return type
if (modifier.IsKind(SyntaxKind.PartialKeyword))
{
return;
}
}

if (!isAsync)
{
return;
}

// Event handlers can only have a void return type
if (method.ParameterList?.Parameters.Count == 2)
{
var parameters = method.ParameterList.Parameters;
var firstArgumentType = context.SemanticModel.GetTypeInfo(parameters[0].Type);
var isFirstArgumentObject = firstArgumentType.Type != null &&
firstArgumentType.Type.SpecialType == SpecialType.System_Object;


var secondArgumentType = context.SemanticModel.GetTypeInfo(parameters[1].Type);
var isSecondArgumentEventArgs = secondArgumentType.Type != null &&
secondArgumentType.Type.InheritsFrom(typeof(EventArgs));

if (isFirstArgumentObject && isSecondArgumentEventArgs)
{
return;
}
}

context.ReportDiagnostic(Diagnostic.Create(Rule, method.GetLocation(), method.Identifier.ValueText));
}
}
}
Loading

0 comments on commit 41bf011

Please sign in to comment.