diff --git a/AsyncUsageAnalyzers.sln b/AsyncUsageAnalyzers.sln
index 538f460..621b362 100644
--- a/AsyncUsageAnalyzers.sln
+++ b/AsyncUsageAnalyzers.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
-VisualStudioVersion = 14.0.24720.0
+VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsyncUsageAnalyzers", "AsyncUsageAnalyzers\AsyncUsageAnalyzers\AsyncUsageAnalyzers.csproj", "{4E32037D-3EE0-419A-BC68-7D28FCD453A0}"
EndProject
@@ -32,6 +32,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "documentation", "documentat
ProjectSection(SolutionItems) = preProject
documentation\AvoidAsyncSuffix.md = documentation\AvoidAsyncSuffix.md
documentation\AvoidAsyncVoid.md = documentation\AvoidAsyncVoid.md
+ documentation\DontUseThreadSleep.md = documentation\DontUseThreadSleep.md
+ documentation\DontUseThreadSleepInAsyncCode.md = documentation\DontUseThreadSleepInAsyncCode.md
documentation\UseAsyncSuffix.md = documentation\UseAsyncSuffix.md
documentation\UseConfigureAwait.md = documentation\UseConfigureAwait.md
EndProjectSection
diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/AsyncUsageAnalyzers.CodeFixes.csproj b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/AsyncUsageAnalyzers.CodeFixes.csproj
index 77f08d3..08b628d 100644
--- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/AsyncUsageAnalyzers.CodeFixes.csproj
+++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/AsyncUsageAnalyzers.CodeFixes.csproj
@@ -51,6 +51,7 @@
+
@@ -80,24 +81,24 @@
-
- ..\..\packages\Microsoft.CodeAnalysis.Common.1.0.0\lib\portable-net45+win8\Microsoft.CodeAnalysis.dll
+
+ ..\..\packages\Microsoft.CodeAnalysis.Common.1.3.2\lib\portable-net45+win8\Microsoft.CodeAnalysis.dllTrue
-
- ..\..\packages\Microsoft.CodeAnalysis.CSharp.1.0.0\lib\portable-net45+win8\Microsoft.CodeAnalysis.CSharp.dll
+
+ ..\..\packages\Microsoft.CodeAnalysis.CSharp.1.3.2\lib\portable-net45+win8\Microsoft.CodeAnalysis.CSharp.dllTrue
-
- ..\..\packages\Microsoft.CodeAnalysis.CSharp.Workspaces.1.0.0\lib\portable-net45+win8\Microsoft.CodeAnalysis.CSharp.Workspaces.dll
+
+ ..\..\packages\Microsoft.CodeAnalysis.CSharp.Workspaces.1.3.2\lib\portable-net45+win8\Microsoft.CodeAnalysis.CSharp.Workspaces.dllTrue
-
- ..\..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.0.0\lib\portable-net45+win8\Microsoft.CodeAnalysis.Workspaces.dll
+
+ ..\..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.3.2\lib\portable-net45+win8\Microsoft.CodeAnalysis.Workspaces.dllTrue
-
- ..\..\packages\System.Collections.Immutable.1.1.36\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll
+
+ ..\..\packages\System.Collections.Immutable.1.1.37\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dllTrue
@@ -120,15 +121,15 @@
..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.TypedParts.dllFalse
-
- ..\..\packages\System.Reflection.Metadata.1.0.21\lib\portable-net45+win8\System.Reflection.Metadata.dll
+
+ ..\..\packages\System.Reflection.Metadata.1.2.0\lib\portable-net45+win8\System.Reflection.Metadata.dllTrue
-
-
+
+
diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs
new file mode 100644
index 0000000..d0c3b31
--- /dev/null
+++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs
@@ -0,0 +1,160 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+namespace AsyncUsageAnalyzers.Usage
+{
+ using System.Collections.Immutable;
+ using System.Composition;
+ using System.Linq;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using Helpers;
+ using Microsoft.CodeAnalysis;
+ using Microsoft.CodeAnalysis.CodeActions;
+ using Microsoft.CodeAnalysis.CodeFixes;
+ using Microsoft.CodeAnalysis.CSharp;
+ using Microsoft.CodeAnalysis.CSharp.Syntax;
+ using Microsoft.CodeAnalysis.Text;
+
+ [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(DontUseThreadSleepCodeUniversalCodeFixProvider))]
+ [Shared]
+ internal class DontUseThreadSleepCodeUniversalCodeFixProvider : CodeFixProvider
+ {
+ private static readonly ImmutableArray FixableDiagnostics =
+ ImmutableArray.Create(DontUseThreadSleepAnalyzer.DiagnosticId, DontUseThreadSleepInAsyncCodeAnalyzer.DiagnosticId);
+
+ public override ImmutableArray FixableDiagnosticIds => FixableDiagnostics;
+
+ ///
+ public override FixAllProvider GetFixAllProvider()
+ {
+ return CustomFixAllProviders.BatchFixer;
+ }
+
+ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
+ {
+ foreach (var diagnostic in context.Diagnostics)
+ {
+ if (diagnostic.Id == DontUseThreadSleepInAsyncCodeAnalyzer.DiagnosticId)
+ {
+ RegisterCodeFixForDiagnostic(context, diagnostic);
+ }
+ else if (diagnostic.Id == DontUseThreadSleepAnalyzer.DiagnosticId)
+ {
+ var document = context.Document;
+
+ var root = await document.GetSyntaxRootAsync().ConfigureAwait(false);
+ var invocationExpression = root.FindNode(TextSpan.FromBounds(diagnostic.Location.SourceSpan.Start, diagnostic.Location.SourceSpan.End), getInnermostNodeForTie: true) as InvocationExpressionSyntax;
+
+ if (invocationExpression == null)
+ {
+ return;
+ }
+
+ if (invocationExpression.IsInsideAsyncCode())
+ {
+ RegisterCodeFixForDiagnostic(context, diagnostic);
+ }
+ }
+ }
+ }
+
+ private static void RegisterCodeFixForDiagnostic(CodeFixContext context, Diagnostic diagnostic)
+ {
+ context.RegisterCodeFix(
+ CodeAction.Create(
+ "Use `await Task.Delay(...)` or `await Task.Yield()`",
+ cancellationToken => GetTransformedDocumentAsync(context.Document, diagnostic, cancellationToken)),
+ diagnostic);
+ }
+
+ private static async Task GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
+ {
+ var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
+ var firstNodeWithCorrectSpan = root
+ .FindNode(diagnostic.Location.SourceSpan);
+ InvocationExpressionSyntax expression = firstNodeWithCorrectSpan
+ .DescendantNodesAndSelf()
+ .OfType()
+ .First();
+
+ var arguments = expression.ArgumentList;
+
+ var newExpression = await IsArgumentListWithZeroValueAsync(document, arguments).ConfigureAwait(true)
+ ? GenerateTaskYieldExpression()
+ : GenerateTaskDelayExpression(arguments);
+
+ SyntaxNode newRoot = root.ReplaceNode(expression, newExpression.WithTriviaFrom(expression));
+ var newDocument = document.WithSyntaxRoot(newRoot);
+ return newDocument;
+ }
+
+ private static async Task IsArgumentListWithZeroValueAsync(Document document, ArgumentListSyntax argumentListSyntax)
+ {
+ // all valid overloads of Thread.Sleep() method take exactly one argument
+ if (argumentListSyntax.Arguments.Count != 1)
+ {
+ return false;
+ }
+
+ var argumentExpression = argumentListSyntax.Arguments.First().Expression;
+
+ var argumentString = argumentExpression.ToString().Trim();
+ if (argumentString == "0" || argumentString == "TimeSpan.Zero")
+ {
+ return true;
+ }
+
+ var semanticModel = await document.GetSemanticModelAsync().ConfigureAwait(false);
+ var optionalValue = semanticModel.GetConstantValue(argumentExpression);
+ if (optionalValue.HasValue && optionalValue.Value.Equals(0))
+ {
+ return true;
+ }
+
+ var memberAccessExpression = argumentExpression as MemberAccessExpressionSyntax;
+ if (memberAccessExpression != null)
+ {
+ IFieldSymbol propertySymbol = null;
+ return memberAccessExpression.TryGetFieldSymbolByTypeNameAndMethodName(semanticModel, "System.TimeSpan", "Zero", out propertySymbol);
+ }
+
+ return false;
+ }
+
+ private static AwaitExpressionSyntax GenerateTaskDelayExpression(ArgumentListSyntax methodArgumentList) =>
+ SyntaxFactory.AwaitExpression(
+ SyntaxFactory.InvocationExpression(
+ SyntaxFactory.MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ SyntaxFactory.MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ SyntaxFactory.MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ SyntaxFactory.MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ SyntaxFactory.IdentifierName("System"),
+ SyntaxFactory.IdentifierName("Threading")),
+ SyntaxFactory.IdentifierName("Tasks")),
+ SyntaxFactory.IdentifierName("Task")),
+ SyntaxFactory.IdentifierName("Delay")))
+ .WithArgumentList(methodArgumentList));
+
+ private static AwaitExpressionSyntax GenerateTaskYieldExpression() =>
+ SyntaxFactory.AwaitExpression(
+ SyntaxFactory.InvocationExpression(
+ SyntaxFactory.MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ SyntaxFactory.MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ SyntaxFactory.MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ SyntaxFactory.MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ SyntaxFactory.IdentifierName("System"),
+ SyntaxFactory.IdentifierName("Threading")),
+ SyntaxFactory.IdentifierName("Tasks")),
+ SyntaxFactory.IdentifierName("Task")),
+ SyntaxFactory.IdentifierName("Yield"))));
+ }
+}
diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/packages.config b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/packages.config
index 8063a18..27471dc 100644
--- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/packages.config
+++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/packages.config
@@ -1,15 +1,15 @@
-
-
-
-
-
+
+
+
+
+
-
-
+
+
\ No newline at end of file
diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/AsyncUsageAnalyzers.Test.csproj b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/AsyncUsageAnalyzers.Test.csproj
index 8fce4f7..b79dc89 100644
--- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/AsyncUsageAnalyzers.Test.csproj
+++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/AsyncUsageAnalyzers.Test.csproj
@@ -44,29 +44,29 @@
..\..\build\keys\TestingKey.snk
-
- ..\..\packages\Microsoft.CodeAnalysis.Common.1.0.0\lib\net45\Microsoft.CodeAnalysis.dll
+
+ ..\..\packages\Microsoft.CodeAnalysis.Common.1.3.2\lib\net45\Microsoft.CodeAnalysis.dllTrue
-
- ..\..\packages\Microsoft.CodeAnalysis.CSharp.1.0.0\lib\net45\Microsoft.CodeAnalysis.CSharp.dll
+
+ ..\..\packages\Microsoft.CodeAnalysis.CSharp.1.3.2\lib\net45\Microsoft.CodeAnalysis.CSharp.dllTrue
-
- ..\..\packages\Microsoft.CodeAnalysis.CSharp.Workspaces.1.0.0\lib\net45\Microsoft.CodeAnalysis.CSharp.Workspaces.dll
+
+ ..\..\packages\Microsoft.CodeAnalysis.CSharp.Workspaces.1.3.2\lib\net45\Microsoft.CodeAnalysis.CSharp.Workspaces.dllTrue
-
- ..\..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.0.0\lib\net45\Microsoft.CodeAnalysis.Workspaces.dll
+
+ ..\..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.3.2\lib\net45\Microsoft.CodeAnalysis.Workspaces.dllTrue
-
- ..\..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.0.0\lib\net45\Microsoft.CodeAnalysis.Workspaces.Desktop.dll
+
+ ..\..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.3.2\lib\net45\Microsoft.CodeAnalysis.Workspaces.Desktop.dllTrue
-
- ..\..\packages\System.Collections.Immutable.1.1.36\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll
+
+ ..\..\packages\System.Collections.Immutable.1.1.37\lib\dotnet\System.Collections.Immutable.dllTrue
@@ -90,8 +90,8 @@
True
-
- ..\..\packages\System.Reflection.Metadata.1.0.21\lib\portable-net45+win8\System.Reflection.Metadata.dll
+
+ ..\..\packages\System.Reflection.Metadata.1.2.0\lib\portable-net45+win8\System.Reflection.Metadata.dllTrue
@@ -126,6 +126,9 @@
+
+
+
@@ -155,8 +158,8 @@
-
-
+
+
diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepInAsyncCodeTests.cs b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepInAsyncCodeTests.cs
new file mode 100644
index 0000000..65099b4
--- /dev/null
+++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepInAsyncCodeTests.cs
@@ -0,0 +1,57 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+namespace AsyncUsageAnalyzers.Test.Usage
+{
+ using System.Collections.Generic;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using AsyncUsageAnalyzers.Usage;
+ using Microsoft.CodeAnalysis.Diagnostics;
+ using TestHelper;
+ using Xunit;
+
+ public class DontUseThreadSleepInAsyncCodeTests : DontUseThreadSleepTestsBase
+ {
+ protected override DiagnosticResult OptionallyAddArgumentsToDiagnostic(DiagnosticResult diagnostic, params object[] arguments) =>
+ diagnostic.WithArguments(arguments);
+
+ [Fact]
+ public async Task TestThreadSleepInNonAsyncCodeAsync()
+ {
+ string testCode = @"
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+class ClassA
+{
+ public delegate void SampleDelegate();
+ SampleDelegate AnonymousMethod = delegate ()
+ {
+ Thread.Sleep(0);
+ };
+
+ Func testFunc = (x) =>
+ {
+ Thread.Sleep(0);
+ return x;
+ };
+
+ public void NonAsyncMethod()
+ {
+ Thread.Sleep(1000);
+ System.Threading.Thread.Sleep(1000);
+ global::System.Threading.Thread.Sleep(1000);
+ }
+}";
+
+ await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ protected override IEnumerable GetCSharpDiagnosticAnalyzers()
+ {
+ yield return new DontUseThreadSleepInAsyncCodeAnalyzer();
+ }
+ }
+}
diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepTests.cs b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepTests.cs
new file mode 100644
index 0000000..8e8d659
--- /dev/null
+++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepTests.cs
@@ -0,0 +1,107 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+namespace AsyncUsageAnalyzers.Test.Usage
+{
+ using System.Collections.Generic;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using AsyncUsageAnalyzers.Usage;
+ using Microsoft.CodeAnalysis.Diagnostics;
+ using TestHelper;
+ using Xunit;
+
+ public class DontUseThreadSleepTests : DontUseThreadSleepTestsBase
+ {
+ protected override DiagnosticResult OptionallyAddArgumentsToDiagnostic(DiagnosticResult diagnostic, params object[] arguments) =>
+ diagnostic;
+
+ [Fact]
+ public async Task TestThreadSleepInMethodAsync()
+ {
+ var testCode = @"
+using System.Threading.Tasks;
+using System.Threading;
+
+class ClassA
+{
+ public void NonAsyncMethod()
+ {
+ Thread.Sleep(1000);
+ System.Threading.Thread.Sleep(1000);
+ global::System.Threading.Thread.Sleep(1000);
+ }
+}";
+ var expected = new[]
+ {
+ this.CSharpDiagnostic().WithLocation(9, 9),
+ this.CSharpDiagnostic().WithLocation(10, 9),
+ this.CSharpDiagnostic().WithLocation(11, 9)
+ };
+
+ await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
+ await this.VerifyCSharpFixAllFixAsync(
+ testCode,
+ testCode /* source code should not be changed as there's no automatic code fix */,
+ cancellationToken: CancellationToken.None)
+ .ConfigureAwait(false);
+ }
+
+ [Fact]
+ public async Task TestThreadSleepInAnonymousFunctionAsync()
+ {
+ var testCode = @"
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+class ClassA
+{
+ Func testFunc = (x) =>
+ {
+ Thread.Sleep(0);
+ return x;
+ };
+}";
+ var expected = this.CSharpDiagnostic().WithLocation(10, 9);
+
+ await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
+ await this.VerifyCSharpFixAllFixAsync(
+ testCode,
+ testCode /* source code should not be changed as there's no automatic code fix */,
+ cancellationToken: CancellationToken.None)
+ .ConfigureAwait(false);
+ }
+
+ [Fact]
+ public async Task TestThreadSleepInAnonymousMethodAsync()
+ {
+ var testCode = @"
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+class ClassA
+{
+ public delegate void SampleDelegate();
+ SampleDelegate AnonymousMethod = delegate ()
+ {
+ Thread.Sleep(0);
+ };
+}";
+ var expected = this.CSharpDiagnostic().WithLocation(11, 9);
+
+ await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
+ await this.VerifyCSharpFixAllFixAsync(
+ testCode,
+ testCode /* source code should not be changed as there's no automatic code fix */,
+ cancellationToken: CancellationToken.None)
+ .ConfigureAwait(false);
+ }
+
+ protected override IEnumerable GetCSharpDiagnosticAnalyzers()
+ {
+ yield return new DontUseThreadSleepAnalyzer();
+ }
+ }
+}
diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepTestsBase.cs b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepTestsBase.cs
new file mode 100644
index 0000000..5147cd3
--- /dev/null
+++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepTestsBase.cs
@@ -0,0 +1,377 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+namespace AsyncUsageAnalyzers.Test.Usage
+{
+ using System.Linq;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using AsyncUsageAnalyzers.Usage;
+ using Microsoft.CodeAnalysis.CodeFixes;
+ using TestHelper;
+ using Xunit;
+
+ public abstract class DontUseThreadSleepTestsBase : CodeFixVerifier
+ {
+ ///
+ /// Returns a new diagnostic with updated arguments or leaves a diagnostic intact.
+ ///
+ /// a diagnostic to be modified
+ /// arguments which can be used to update diagnostic
+ /// An appropriately modified diagnostic or unchanged diagnostic
+ protected abstract DiagnosticResult OptionallyAddArgumentsToDiagnostic(DiagnosticResult diagnostic, params object[] arguments);
+
+ [Fact]
+ public async Task TestThreadSleepInAsyncMethodAsync()
+ {
+ var testCode = @"
+using System.Threading;
+using System.Threading.Tasks;
+using static System.Threading.Thread;
+
+class ClassA
+{
+ public async Task MethodAsync()
+ {
+ Sleep(1);
+ Thread.Sleep(2);
+ System.Threading.Thread.Sleep(3);
+ global::System.Threading.Thread.Sleep(4);
+
+ return await Task.FromResult(0);
+ }
+}";
+ var fixedCode = @"
+using System.Threading;
+using System.Threading.Tasks;
+using static System.Threading.Thread;
+
+class ClassA
+{
+ public async Task MethodAsync()
+ {
+ await System.Threading.Tasks.Task.Delay(1);
+ await System.Threading.Tasks.Task.Delay(2);
+ await System.Threading.Tasks.Task.Delay(3);
+ await System.Threading.Tasks.Task.Delay(4);
+
+ return await Task.FromResult(0);
+ }
+}";
+ var expectedResults = new[]
+ {
+ this.CSharpDiagnostic().WithLocation(10, 9),
+ this.CSharpDiagnostic().WithLocation(11, 9),
+ this.CSharpDiagnostic().WithLocation(12, 9),
+ this.CSharpDiagnostic().WithLocation(13, 9)
+ }
+ .Select(diag => this.OptionallyAddArgumentsToDiagnostic(diag, string.Format(UsageResources.MethodFormat, "MethodAsync")))
+ .ToArray();
+
+ await this.VerifyCSharpDiagnosticAsync(testCode, expectedResults, CancellationToken.None).ConfigureAwait(false);
+ await this.VerifyCSharpFixAllFixAsync(
+ testCode,
+ fixedCode,
+ cancellationToken: CancellationToken.None,
+ allowNewCompilerDiagnostics: true /* expected new diagnostic is "hidden CS8019: Unnecessary using directive." */)
+ .ConfigureAwait(false);
+ }
+
+ [Theory]
+ [InlineData("0")]
+ [InlineData("0 /* some inline comment */")]
+ [InlineData("(int)0L")]
+ [InlineData("1-1")]
+ [InlineData("TimeSpan.Zero")]
+ [InlineData("System.TimeSpan.Zero")]
+ public async Task TestThreadSleepZeroInAsyncMethodAsync(string zeroParams)
+ {
+ var testCode = $@"
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using static System.Threading.Thread;
+
+class ClassA
+{{
+ public async Task MethodAsync()
+ {{
+ Sleep({zeroParams});
+ Thread.Sleep({zeroParams});
+ System.Threading.Thread.Sleep({zeroParams});
+ global::System.Threading.Thread.Sleep({zeroParams});
+
+ return await Task.FromResult(0);
+ }}
+}}";
+ var fixedCode = @"
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using static System.Threading.Thread;
+
+class ClassA
+{
+ public async Task MethodAsync()
+ {
+ await System.Threading.Tasks.Task.Yield();
+ await System.Threading.Tasks.Task.Yield();
+ await System.Threading.Tasks.Task.Yield();
+ await System.Threading.Tasks.Task.Yield();
+
+ return await Task.FromResult(0);
+ }
+}";
+ var expectedResults = new[]
+ {
+ this.CSharpDiagnostic().WithLocation(11, 9),
+ this.CSharpDiagnostic().WithLocation(12, 9),
+ this.CSharpDiagnostic().WithLocation(13, 9),
+ this.CSharpDiagnostic().WithLocation(14, 9)
+ }
+ .Select(diag => this.OptionallyAddArgumentsToDiagnostic(diag, string.Format(UsageResources.MethodFormat, "MethodAsync")))
+ .ToArray();
+
+ await this.VerifyCSharpDiagnosticAsync(testCode, expectedResults, CancellationToken.None).ConfigureAwait(false);
+ await this.VerifyCSharpFixAllFixAsync(
+ testCode,
+ fixedCode,
+ cancellationToken: CancellationToken.None,
+ allowNewCompilerDiagnostics: true /* expected new diagnostic is "hidden CS8019: Unnecessary using directive." */)
+ .ConfigureAwait(false);
+ }
+
+ [Fact]
+ public async Task TestThreadSleepInAsyncAnonymousFunctionAsync()
+ {
+ var testCode = @"
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+class ClassA
+{
+ public void MethodA()
+ {
+ Func testFunc = async () =>
+ {
+ Thread.Sleep(1);
+ await Task.FromResult(0);
+ };
+ }
+}";
+
+ var fixedCode = @"
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+class ClassA
+{
+ public void MethodA()
+ {
+ Func testFunc = async () =>
+ {
+ await System.Threading.Tasks.Task.Delay(1);
+ await Task.FromResult(0);
+ };
+ }
+}";
+ var expected = this.OptionallyAddArgumentsToDiagnostic(this.CSharpDiagnostic().WithLocation(12, 13), UsageResources.AsyncAnonymousFunctionsAndMethods);
+
+ await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
+ await this.VerifyCSharpFixAllFixAsync(
+ testCode,
+ fixedCode,
+ cancellationToken: CancellationToken.None,
+ allowNewCompilerDiagnostics: true /* expected new diagnostic is "hidden CS8019: Unnecessary using directive." */)
+ .ConfigureAwait(false);
+ }
+
+ [Theory]
+ [InlineData("0")]
+ [InlineData("0 /* some inline comment */")]
+ [InlineData("(int)0L")]
+ [InlineData("1-1")]
+ [InlineData("TimeSpan.Zero")]
+ [InlineData("System.TimeSpan.Zero")]
+ public async Task TestThreadSleepZeroInAsyncAnonymousFunctionAsync(string zeroParams)
+ {
+ var testCode = $@"
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+class ClassA
+{{
+ public void MethodA()
+ {{
+ Func testFunc = async () =>
+ {{
+ Thread.Sleep({zeroParams});
+ await Task.FromResult(0);
+ }};
+ }}
+}}";
+
+ var fixedCode = @"
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+class ClassA
+{
+ public void MethodA()
+ {
+ Func testFunc = async () =>
+ {
+ await System.Threading.Tasks.Task.Yield();
+ await Task.FromResult(0);
+ };
+ }
+}";
+ var expected = this.OptionallyAddArgumentsToDiagnostic(this.CSharpDiagnostic().WithLocation(12, 13), UsageResources.AsyncAnonymousFunctionsAndMethods);
+
+ await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
+ await this.VerifyCSharpFixAllFixAsync(
+ testCode,
+ fixedCode,
+ cancellationToken: CancellationToken.None,
+ allowNewCompilerDiagnostics: true /* expected new diagnostic is "hidden CS8019: Unnecessary using directive." */)
+ .ConfigureAwait(false);
+ }
+
+ [Fact]
+ public async Task TestThreadSleepInAsyncAnonymousMethodAsync()
+ {
+ var testCode = @"
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+class ClassA
+{
+ public delegate Task SampleDelegate();
+ SampleDelegate AsyncAnonymousMethod = async delegate ()
+ {
+ Thread.Sleep(1);
+ return await Task.FromResult(0);
+ };
+}";
+ var fixedCode = @"
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+class ClassA
+{
+ public delegate Task SampleDelegate();
+ SampleDelegate AsyncAnonymousMethod = async delegate ()
+ {
+ await System.Threading.Tasks.Task.Delay(1);
+ return await Task.FromResult(0);
+ };
+}";
+ var result = this.OptionallyAddArgumentsToDiagnostic(this.CSharpDiagnostic().WithLocation(11, 9), UsageResources.AsyncAnonymousFunctionsAndMethods);
+
+ await this.VerifyCSharpDiagnosticAsync(testCode, result, CancellationToken.None).ConfigureAwait(false);
+ await this.VerifyCSharpFixAllFixAsync(
+ testCode,
+ fixedCode,
+ cancellationToken: CancellationToken.None,
+ allowNewCompilerDiagnostics: true /* expected new diagnostic is "hidden CS8019: Unnecessary using directive." */)
+ .ConfigureAwait(false);
+ }
+
+ [Theory]
+ [InlineData("0")]
+ [InlineData("0 /* some inline comment */")]
+ [InlineData("(int)0L")]
+ [InlineData("1-1")]
+ [InlineData("TimeSpan.Zero")]
+ [InlineData("System.TimeSpan.Zero")]
+ public async Task TestThreadSleepZeroInAsyncAnonymousMethodAsync(string zeroParams)
+ {
+ var testCode = $@"
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+class ClassA
+{{
+ public delegate Task SampleDelegate();
+ SampleDelegate AsyncAnonymousMethod = async delegate ()
+ {{
+ Thread.Sleep({zeroParams});
+ return await Task.FromResult(0);
+ }};
+}}";
+ var fixedCode = @"
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+class ClassA
+{
+ public delegate Task SampleDelegate();
+ SampleDelegate AsyncAnonymousMethod = async delegate ()
+ {
+ await System.Threading.Tasks.Task.Yield();
+ return await Task.FromResult(0);
+ };
+}";
+ var result = this.OptionallyAddArgumentsToDiagnostic(this.CSharpDiagnostic().WithLocation(11, 9), UsageResources.AsyncAnonymousFunctionsAndMethods);
+
+ await this.VerifyCSharpDiagnosticAsync(testCode, result, CancellationToken.None).ConfigureAwait(false);
+ await this.VerifyCSharpFixAllFixAsync(
+ testCode,
+ fixedCode,
+ cancellationToken: CancellationToken.None,
+ allowNewCompilerDiagnostics: true /* expected new diagnostic is "hidden CS8019: Unnecessary using directive." */)
+ .ConfigureAwait(false);
+ }
+
+ [Fact]
+ public async Task TestUsingTaskDelayIsOKAsync()
+ {
+ var testCode = @"
+using System.Threading.Tasks;
+using System.Threading;
+
+class ClassA
+{
+ public async Task Method1Async()
+ {
+ await Task.Delay(1);
+ return await Task.FromResult(0);
+ }
+}";
+
+ await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ [Fact]
+ public async Task TestUsingTaskYieldIsOKAsync()
+ {
+ var testCode = @"
+using System.Threading.Tasks;
+using System.Threading;
+
+class ClassA
+{
+ public async Task Method1Async()
+ {
+ await Task.Delay(1);
+ return await Task.FromResult(0);
+ }
+}";
+
+ await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ protected override CodeFixProvider GetCSharpCodeFixProvider()
+ {
+ return new DontUseThreadSleepCodeUniversalCodeFixProvider();
+ }
+ }
+}
diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/packages.config b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/packages.config
index 46f927a..c61bfac 100644
--- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/packages.config
+++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/packages.config
@@ -1,15 +1,23 @@
-
-
-
-
-
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers/AsyncUsageAnalyzers.csproj b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/AsyncUsageAnalyzers.csproj
index 29b764b..4e8fb9b 100644
--- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers/AsyncUsageAnalyzers.csproj
+++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/AsyncUsageAnalyzers.csproj
@@ -50,6 +50,7 @@
+ TrueTrue
@@ -79,13 +80,16 @@
TrueResources.resx
+
+
+
+ TrueTrueUsageResources.resx
-
@@ -124,16 +128,16 @@
-
- ..\..\packages\Microsoft.CodeAnalysis.Common.1.0.0\lib\portable-net45+win8\Microsoft.CodeAnalysis.dll
+
+ ..\..\packages\Microsoft.CodeAnalysis.Common.1.3.2\lib\portable-net45+win8\Microsoft.CodeAnalysis.dllTrue
-
- ..\..\packages\Microsoft.CodeAnalysis.CSharp.1.0.0\lib\portable-net45+win8\Microsoft.CodeAnalysis.CSharp.dll
+
+ ..\..\packages\Microsoft.CodeAnalysis.CSharp.1.3.2\lib\portable-net45+win8\Microsoft.CodeAnalysis.CSharp.dllTrue
-
- ..\..\packages\System.Collections.Immutable.1.1.36\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll
+
+ ..\..\packages\System.Collections.Immutable.1.1.37\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dllTrue
@@ -156,15 +160,15 @@
..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.TypedParts.dllFalse
-
- ..\..\packages\System.Reflection.Metadata.1.0.21\lib\portable-net45+win8\System.Reflection.Metadata.dll
+
+ ..\..\packages\System.Reflection.Metadata.1.2.0\lib\portable-net45+win8\System.Reflection.Metadata.dllTrue
-
-
+
+
diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Helpers/ExpressionSyntaxExtensions.cs b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Helpers/ExpressionSyntaxExtensions.cs
new file mode 100644
index 0000000..596782c
--- /dev/null
+++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Helpers/ExpressionSyntaxExtensions.cs
@@ -0,0 +1,92 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+namespace AsyncUsageAnalyzers.Helpers
+{
+ using System.Linq;
+ using Microsoft.CodeAnalysis;
+ using Microsoft.CodeAnalysis.CSharp;
+ using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+ public static class ExpressionSyntaxExtensions
+ {
+ public static bool TryGetMethodSymbolByTypeNameAndMethodName(
+ this ExpressionSyntax invocationExpression,
+ SemanticModel semanticModel,
+ string fullyQualifiedName,
+ string methodName,
+ out IMethodSymbol methodSymbol)
+ {
+ var methodSymbolCandidate = ModelExtensions.GetSymbolInfo(semanticModel, invocationExpression).Symbol as IMethodSymbol;
+ if (methodSymbolCandidate != null)
+ {
+ var typeMetadata = semanticModel.Compilation.GetTypeByMetadataName(fullyQualifiedName);
+ if (typeMetadata.Equals(methodSymbolCandidate.ReceiverType) && (methodSymbolCandidate.Name == methodName))
+ {
+ methodSymbol = methodSymbolCandidate;
+ return true;
+ }
+ }
+
+ methodSymbol = null;
+ return false;
+ }
+
+ public static bool TryGetFieldSymbolByTypeNameAndMethodName(
+ this ExpressionSyntax invocationExpression,
+ SemanticModel semanticModel,
+ string fullyQualifiedName,
+ string propertyName,
+ out IFieldSymbol propertySymbol)
+ {
+ var propertySymbolCandidate = ModelExtensions.GetSymbolInfo(semanticModel, invocationExpression).Symbol as IFieldSymbol;
+ if (propertySymbolCandidate != null)
+ {
+ var typeMetadata = semanticModel.Compilation.GetTypeByMetadataName(fullyQualifiedName);
+ if (typeMetadata.Equals(propertySymbolCandidate.ContainingType) && (propertySymbolCandidate.Name == propertyName))
+ {
+ propertySymbol = propertySymbolCandidate;
+ return true;
+ }
+ }
+
+ propertySymbol = null;
+ return false;
+ }
+
+ public static bool IsInsideAsyncCode(this ExpressionSyntax invocationExpression, ref SyntaxNode enclosingMethodOrFunctionDeclaration)
+ {
+ foreach (var syntaxNode in invocationExpression.Ancestors())
+ {
+ var methodDeclaration = syntaxNode as MethodDeclarationSyntax;
+ if (methodDeclaration != null)
+ {
+ enclosingMethodOrFunctionDeclaration = syntaxNode;
+ return HasAsyncMethodModifier(methodDeclaration);
+ }
+
+ // This handles also AnonymousMethodExpressionSyntax since AnonymousMethodExpressionSyntax inherits from AnonymousFunctionExpressionSyntax
+ var anonymousFunction = syntaxNode as AnonymousFunctionExpressionSyntax;
+ if (anonymousFunction != null)
+ {
+ enclosingMethodOrFunctionDeclaration = syntaxNode;
+ return IsAsyncAnonymousFunction(anonymousFunction);
+ }
+ }
+
+ return false;
+ }
+
+ public static bool IsInsideAsyncCode(this ExpressionSyntax invocationExpression)
+ {
+ SyntaxNode enclosingMethodOrFunctionDeclaration = null;
+ return invocationExpression.IsInsideAsyncCode(ref enclosingMethodOrFunctionDeclaration);
+ }
+
+ private static bool HasAsyncMethodModifier(MethodDeclarationSyntax methodDeclaration) =>
+ methodDeclaration.Modifiers.Any(x => x.Kind() == SyntaxKind.AsyncKeyword);
+
+ private static bool IsAsyncAnonymousFunction(AnonymousFunctionExpressionSyntax anonymousFunctionExpressionSyntax) =>
+ anonymousFunctionExpressionSyntax.AsyncKeyword.Kind() == SyntaxKind.AsyncKeyword;
+ }
+}
diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/DontUseThreadSleepAnalyzer.cs b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/DontUseThreadSleepAnalyzer.cs
new file mode 100644
index 0000000..5ec6cec
--- /dev/null
+++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/DontUseThreadSleepAnalyzer.cs
@@ -0,0 +1,42 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+namespace AsyncUsageAnalyzers.Usage
+{
+ using System.Collections.Immutable;
+ using Microsoft.CodeAnalysis;
+ using Microsoft.CodeAnalysis.CSharp.Syntax;
+ using Microsoft.CodeAnalysis.Diagnostics;
+
+ ///
+ /// This analyzer reports a diagnostic if System.Threading.Thread.Sleep() method is called.
+ ///
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ public class DontUseThreadSleepAnalyzer : DontUseThreadSleepAnalyzerBase
+ {
+ ///
+ /// The ID for diagnostics produced by the analyzer.
+ ///
+ public const string DiagnosticId = "DontUseThreadSleep";
+ private static readonly LocalizableString Title = new LocalizableResourceString(nameof(UsageResources.DontUseThreadSleepTitle), UsageResources.ResourceManager, typeof(UsageResources));
+ private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(UsageResources.DontUseThreadSleepMessageFormat), UsageResources.ResourceManager, typeof(UsageResources));
+ private static readonly string Category = "AsyncUsage.CSharp.Usage";
+ private static readonly LocalizableString Description = new LocalizableResourceString(nameof(UsageResources.DontUseThreadSleepDescription), UsageResources.ResourceManager, typeof(UsageResources));
+ private static readonly string HelpLink = "https://github.com/DotNetAnalyzers/AsyncUsageAnalyzers/blob/master/documentation/DontUseThreadSleep.md";
+ private static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, AnalyzerConstants.EnabledByDefault, Description, HelpLink);
+
+ ///
+ public override ImmutableArray SupportedDiagnostics { get; } =
+ ImmutableArray.Create(Descriptor);
+
+ protected override AnalyzerBase GetAnalyzer() => new Analyzer();
+
+ private sealed class Analyzer : DontUseThreadSleepAnalyzerBase.AnalyzerBase
+ {
+ protected override void ReportDiagnosticOnThreadSleepInvocation(SyntaxNodeAnalysisContext context, InvocationExpressionSyntax invocationExpression)
+ {
+ context.ReportDiagnostic(Diagnostic.Create(Descriptor, invocationExpression.GetLocation()));
+ }
+ }
+ }
+}
diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/DontUseThreadSleepAnalyzerBase.cs b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/DontUseThreadSleepAnalyzerBase.cs
new file mode 100644
index 0000000..0fd4cae
--- /dev/null
+++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/DontUseThreadSleepAnalyzerBase.cs
@@ -0,0 +1,58 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+namespace AsyncUsageAnalyzers.Usage
+{
+ using AsyncUsageAnalyzers.Helpers;
+ using Microsoft.CodeAnalysis;
+ using Microsoft.CodeAnalysis.CSharp;
+ using Microsoft.CodeAnalysis.CSharp.Syntax;
+ using Microsoft.CodeAnalysis.Diagnostics;
+
+ ///
+ /// This analyzer is a base class for analyzers repoting usage of System.Threading.Thread.Sleep() method in various scenerios.
+ ///
+ public abstract class DontUseThreadSleepAnalyzerBase : DiagnosticAnalyzer
+ {
+ ///
+ public override void Initialize(AnalysisContext context)
+ {
+ var analyzer = this.GetAnalyzer();
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+ context.EnableConcurrentExecution();
+
+ context.RegisterSyntaxNodeAction(analyzer.HandleInvocation, SyntaxKind.InvocationExpression);
+ }
+
+ protected abstract AnalyzerBase GetAnalyzer();
+
+ protected abstract class AnalyzerBase
+ {
+ protected abstract void ReportDiagnosticOnThreadSleepInvocation(SyntaxNodeAnalysisContext context, InvocationExpressionSyntax invocationExpression);
+
+ internal void HandleInvocation(SyntaxNodeAnalysisContext context)
+ {
+ var invocationExpression = (InvocationExpressionSyntax)context.Node;
+
+ var semanticModel = context.SemanticModel;
+ var fullyQualifiedName = "System.Threading.Thread";
+ var methodName = "Sleep";
+
+ // This check aims at increasing the performance.
+ // Thanks to it, getting a semantic model in not necessary in majority of cases.
+ if (!invocationExpression.Expression.GetText().ToString().Contains(methodName))
+ {
+ return;
+ }
+
+ IMethodSymbol methodSymbol;
+ if (!invocationExpression.TryGetMethodSymbolByTypeNameAndMethodName(semanticModel, fullyQualifiedName, methodName, out methodSymbol))
+ {
+ return;
+ }
+
+ this.ReportDiagnosticOnThreadSleepInvocation(context, invocationExpression);
+ }
+ }
+ }
+}
diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/DontUseThreadSleepInAsyncCodeAnalyzer.cs b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/DontUseThreadSleepInAsyncCodeAnalyzer.cs
new file mode 100644
index 0000000..0ec635e
--- /dev/null
+++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/DontUseThreadSleepInAsyncCodeAnalyzer.cs
@@ -0,0 +1,61 @@
+// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+namespace AsyncUsageAnalyzers.Usage
+{
+ using System.Collections.Immutable;
+ using AsyncUsageAnalyzers.Helpers;
+ using Microsoft.CodeAnalysis;
+ using Microsoft.CodeAnalysis.CSharp.Syntax;
+ using Microsoft.CodeAnalysis.Diagnostics;
+
+ ///
+ /// This analyzer reports a diagnostic if System.Threading.Thread.Sleep() method is inside async code
+ /// (i.e. asynchronous methods, asynchronous anonymous functions or asynchronous anonymous methods).
+ ///
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ internal class DontUseThreadSleepInAsyncCodeAnalyzer : DontUseThreadSleepAnalyzerBase
+ {
+ ///
+ /// The ID for diagnostics produced by the analyzer.
+ ///
+ public const string DiagnosticId = "DontUseThreadSleepInAsyncCode";
+ private static readonly LocalizableString Title = new LocalizableResourceString(nameof(UsageResources.DontUseThreadSleepInAsyncCodeTitle), UsageResources.ResourceManager, typeof(UsageResources));
+ private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(UsageResources.DontUseThreadSleepInAsyncCodeMessageFormat), UsageResources.ResourceManager, typeof(UsageResources));
+ private static readonly string Category = "AsyncUsage.CSharp.Usage";
+ private static readonly LocalizableString Description = new LocalizableResourceString(nameof(UsageResources.DontUseThreadSleepInAsyncCodeDescription), UsageResources.ResourceManager, typeof(UsageResources));
+ private static readonly string HelpLink = "https://github.com/DotNetAnalyzers/AsyncUsageAnalyzers/blob/master/documentation/DontUseThreadSleepInAsyncCode.md";
+ private static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, AnalyzerConstants.EnabledByDefault, Description, HelpLink);
+
+ ///
+ public override ImmutableArray SupportedDiagnostics { get; } =
+ ImmutableArray.Create(Descriptor);
+
+ protected override AnalyzerBase GetAnalyzer() => new Analyzer();
+
+ private static string GetMethodText(string methodName) =>
+ string.Format(UsageResources.MethodFormat, methodName);
+
+ private sealed class Analyzer : DontUseThreadSleepAnalyzerBase.AnalyzerBase
+ {
+ protected override void ReportDiagnosticOnThreadSleepInvocation(SyntaxNodeAnalysisContext context, InvocationExpressionSyntax invocationExpression)
+ {
+ SyntaxNode asycNode = null;
+ if (invocationExpression.IsInsideAsyncCode(ref asycNode))
+ {
+ var asyncMethod = asycNode as MethodDeclarationSyntax;
+ if (asyncMethod != null)
+ {
+ context.ReportDiagnostic(Diagnostic.Create(Descriptor, invocationExpression.GetLocation(), GetMethodText(asyncMethod.Identifier.Text)));
+ }
+
+ var asyncFunction = asycNode as AnonymousFunctionExpressionSyntax;
+ if (asyncFunction != null)
+ {
+ context.ReportDiagnostic(Diagnostic.Create(Descriptor, invocationExpression.GetLocation(), UsageResources.AsyncAnonymousFunctionsAndMethods));
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/UsageResources.Designer.cs b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/UsageResources.Designer.cs
index 9680621..3e4dc5b 100644
--- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/UsageResources.Designer.cs
+++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/UsageResources.Designer.cs
@@ -61,6 +61,69 @@ internal UsageResources() {
}
}
+ ///
+ /// Looks up a localized string similar to Asynchronous anonymous functions and methods.
+ ///
+ internal static string AsyncAnonymousFunctionsAndMethods {
+ get {
+ return ResourceManager.GetString("AsyncAnonymousFunctionsAndMethods", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Don't use Thread.Sleep() .
+ ///
+ internal static string DontUseThreadSleepDescription {
+ get {
+ return ResourceManager.GetString("DontUseThreadSleepDescription", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Don't use Thread.Sleep() in a aync code.
+ ///
+ internal static string DontUseThreadSleepInAsyncCodeDescription {
+ get {
+ return ResourceManager.GetString("DontUseThreadSleepInAsyncCodeDescription", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to {0} should not call Thread.Sleep().
+ ///
+ internal static string DontUseThreadSleepInAsyncCodeMessageFormat {
+ get {
+ return ResourceManager.GetString("DontUseThreadSleepInAsyncCodeMessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Don't use Thread.Sleep() In async code.
+ ///
+ internal static string DontUseThreadSleepInAsyncCodeTitle {
+ get {
+ return ResourceManager.GetString("DontUseThreadSleepInAsyncCodeTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Thread.Sleep() should not be used.
+ ///
+ internal static string DontUseThreadSleepMessageFormat {
+ get {
+ return ResourceManager.GetString("DontUseThreadSleepMessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Don't use Thread.Sleep().
+ ///
+ internal static string DontUseThreadSleepTitle {
+ get {
+ return ResourceManager.GetString("DontUseThreadSleepTitle", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Asynchronous methods should include a CancellationToken parameter..
///
@@ -88,6 +151,24 @@ internal static string IncludeCancellationParameterTitle {
}
}
+ ///
+ /// Looks up a localized string similar to lambda function.
+ ///
+ internal static string LambdaFunction {
+ get {
+ return ResourceManager.GetString("LambdaFunction", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Method '{0}'.
+ ///
+ internal static string MethodFormat {
+ get {
+ return ResourceManager.GetString("MethodFormat", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to The continuation behavior for a Task should be configured by calling ConfigureAwait prior to awaiting the task..
///
diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/UsageResources.resx b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/UsageResources.resx
index 440ea08..d5a0ec5 100644
--- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/UsageResources.resx
+++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/UsageResources.resx
@@ -117,6 +117,27 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+ Asynchronous anonymous functions and methods
+
+
+ Don't use Thread.Sleep()
+
+
+ Don't use Thread.Sleep() in a aync code
+
+
+ {0} should not call Thread.Sleep()
+
+
+ Don't use Thread.Sleep() In async code
+
+
+ Thread.Sleep() should not be used
+
+
+ Don't use Thread.Sleep()
+
Asynchronous methods should include a CancellationToken parameter.
@@ -126,6 +147,12 @@
Include CancellationToken parameter
+
+ lambda function
+
+
+ Method '{0}'
+
The continuation behavior for a Task should be configured by calling ConfigureAwait prior to awaiting the task.
diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers/packages.config b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/packages.config
index ecd7987..e09960c 100644
--- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers/packages.config
+++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/packages.config
@@ -1,12 +1,12 @@
-
-
-
+
+
+
-
-
+
+
\ No newline at end of file
diff --git a/documentation/DontUseThreadSleep.md b/documentation/DontUseThreadSleep.md
new file mode 100644
index 0000000..53d8b21
--- /dev/null
+++ b/documentation/DontUseThreadSleep.md
@@ -0,0 +1,53 @@
+## DontUseThreadSleep
+
+
+
+
TypeName
+
DontUseThreadSleep
+
+
+
CheckId
+
DontUseThreadSleep
+
+
+
Category
+
Usage Rules
+
+
+
+## Cause
+
+`System.Threading.Thread.Sleep()` method is called in the code.
+
+## Rule description
+
+`System.Threading.Thread.Sleep()` method is called in the code.
+`Thread.Sleep(0)` causes the thread to relinquishes the remainder of its time slice to any thread of equal priority that is ready to run.
+`Thread.Sleep(...)` with non-zero argument suspends the thread.
+Suspended thread cannot be used to execute other code which is undesirable since threads are quite expensive to create and take significant amount of memory.
+Switching between can decrease program's performance.
+`Thread.Sleep` should not be used to run an action periodically because it is imprecise (since it depends on OS's thread scheduler) and inefficient.
+`Thread.Sleep(...)` on UI thread pauses message pumping which makes the app unresponsive.
+There are cases when using `Thread.Sleep()` method is valid.
+
+## How to fix violations
+
+Thrad.Sleep with non-zero argument in async code can be replaced with `await System.Threading.Tasks.Task.Delay(...)`; `Thread.Sleep(0)` in async code can be replaced with `await System.Threading.Tasks.Yield()`.
+If `Thread.Sleep` is used to run actions periodically, consider using timer or appropriate observable instead.
+
+In some cases in non-async code, changing the logic of the program and using `ManualResetEvent`, `ManualResetEventSlim` or `AutoResetEvent` might be an alternative.
+
+If you are sure that using `Thread.Sleep()` is valid, suppress violations as described below.
+You may use less strict rule (e.g. DontUseThreadSleepInAsyncCode) or opt-out of this rule completely.
+
+## How to suppress violations
+
+```csharp
+[SuppressMessage("AsyncUsage.CSharp.Usage", "DontUseThreadSleep", Justification = "Reviewed.")]
+```
+
+```csharp
+#pragma warning disable DontUseThreadSleep // Use Async suffix
+Thread.Sleep(1000)
+#pragma warning restore DontUseThreadSleep // Use Async suffix
+```
diff --git a/documentation/DontUseThreadSleepInAsyncCode.md b/documentation/DontUseThreadSleepInAsyncCode.md
new file mode 100644
index 0000000..b02b586
--- /dev/null
+++ b/documentation/DontUseThreadSleepInAsyncCode.md
@@ -0,0 +1,51 @@
+## DontUseThreadSleepInAsyncCode
+
+
+
+
TypeName
+
DontUseThreadSleepInAsyncCode
+
+
+
CheckId
+
DontUseThreadSleepInAsyncCode
+
+
+
Category
+
Usage Rules
+
+
+
+## Cause
+
+`System.Threading.Thread.Sleep()` method is called in the async code (i.e. asynchronous method, asynchronous anonymous function or asynchronous anonymous method).
+
+## Rule description
+
+`System.Threading.Thread.Sleep()` method is called in the async code (i.e. asynchronous method, asynchronous anonymous function or asynchronous anonymous method).
+`Thread.Sleep(0)` causes the thread to relinquishes the remainder of its time slice to any thread of equal priority that is ready to run.
+`Thread.Sleep(...)` with non-zero argument suspends the thread.
+Suspended thread cannot be used to execute other code which is undesirable since threads are quite expensive to create and take significant amount of memory.
+Switching between can decrease program's performance.
+`Thread.Sleep` should not be used to run an action periodically because it is imprecise (since it depends on OS's thread scheduler) and inefficient.
+`Thread.Sleep(...)` on UI thread pauses message pumping which makes the app unresponsive.
+There are cases when using `Thread.Sleep()` method is valid.
+
+## How to fix violations
+
+`Thrad.Sleep` with non-zero argument in async code can be replaced with `await System.Threading.Tasks.Task.Delay(...)`; `Thread.Sleep(0)` in async code can be replaced with `await System.Threading.Tasks.Yield()`.
+If `Thread.Sleep` is used to run actions periodically, consider using timer or appropriate observable instead.
+
+If you are sure that using `Thread.Sleep()` is valid, suppress violations as described below.
+Alternatively, you may opt-out of this rule.
+
+## How to suppress violations
+
+```csharp
+[SuppressMessage("AsyncUsage.CSharp.Usage", "DontUseThreadSleep", Justification = "Reviewed.")]
+```
+
+```csharp
+#pragma warning disable DontUseThreadSleep // Use Async suffix
+Thread.Sleep(1000)
+#pragma warning restore DontUseThreadSleep // Use Async suffix
+```