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.dll True - - ..\..\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.dll True - - ..\..\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.dll True - - ..\..\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.dll True - - ..\..\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.dll True @@ -120,15 +121,15 @@ ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.TypedParts.dll False - - ..\..\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.dll True - - + + 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.dll True - - ..\..\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.dll True - - ..\..\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.dll True - - ..\..\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.dll True - - ..\..\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.dll True - - ..\..\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.dll True @@ -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.dll True @@ -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 @@ + True True @@ -79,13 +80,16 @@ True Resources.resx + + + + True True UsageResources.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.dll True - - ..\..\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.dll True - - ..\..\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.dll True @@ -156,15 +160,15 @@ ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.TypedParts.dll False - - ..\..\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.dll True - - + + 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 + + + + + + + + + + + + + + +
TypeNameDontUseThreadSleep
CheckIdDontUseThreadSleep
CategoryUsage 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 + + + + + + + + + + + + + + +
TypeNameDontUseThreadSleepInAsyncCode
CheckIdDontUseThreadSleepInAsyncCode
CategoryUsage 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 +```