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

Commit 9a4f3fa

Browse files
committed
Support async Main methods
Fixes #66
1 parent a6e2f51 commit 9a4f3fa

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
@@ -411,6 +411,51 @@ class ClassName
411411
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
412412
}
413413

414+
[Theory]
415+
[InlineData("static Task Main()")]
416+
[InlineData("static Task<int> Main()")]
417+
[InlineData("static Task Main(string[] args)")]
418+
[InlineData("static Task Main(params string[] args)")]
419+
[InlineData("static Task<int> Main(string[] args)")]
420+
[InlineData("static Task<int> Main(params string[] args)")]
421+
[InlineData("public static Task Main(string[] args)")]
422+
[InlineData("public static Task<int> Main(params string[] args)")]
423+
public async Task TestAsyncMainAsync(string signature)
424+
{
425+
string testCode = $@"
426+
using System.Threading.Tasks;
427+
class ClassName
428+
{{
429+
{signature} {{ throw null; }}
430+
}}
431+
";
432+
433+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
434+
}
435+
436+
[Theory]
437+
[InlineData(" Task Main()")]
438+
[InlineData("static Task<uint> Main()")]
439+
[InlineData("static Task Main(string[] args, CancellationToken cancellationToken)")]
440+
[InlineData("static Task Main(string args)")]
441+
public async Task TestAsyncMainNonMatchingSignatureAsync(string signature)
442+
{
443+
string testCode = $@"
444+
using System.Threading;
445+
using System.Threading.Tasks;
446+
class ClassName
447+
{{
448+
{signature} {{ throw null; }}
449+
}}
450+
";
451+
string fixedCode = testCode.Replace("Main", "MainAsync");
452+
453+
DiagnosticResult expected = this.CSharpDiagnostic().WithArguments("Main").WithLocation(6, 23);
454+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
455+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
456+
await this.VerifyCSharpFixAsync(testCode, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
457+
}
458+
414459
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
415460
{
416461
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)