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

Commit 797fb0b

Browse files
committed
Support async Main methods
Fixes #66
1 parent 1b5a65d commit 797fb0b

File tree

3 files changed

+99
-2
lines changed

3 files changed

+99
-2
lines changed

AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Naming/UseAsyncSuffixUnitTests.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,51 @@ class ClassName
385385
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
386386
}
387387

388+
[Theory]
389+
[InlineData("static Task Main()")]
390+
[InlineData("static Task<int> Main()")]
391+
[InlineData("static Task Main(string[] args)")]
392+
[InlineData("static Task Main(params string[] args)")]
393+
[InlineData("static Task<int> Main(string[] args)")]
394+
[InlineData("static Task<int> Main(params string[] args)")]
395+
[InlineData("public static Task Main(string[] args)")]
396+
[InlineData("public static Task<int> Main(params string[] args)")]
397+
public async Task TestAsyncMainAsync(string signature)
398+
{
399+
string testCode = $@"
400+
using System.Threading.Tasks;
401+
class ClassName
402+
{{
403+
{signature} {{ throw null; }}
404+
}}
405+
";
406+
407+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
408+
}
409+
410+
[Theory]
411+
[InlineData(" Task Main()")]
412+
[InlineData("static Task<uint> Main()")]
413+
[InlineData("static Task Main(string[] args, CancellationToken cancellationToken)")]
414+
[InlineData("static Task Main(string args)")]
415+
public async Task TestAsyncMainNonMatchingSignatureAsync(string signature)
416+
{
417+
string testCode = $@"
418+
using System.Threading;
419+
using System.Threading.Tasks;
420+
class ClassName
421+
{{
422+
{signature} {{ throw null; }}
423+
}}
424+
";
425+
string fixedCode = testCode.Replace("Main", "MainAsync");
426+
427+
DiagnosticResult expected = this.CSharpDiagnostic().WithArguments("Main").WithLocation(6, 23);
428+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
429+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
430+
await this.VerifyCSharpFixAsync(testCode, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
431+
}
432+
388433
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
389434
{
390435
yield return new UseAsyncSuffixAnalyzer();

AsyncUsageAnalyzers/AsyncUsageAnalyzers/Helpers/MethodSymbolExtensions.cs

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace AsyncUsageAnalyzers.Helpers
99

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

9393
return false;
9494
}
95+
96+
public static bool IsAsyncMain(this IMethodSymbol symbol)
97+
{
98+
// The following signatures are allowed:
99+
//
100+
// static Task Main()
101+
// static Task<int> Main()
102+
// static Task Main(string[])
103+
// static Task<int> Main(string[])
104+
if (!symbol.IsStatic)
105+
{
106+
return false;
107+
}
108+
109+
if (!string.Equals(symbol.Name, "Main", StringComparison.Ordinal))
110+
{
111+
return false;
112+
}
113+
114+
if (!symbol.HasAsyncSignature(treatAsyncVoidAsAsync: false, treatValueTaskAsAsync: false))
115+
{
116+
return false;
117+
}
118+
119+
var returnType = (INamedTypeSymbol)symbol.ReturnType;
120+
if (returnType.IsGenericType)
121+
{
122+
if (returnType.TypeArguments.Length != 1
123+
|| returnType.TypeArguments[0].SpecialType != SpecialType.System_Int32)
124+
{
125+
return false;
126+
}
127+
}
128+
129+
switch (symbol.Parameters.Length)
130+
{
131+
case 0:
132+
return true;
133+
134+
case 1:
135+
return symbol.Parameters[0].Type is IArrayTypeSymbol arrayType
136+
&& arrayType.ElementType.SpecialType == SpecialType.System_String;
137+
138+
default:
139+
return false;
140+
}
141+
}
95142
}
96143
}

AsyncUsageAnalyzers/AsyncUsageAnalyzers/Naming/UseAsyncSuffixAnalyzer.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ private static void HandleMethodDeclaration(SymbolAnalysisContext context)
6767
return;
6868
}
6969

70+
if (symbol.IsAsyncMain())
71+
{
72+
return;
73+
}
74+
7075
context.ReportDiagnostic(Diagnostic.Create(Descriptor, symbol.Locations[0], symbol.Name));
7176
}
7277
}

0 commit comments

Comments
 (0)