Skip to content
This repository has been archived by the owner on Nov 8, 2018. It is now read-only.

Support async Main methods #72

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,51 @@ class ClassName
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
}

[Theory]
[InlineData("static Task Main()")]
[InlineData("static Task<int> Main()")]
[InlineData("static Task Main(string[] args)")]
[InlineData("static Task Main(params string[] args)")]
[InlineData("static Task<int> Main(string[] args)")]
[InlineData("static Task<int> Main(params string[] args)")]
[InlineData("public static Task Main(string[] args)")]
[InlineData("public static Task<int> Main(params string[] args)")]
public async Task TestAsyncMainAsync(string signature)
{
string testCode = $@"
using System.Threading.Tasks;
class ClassName
{{
{signature} {{ throw null; }}
}}
";

await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
}

[Theory]
[InlineData(" Task Main()")]
[InlineData("static Task<uint> Main()")]
[InlineData("static Task Main(string[] args, CancellationToken cancellationToken)")]
[InlineData("static Task Main(string args)")]
public async Task TestAsyncMainNonMatchingSignatureAsync(string signature)
{
string testCode = $@"
using System.Threading;
using System.Threading.Tasks;
class ClassName
{{
{signature} {{ throw null; }}
}}
";
string fixedCode = testCode.Replace("Main", "MainAsync");

DiagnosticResult expected = this.CSharpDiagnostic().WithArguments("Main").WithLocation(6, 23);
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
await this.VerifyCSharpFixAsync(testCode, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
}

protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
{
yield return new UseAsyncSuffixAnalyzer();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace AsyncUsageAnalyzers.Helpers

internal static class MethodSymbolExtensions
{
public static bool HasAsyncSignature(this IMethodSymbol symbol, bool treatAsyncVoidAsAsync = false)
public static bool HasAsyncSignature(this IMethodSymbol symbol, bool treatAsyncVoidAsAsync = false, bool treatValueTaskAsAsync = true)
{
// void-returning methods are not asynchronous according to their signature, even if they use `async`
if (symbol.ReturnsVoid)
Expand All @@ -26,7 +26,7 @@ public static bool HasAsyncSignature(this IMethodSymbol symbol, bool treatAsyncV
{
// This check conveniently covers Task and Task<T> by ignoring the `1 in Task<T>.
if (!string.Equals(nameof(Task), symbol.ReturnType?.Name, StringComparison.Ordinal)
&& !string.Equals("ValueTask", symbol.ReturnType?.Name, StringComparison.Ordinal))
&& !(treatValueTaskAsAsync && string.Equals("ValueTask", symbol.ReturnType?.Name, StringComparison.Ordinal)))
{
return false;
}
Expand Down Expand Up @@ -92,5 +92,52 @@ public static bool IsOverrideOrImplementation(this IMethodSymbol symbol)

return false;
}

public static bool IsAsyncMain(this IMethodSymbol symbol)
{
// The following signatures are allowed:
//
// static Task Main()
// static Task<int> Main()
// static Task Main(string[])
// static Task<int> Main(string[])
if (!symbol.IsStatic)
{
return false;
}

if (!string.Equals(symbol.Name, "Main", StringComparison.Ordinal))
{
return false;
}

if (!symbol.HasAsyncSignature(treatAsyncVoidAsAsync: false, treatValueTaskAsAsync: false))
{
return false;
}

var returnType = (INamedTypeSymbol)symbol.ReturnType;
if (returnType.IsGenericType)
{
if (returnType.TypeArguments.Length != 1
|| returnType.TypeArguments[0].SpecialType != SpecialType.System_Int32)
{
return false;
}
}

switch (symbol.Parameters.Length)
{
case 0:
return true;

case 1:
return symbol.Parameters[0].Type is IArrayTypeSymbol arrayType
&& arrayType.ElementType.SpecialType == SpecialType.System_String;

default:
return false;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ private static void HandleMethodDeclaration(SymbolAnalysisContext context)
return;
}

if (symbol.IsAsyncMain())
{
return;
}

context.ReportDiagnostic(Diagnostic.Create(Descriptor, symbol.Locations[0], symbol.Name));
}
}
Expand Down
2 changes: 1 addition & 1 deletion AsyncUsageAnalyzers/Directory.build.props
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
</PropertyGroup>

<PropertyGroup>
<LangVersion>6</LangVersion>
<LangVersion>7</LangVersion>
<Features>strict</Features>
</PropertyGroup>

Expand Down