From 133c5f26ccedff11b25ec0df04a59f47bb229749 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Maczy=C5=84ski?= Date: Sun, 4 Sep 2016 14:12:05 +0200 Subject: [PATCH 01/22] AsyncUsageAnalyzers.sln: update version of VisualStudio from 14.0.24720.0 to 14.0.25420.1 --- AsyncUsageAnalyzers.sln | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncUsageAnalyzers.sln b/AsyncUsageAnalyzers.sln index 538f460..96bf4d6 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 From 969c1f35282a158627e5a3766de0bf6b0e4aebc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Maczy=C5=84ski?= Date: Sun, 4 Sep 2016 14:17:10 +0200 Subject: [PATCH 02/22] update version of Microsoft.CodeAnalysis.Analyzers and dependent packages --- .../AsyncUsageAnalyzers.CodeFixes.csproj | 28 ++++++++-------- .../packages.config | 14 ++++---- .../AsyncUsageAnalyzers.Test.csproj | 32 +++++++++---------- .../AsyncUsageAnalyzers.Test/packages.config | 22 +++++++++---- .../AsyncUsageAnalyzers.csproj | 20 ++++++------ .../AsyncUsageAnalyzers/packages.config | 10 +++--- 6 files changed, 67 insertions(+), 59 deletions(-) diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/AsyncUsageAnalyzers.CodeFixes.csproj b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/AsyncUsageAnalyzers.CodeFixes.csproj index 77f08d3..9001aeb 100644 --- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/AsyncUsageAnalyzers.CodeFixes.csproj +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/AsyncUsageAnalyzers.CodeFixes.csproj @@ -80,24 +80,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 +120,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/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..c810977 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 @@ -155,8 +155,8 @@ - - + + 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..08073c6 100644 --- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers/AsyncUsageAnalyzers.csproj +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/AsyncUsageAnalyzers.csproj @@ -124,16 +124,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 +156,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/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 From 466b00b7716aacc9d839e7061d1e6617dab5ef77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Maczy=C5=84ski?= Date: Sun, 4 Sep 2016 14:19:40 +0200 Subject: [PATCH 03/22] Initial implementation of DontUseThreadSleep and DontUseThreadSleepInAsyncCode analysis --- AsyncUsageAnalyzers.sln | 2 + .../AsyncUsageAnalyzers.CodeFixes.csproj | 1 + ...ThreadSleepCodeUniversalCodeFixProvider.cs | 110 ++++++++++ .../AsyncUsageAnalyzers.Test.csproj | 3 + .../DontUseThreadSleepInAsyncCodeTests.cs | 59 ++++++ .../Usage/DontUseThreadSleepTests.cs | 109 ++++++++++ .../Usage/DontUseThreadSleepTestsBase.cs | 195 ++++++++++++++++++ .../AsyncUsageAnalyzers.csproj | 6 +- .../InvocationExpressionSyntaxExtensions.cs | 78 +++++++ .../Usage/DontUseThreadSleepAnalyzer.cs | 44 ++++ .../Usage/DontUseThreadSleepAnalyzerBase.cs | 60 ++++++ .../DontUseThreadSleepInAsyncCodeAnalyzer.cs | 63 ++++++ .../Usage/UsageResources.Designer.cs | 81 ++++++++ .../Usage/UsageResources.resx | 27 +++ DontUseThreadSleep.md | 46 +++++ DontUseThreadSleepInAsyncCode.md | 42 ++++ 16 files changed, 925 insertions(+), 1 deletion(-) create mode 100644 AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs create mode 100644 AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepInAsyncCodeTests.cs create mode 100644 AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepTests.cs create mode 100644 AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepTestsBase.cs create mode 100644 AsyncUsageAnalyzers/AsyncUsageAnalyzers/Helpers/InvocationExpressionSyntaxExtensions.cs create mode 100644 AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/DontUseThreadSleepAnalyzer.cs create mode 100644 AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/DontUseThreadSleepAnalyzerBase.cs create mode 100644 AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/DontUseThreadSleepInAsyncCodeAnalyzer.cs create mode 100644 DontUseThreadSleep.md create mode 100644 DontUseThreadSleepInAsyncCode.md diff --git a/AsyncUsageAnalyzers.sln b/AsyncUsageAnalyzers.sln index 96bf4d6..2b9f008 100644 --- a/AsyncUsageAnalyzers.sln +++ b/AsyncUsageAnalyzers.sln @@ -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 + DontUseThreadSleep.md = DontUseThreadSleep.md + DontUseThreadSleepInAsyncCode.md = 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 9001aeb..08b628d 100644 --- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/AsyncUsageAnalyzers.CodeFixes.csproj +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/AsyncUsageAnalyzers.CodeFixes.csproj @@ -51,6 +51,7 @@ + diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs new file mode 100644 index 0000000..41a6c02 --- /dev/null +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs @@ -0,0 +1,110 @@ +// 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. + +/* Contributor: Tomasz Maczyński */ + +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, LanguageNames.VisualBasic, 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) + { + RegisterCodeFixForDiagnosic(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()) + { + RegisterCodeFixForDiagnosic(context, diagnostic); + } + } + } + } + + private static void RegisterCodeFixForDiagnosic(CodeFixContext context, Diagnostic diagnostic) + { + context.RegisterCodeFix( + CodeAction.Create( + "Use await System.Threading.Tasks.Task.Delay(...)", + 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 = GenerateTaskDelayExpression(arguments); + + SyntaxNode newRoot = root.ReplaceNode(expression, newExpression.WithTriviaFrom(expression)); + var newDocument = document.WithSyntaxRoot(newRoot); + return newDocument; + } + + 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)); + } +} diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/AsyncUsageAnalyzers.Test.csproj b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/AsyncUsageAnalyzers.Test.csproj index c810977..b79dc89 100644 --- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/AsyncUsageAnalyzers.Test.csproj +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/AsyncUsageAnalyzers.Test.csproj @@ -126,6 +126,9 @@ + + + diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepInAsyncCodeTests.cs b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepInAsyncCodeTests.cs new file mode 100644 index 0000000..87da184 --- /dev/null +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepInAsyncCodeTests.cs @@ -0,0 +1,59 @@ +// 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. + +/* Contributor: Tomasz Maczyński */ + +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..9fa796a --- /dev/null +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepTests.cs @@ -0,0 +1,109 @@ +// 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. + +/* Contributor: Tomasz Maczyński */ + +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..5303f11 --- /dev/null +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepTestsBase.cs @@ -0,0 +1,195 @@ +// 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. + +/* Contributor: Tomasz Maczyński */ + +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); + } + + [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(1); + }; + } +}"; + + 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(1); + }; + } +}"; + 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(0); + 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(0); + 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(1000); + 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/AsyncUsageAnalyzers.csproj b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/AsyncUsageAnalyzers.csproj index 08073c6..071210d 100644 --- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers/AsyncUsageAnalyzers.csproj +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/AsyncUsageAnalyzers.csproj @@ -55,6 +55,7 @@ True HelpersResources.resx + @@ -79,13 +80,16 @@ True Resources.resx + + + + True True UsageResources.resx - diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Helpers/InvocationExpressionSyntaxExtensions.cs b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Helpers/InvocationExpressionSyntaxExtensions.cs new file mode 100644 index 0000000..ce542c5 --- /dev/null +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Helpers/InvocationExpressionSyntaxExtensions.cs @@ -0,0 +1,78 @@ +// 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. + +/* Contributor: Tomasz Maczyński */ + +namespace AsyncUsageAnalyzers.Helpers +{ + using System.Linq; + using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.CSharp; + using Microsoft.CodeAnalysis.CSharp.Syntax; + + internal static class InvocationExpressionSyntaxExtensions + { + public static bool TryGetMethodSymbolByTypeNameAndMethodName( + this InvocationExpressionSyntax invocationExpression, + SemanticModel semanticModel, + string fullyQualifiedName, + string methodName, + out IMethodSymbol methodSymbol) + { + methodSymbol = ModelExtensions.GetSymbolInfo(semanticModel, invocationExpression).Symbol as IMethodSymbol; + if (methodSymbol == null) + { + return false; + } + + var threadTypeMetadata = semanticModel.Compilation.GetTypeByMetadataName(fullyQualifiedName); + if (!threadTypeMetadata.Equals(methodSymbol.ReceiverType)) + { + return false; + } + + if (methodSymbol.Name != methodName) + { + return false; + } + + return true; + } + + public static bool IsInsideAsyncCode(this InvocationExpressionSyntax 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 InvocationExpressionSyntax 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..f24ef3a --- /dev/null +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/DontUseThreadSleepAnalyzer.cs @@ -0,0 +1,44 @@ +// 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. + +/* Contributor: Tomasz Maczyński */ + +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..5b55727 --- /dev/null +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/DontUseThreadSleepAnalyzerBase.cs @@ -0,0 +1,60 @@ +// 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. + +/* Contributor: Tomasz Maczyński */ + +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..f81037a --- /dev/null +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/DontUseThreadSleepInAsyncCodeAnalyzer.cs @@ -0,0 +1,63 @@ +// 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. + +/* Contributor: Tomasz Maczyński */ + +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 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)); + } + } + } + } + + private static string GetMethodText(string methodName) => + string.Format(UsageResources.MethodFormat, methodName); + } +} \ 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/DontUseThreadSleep.md b/DontUseThreadSleep.md new file mode 100644 index 0000000..85f6971 --- /dev/null +++ b/DontUseThreadSleep.md @@ -0,0 +1,46 @@ +## 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. +If this method is in code which can executed asynchronously, the code is not optimal - the thread that is sleeping cannot execute any other tasks. +Note that non-async method might be called from asynchronous code and in such circumstances the thread cannot execute other task. +In such case, consider refactoring your code. +There are cases when using Thread.Sleep() method is valid. + +## How to fix violations + +If identified method call is inside asynchronous method or function, consider using "await System.Threading.Tasks.Task.Delay(...)" as an alternative. +If you are sure that using Thread.Sleep() is approprate, 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/DontUseThreadSleepInAsyncCode.md b/DontUseThreadSleepInAsyncCode.md new file mode 100644 index 0000000..974b392 --- /dev/null +++ b/DontUseThreadSleepInAsyncCode.md @@ -0,0 +1,42 @@ +## 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 asynchronous code. +The code is not optimal - the thread that is sleeping cannot execute any other tasks. + +## How to fix violations + +Use "await System.Threading.Tasks.Task.Delay(...)" instead. +If a sleep is interrupted by some other thread, use overload of Task.Delay() which takes a cancallation token. + +## 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 +``` From ddc42f4b3439584399f9448b1a555136f93cd66f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Maczy=C5=84ski?= Date: Sun, 16 Oct 2016 17:00:45 +0200 Subject: [PATCH 04/22] add test which checks if `Thread.Sleep(0)` is changed to` await Task.Yield()`. Don't use 0 as an Thread.Sleep's argument in other tests --- .../Usage/DontUseThreadSleepTestsBase.cs | 174 +++++++++++++++++- 1 file changed, 169 insertions(+), 5 deletions(-) diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepTestsBase.cs b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepTestsBase.cs index 5303f11..7302c70 100644 --- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepTestsBase.cs +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepTestsBase.cs @@ -79,6 +79,62 @@ await this.VerifyCSharpFixAllFixAsync( .ConfigureAwait(false); } + [Fact] + public async Task TestThreadSleepZeroInAsyncMethodAsync() + { + var testCode = @" +using System.Threading; +using System.Threading.Tasks; +using static System.Threading.Thread; + +class ClassA +{ + public async Task MethodAsync() + { + Sleep(0); + Thread.Sleep(0); + System.Threading.Thread.Sleep(0); + global::System.Threading.Thread.Sleep(0); + + 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.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(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); + } + [Fact] public async Task TestThreadSleepInAsyncAnonymousFunctionAsync() { @@ -94,7 +150,7 @@ public void MethodA() Func testFunc = async () => { Thread.Sleep(1); - await Task.FromResult(1); + await Task.FromResult(0); }; } }"; @@ -111,7 +167,54 @@ public void MethodA() Func testFunc = async () => { await System.Threading.Tasks.Task.Delay(1); - await Task.FromResult(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); + } + + [Fact] + public async Task TestThreadSleepZeroInAsyncAnonymousFunctionAsync() + { + var testCode = @" +using System; +using System.Threading; +using System.Threading.Tasks; + +class ClassA +{ + public void MethodA() + { + Func testFunc = async () => + { + Thread.Sleep(0); + 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); }; } }"; @@ -134,6 +237,48 @@ public async Task TestThreadSleepInAsyncAnonymousMethodAsync() 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); + } + + [Fact] + public async Task TestThreadSleepZeroInAsyncAnonymousMethodAsync() + { + var testCode = @" +using System; +using System.Threading; +using System.Threading.Tasks; + class ClassA { public delegate Task SampleDelegate(); @@ -153,7 +298,7 @@ class ClassA public delegate Task SampleDelegate(); SampleDelegate AsyncAnonymousMethod = async delegate () { - await System.Threading.Tasks.Task.Delay(0); + await System.Threading.Tasks.Task.Yield(); return await Task.FromResult(0); }; }"; @@ -179,8 +324,27 @@ class ClassA { public async Task Method1Async() { - await Task.Delay(1000); - return await Task.FromResult(0); + 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); } }"; From f4b7d8b81b328e0db6c034ddbc4b771ec2461fbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Maczy=C5=84ski?= Date: Sun, 16 Oct 2016 17:04:50 +0200 Subject: [PATCH 05/22] update DontUseThreadSleepCodeUniversalCodeFixProvider CodeAction's title update DontUseThreadSleepCodeUniversalCodeFixProvider CodeAction's title so that it mentions Task.Yield() as a possible fix --- .../Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs index 41a6c02..6e5046c 100644 --- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs @@ -65,7 +65,7 @@ private static void RegisterCodeFixForDiagnosic(CodeFixContext context, Diagnost { context.RegisterCodeFix( CodeAction.Create( - "Use await System.Threading.Tasks.Task.Delay(...)", + "Use `await Task.Delay(...)` or `await Task.Yield()`", cancellationToken => GetTransformedDocumentAsync(context.Document, diagnostic, cancellationToken)), diagnostic); } From 9982504fc81fab9b24d640e3af443676916eb517 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Maczy=C5=84ski?= Date: Sun, 16 Oct 2016 17:51:43 +0200 Subject: [PATCH 06/22] basic syntax-based heuristic to handle Thread.Sleep(0) correctly --- ...ThreadSleepCodeUniversalCodeFixProvider.cs | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs index 6e5046c..5b1c306 100644 --- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs @@ -82,13 +82,30 @@ private static async Task GetTransformedDocumentAsync(Document documen var arguments = expression.ArgumentList; - var newExpression = GenerateTaskDelayExpression(arguments); + var newExpression = IsArgumentListWithZeroValue(arguments) + ? GenerateTaskYieldExpression() + : GenerateTaskDelayExpression(arguments); SyntaxNode newRoot = root.ReplaceNode(expression, newExpression.WithTriviaFrom(expression)); var newDocument = document.WithSyntaxRoot(newRoot); return newDocument; } + private static bool IsArgumentListWithZeroValue(ArgumentListSyntax argumentListSyntax) + { + if (argumentListSyntax.Arguments.Count != 1) + { + return false; + } + + if (argumentListSyntax.Arguments.First().ToString().Trim() == "0") + { + return true; + } + + return false; + } + private static AwaitExpressionSyntax GenerateTaskDelayExpression(ArgumentListSyntax methodArgumentList) => SyntaxFactory.AwaitExpression( SyntaxFactory.InvocationExpression( @@ -106,5 +123,22 @@ private static AwaitExpressionSyntax GenerateTaskDelayExpression(ArgumentListSyn 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")))); } } From e64d6925c9e39904bc61f2e8c04531ad3a329c71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Maczy=C5=84ski?= Date: Mon, 17 Oct 2016 01:31:20 +0200 Subject: [PATCH 07/22] add additional test cases when 'Thread.Sleep()' which should be converted to 'await Task.Yield()' --- .../Usage/DontUseThreadSleepTestsBase.cs | 67 +++++++++++-------- 1 file changed, 38 insertions(+), 29 deletions(-) diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepTestsBase.cs b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepTestsBase.cs index 7302c70..01d3563 100644 --- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepTestsBase.cs +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepTestsBase.cs @@ -79,26 +79,29 @@ await this.VerifyCSharpFixAllFixAsync( .ConfigureAwait(false); } - [Fact] - public async Task TestThreadSleepZeroInAsyncMethodAsync() + [Theory] + [InlineData("0")] + [InlineData("0 /* some inline comment */")] + [InlineData("(int)0L")] + public async Task TestThreadSleepZeroInAsyncMethodAsync(string zeroParams) { - var testCode = @" + var testCode = $@" using System.Threading; using System.Threading.Tasks; using static System.Threading.Thread; class ClassA -{ +{{ public async Task MethodAsync() - { - Sleep(0); - Thread.Sleep(0); - System.Threading.Thread.Sleep(0); - global::System.Threading.Thread.Sleep(0); + {{ + 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.Threading; using System.Threading.Tasks; @@ -182,25 +185,28 @@ await this.VerifyCSharpFixAllFixAsync( .ConfigureAwait(false); } - [Fact] - public async Task TestThreadSleepZeroInAsyncAnonymousFunctionAsync() + [Theory] + [InlineData("0")] + [InlineData("0 /* some inline comment */")] + [InlineData("(int)0L")] + public async Task TestThreadSleepZeroInAsyncAnonymousFunctionAsync(string zeroParams) { - var testCode = @" + var testCode = $@" using System; using System.Threading; using System.Threading.Tasks; class ClassA -{ +{{ public void MethodA() - { + {{ Func testFunc = async () => - { - Thread.Sleep(0); + {{ + Thread.Sleep({zeroParams}); await Task.FromResult(0); - }; - } -}"; + }}; + }} +}}"; var fixedCode = @" using System; @@ -271,23 +277,26 @@ await this.VerifyCSharpFixAllFixAsync( .ConfigureAwait(false); } - [Fact] - public async Task TestThreadSleepZeroInAsyncAnonymousMethodAsync() + [Theory] + [InlineData("0")] + [InlineData("0 /* some inline comment */")] + [InlineData("(int)0L")] + public async Task TestThreadSleepZeroInAsyncAnonymousMethodAsync(string zeroParams) { - var testCode = @" + var testCode = $@" using System; using System.Threading; using System.Threading.Tasks; class ClassA -{ +{{ public delegate Task SampleDelegate(); SampleDelegate AsyncAnonymousMethod = async delegate () - { - Thread.Sleep(0); + {{ + Thread.Sleep({zeroParams}); return await Task.FromResult(0); - }; -}"; + }}; +}}"; var fixedCode = @" using System; using System.Threading; From f637170255a622aa035d5f0121ff4ab99b249db0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Maczy=C5=84ski?= Date: Tue, 18 Oct 2016 23:29:01 +0200 Subject: [PATCH 08/22] DontUseThreadSleepTestsBase: add more InlineData which are semanticly equal to zeros --- .../Usage/DontUseThreadSleepTestsBase.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepTestsBase.cs b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepTestsBase.cs index 01d3563..86bdd4d 100644 --- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepTestsBase.cs +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepTestsBase.cs @@ -83,6 +83,7 @@ await this.VerifyCSharpFixAllFixAsync( [InlineData("0")] [InlineData("0 /* some inline comment */")] [InlineData("(int)0L")] + [InlineData("1-1")] public async Task TestThreadSleepZeroInAsyncMethodAsync(string zeroParams) { var testCode = $@" @@ -189,6 +190,7 @@ await this.VerifyCSharpFixAllFixAsync( [InlineData("0")] [InlineData("0 /* some inline comment */")] [InlineData("(int)0L")] + [InlineData("1-1")] public async Task TestThreadSleepZeroInAsyncAnonymousFunctionAsync(string zeroParams) { var testCode = $@" @@ -281,6 +283,7 @@ await this.VerifyCSharpFixAllFixAsync( [InlineData("0")] [InlineData("0 /* some inline comment */")] [InlineData("(int)0L")] + [InlineData("1-1")] public async Task TestThreadSleepZeroInAsyncAnonymousMethodAsync(string zeroParams) { var testCode = $@" From 3fecffa1c68251967c8c065fabaa9b9db3f4debb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Maczy=C5=84ski?= Date: Tue, 18 Oct 2016 23:38:50 +0200 Subject: [PATCH 09/22] DontUseThreadSleepCodeUniversalCodeFixProvider: correctly handle values which are semanticaly equal to zero --- ...seThreadSleepCodeUniversalCodeFixProvider.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs index 5b1c306..865d052 100644 --- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs @@ -82,7 +82,7 @@ private static async Task GetTransformedDocumentAsync(Document documen var arguments = expression.ArgumentList; - var newExpression = IsArgumentListWithZeroValue(arguments) + var newExpression = await IsArgumentListWithZeroValueAsync(document, arguments).ConfigureAwait(true) ? GenerateTaskYieldExpression() : GenerateTaskDelayExpression(arguments); @@ -91,14 +91,25 @@ private static async Task GetTransformedDocumentAsync(Document documen return newDocument; } - private static bool IsArgumentListWithZeroValue(ArgumentListSyntax argumentListSyntax) + 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; } - if (argumentListSyntax.Arguments.First().ToString().Trim() == "0") + var argumentExpression = argumentListSyntax.Arguments.First().Expression; + + var argumentString = argumentExpression.ToString().Trim(); + if (argumentString == "0") + { + return true; + } + + var semanticModel = await document.GetSemanticModelAsync().ConfigureAwait(false); + var optionalValue = semanticModel.GetConstantValue(argumentExpression); + if (optionalValue.HasValue && optionalValue.Value.Equals(0)) { return true; } From 76f050af7c0ed8c0829811872ac23bdb80a4e319 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Maczy=C5=84ski?= Date: Wed, 19 Oct 2016 01:16:21 +0200 Subject: [PATCH 10/22] move extension methods from InvocationExpressionSyntaxExtensions to ExpressionSyntaxExtensions --- .../AsyncUsageAnalyzers/AsyncUsageAnalyzers.csproj | 2 +- ...xExtensions.cs => ExpressionSyntaxExtensions.cs} | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) rename AsyncUsageAnalyzers/AsyncUsageAnalyzers/Helpers/{InvocationExpressionSyntaxExtensions.cs => ExpressionSyntaxExtensions.cs} (82%) diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers/AsyncUsageAnalyzers.csproj b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/AsyncUsageAnalyzers.csproj index 071210d..4e8fb9b 100644 --- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers/AsyncUsageAnalyzers.csproj +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/AsyncUsageAnalyzers.csproj @@ -50,12 +50,12 @@ + True True HelpersResources.resx - diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Helpers/InvocationExpressionSyntaxExtensions.cs b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Helpers/ExpressionSyntaxExtensions.cs similarity index 82% rename from AsyncUsageAnalyzers/AsyncUsageAnalyzers/Helpers/InvocationExpressionSyntaxExtensions.cs rename to AsyncUsageAnalyzers/AsyncUsageAnalyzers/Helpers/ExpressionSyntaxExtensions.cs index ce542c5..640be81 100644 --- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Helpers/InvocationExpressionSyntaxExtensions.cs +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Helpers/ExpressionSyntaxExtensions.cs @@ -10,10 +10,10 @@ namespace AsyncUsageAnalyzers.Helpers using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; - internal static class InvocationExpressionSyntaxExtensions + public static class ExpressionSyntaxExtensions { public static bool TryGetMethodSymbolByTypeNameAndMethodName( - this InvocationExpressionSyntax invocationExpression, + this ExpressionSyntax invocationExpression, SemanticModel semanticModel, string fullyQualifiedName, string methodName, @@ -25,8 +25,8 @@ public static bool TryGetMethodSymbolByTypeNameAndMethodName( return false; } - var threadTypeMetadata = semanticModel.Compilation.GetTypeByMetadataName(fullyQualifiedName); - if (!threadTypeMetadata.Equals(methodSymbol.ReceiverType)) + var typeMetadata = semanticModel.Compilation.GetTypeByMetadataName(fullyQualifiedName); + if (!typeMetadata.Equals(methodSymbol.ReceiverType)) { return false; } @@ -39,7 +39,7 @@ public static bool TryGetMethodSymbolByTypeNameAndMethodName( return true; } - public static bool IsInsideAsyncCode(this InvocationExpressionSyntax invocationExpression, ref SyntaxNode enclosingMethodOrFunctionDeclaration) + public static bool IsInsideAsyncCode(this ExpressionSyntax invocationExpression, ref SyntaxNode enclosingMethodOrFunctionDeclaration) { foreach (var syntaxNode in invocationExpression.Ancestors()) { @@ -62,7 +62,7 @@ public static bool IsInsideAsyncCode(this InvocationExpressionSyntax invocationE return false; } - public static bool IsInsideAsyncCode(this InvocationExpressionSyntax invocationExpression) + public static bool IsInsideAsyncCode(this ExpressionSyntax invocationExpression) { SyntaxNode enclosingMethodOrFunctionDeclaration = null; return invocationExpression.IsInsideAsyncCode(ref enclosingMethodOrFunctionDeclaration); @@ -73,6 +73,5 @@ private static bool HasAsyncMethodModifier(MethodDeclarationSyntax methodDeclara private static bool IsAsyncAnonymousFunction(AnonymousFunctionExpressionSyntax anonymousFunctionExpressionSyntax) => anonymousFunctionExpressionSyntax.AsyncKeyword.Kind() == SyntaxKind.AsyncKeyword; - } } From e9cd7529774c02f28f486b4aef827e867d1ad4a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Maczy=C5=84ski?= Date: Wed, 19 Oct 2016 01:18:39 +0200 Subject: [PATCH 11/22] ExpressionSyntaxExtensions: simplify if statements to boolean expresion --- .../Helpers/ExpressionSyntaxExtensions.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Helpers/ExpressionSyntaxExtensions.cs b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Helpers/ExpressionSyntaxExtensions.cs index 640be81..35a1f7b 100644 --- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Helpers/ExpressionSyntaxExtensions.cs +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Helpers/ExpressionSyntaxExtensions.cs @@ -26,17 +26,7 @@ public static bool TryGetMethodSymbolByTypeNameAndMethodName( } var typeMetadata = semanticModel.Compilation.GetTypeByMetadataName(fullyQualifiedName); - if (!typeMetadata.Equals(methodSymbol.ReceiverType)) - { - return false; - } - - if (methodSymbol.Name != methodName) - { - return false; - } - - return true; + return typeMetadata.Equals(methodSymbol.ReceiverType) && (methodSymbol.Name == methodName); } public static bool IsInsideAsyncCode(this ExpressionSyntax invocationExpression, ref SyntaxNode enclosingMethodOrFunctionDeclaration) From 875fa4f37d81e9725ffb5edb118e09ce7c6d3be6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Maczy=C5=84ski?= Date: Wed, 19 Oct 2016 02:02:17 +0200 Subject: [PATCH 12/22] first working implementation checking if System.TimeSpan.Zero is used in Thread.Sleep() method call this includes code changes, new extension method and changes in CodeFixProvider --- ...seThreadSleepCodeUniversalCodeFixProvider.cs | 9 ++++++++- .../Usage/DontUseThreadSleepTestsBase.cs | 12 ++++++++++-- .../Helpers/ExpressionSyntaxExtensions.cs | 17 +++++++++++++++++ 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs index 865d052..7a6bff6 100644 --- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs @@ -102,7 +102,7 @@ private static async Task IsArgumentListWithZeroValueAsync(Document docume var argumentExpression = argumentListSyntax.Arguments.First().Expression; var argumentString = argumentExpression.ToString().Trim(); - if (argumentString == "0") + if (argumentString == "0" || argumentString == "TimeSpan.Zero") { return true; } @@ -114,6 +114,13 @@ private static async Task IsArgumentListWithZeroValueAsync(Document docume return true; } + var memberAccessExpression = argumentExpression as MemberAccessExpressionSyntax; + if (memberAccessExpression != null) + { + IFieldSymbol propertySymbol = null; + return memberAccessExpression.TryGetFieldSymbolByTypeNameAndMethodName(semanticModel, "System.TimeSpan", "Zero", out propertySymbol); + } + return false; } diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepTestsBase.cs b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepTestsBase.cs index 86bdd4d..81831cc 100644 --- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepTestsBase.cs +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepTestsBase.cs @@ -84,9 +84,12 @@ await this.VerifyCSharpFixAllFixAsync( [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; @@ -104,6 +107,7 @@ public async Task MethodAsync() }} }}"; var fixedCode = @" +using System; using System.Threading; using System.Threading.Tasks; using static System.Threading.Thread; @@ -122,10 +126,10 @@ public async Task MethodAsync() }"; var expectedResults = new[] { - this.CSharpDiagnostic().WithLocation(10, 9), this.CSharpDiagnostic().WithLocation(11, 9), this.CSharpDiagnostic().WithLocation(12, 9), - this.CSharpDiagnostic().WithLocation(13, 9) + this.CSharpDiagnostic().WithLocation(13, 9), + this.CSharpDiagnostic().WithLocation(14, 9) } .Select(diag => this.OptionallyAddArgumentsToDiagnostic(diag, string.Format(UsageResources.MethodFormat, "MethodAsync"))) .ToArray(); @@ -191,6 +195,8 @@ await this.VerifyCSharpFixAllFixAsync( [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 = $@" @@ -284,6 +290,8 @@ await this.VerifyCSharpFixAllFixAsync( [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 = $@" diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Helpers/ExpressionSyntaxExtensions.cs b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Helpers/ExpressionSyntaxExtensions.cs index 35a1f7b..e3ef406 100644 --- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Helpers/ExpressionSyntaxExtensions.cs +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Helpers/ExpressionSyntaxExtensions.cs @@ -29,6 +29,23 @@ public static bool TryGetMethodSymbolByTypeNameAndMethodName( return typeMetadata.Equals(methodSymbol.ReceiverType) && (methodSymbol.Name == methodName); } + public static bool TryGetFieldSymbolByTypeNameAndMethodName( + this ExpressionSyntax invocationExpression, + SemanticModel semanticModel, + string fullyQualifiedName, + string propertyName, + out IFieldSymbol propertySymbol) + { + propertySymbol = ModelExtensions.GetSymbolInfo(semanticModel, invocationExpression).Symbol as IFieldSymbol; + if (propertySymbol == null) + { + return false; + } + + var typeMetadata = semanticModel.Compilation.GetTypeByMetadataName(fullyQualifiedName); + return typeMetadata.Equals(propertySymbol.ContainingType) && (propertySymbol.Name == propertyName); + } + public static bool IsInsideAsyncCode(this ExpressionSyntax invocationExpression, ref SyntaxNode enclosingMethodOrFunctionDeclaration) { foreach (var syntaxNode in invocationExpression.Ancestors()) From f0e2c3d74bc384e325ce09735c76b2ddaa843820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Maczy=C5=84ski?= Date: Thu, 20 Oct 2016 01:04:00 +0200 Subject: [PATCH 13/22] DontUseThreadSleepInAsyncCodeAnalyzer: reorder a method and a class --- .../Usage/DontUseThreadSleepInAsyncCodeAnalyzer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/DontUseThreadSleepInAsyncCodeAnalyzer.cs b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/DontUseThreadSleepInAsyncCodeAnalyzer.cs index f81037a..dd3b518 100644 --- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/DontUseThreadSleepInAsyncCodeAnalyzer.cs +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/DontUseThreadSleepInAsyncCodeAnalyzer.cs @@ -35,6 +35,9 @@ internal class DontUseThreadSleepInAsyncCodeAnalyzer : DontUseThreadSleepAnalyze 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) @@ -56,8 +59,5 @@ protected override void ReportDiagnosticOnThreadSleepInvocation(SyntaxNodeAnalys } } } - - private static string GetMethodText(string methodName) => - string.Format(UsageResources.MethodFormat, methodName); } } \ No newline at end of file From 7ca29258852d2781a694a9a97cd19111a4586fdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Maczy=C5=84ski?= Date: Thu, 20 Oct 2016 01:10:02 +0200 Subject: [PATCH 14/22] delete comments with contributor name --- .../Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs | 2 -- .../Usage/DontUseThreadSleepInAsyncCodeTests.cs | 2 -- .../AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepTests.cs | 2 -- .../Usage/DontUseThreadSleepTestsBase.cs | 2 -- .../AsyncUsageAnalyzers/Helpers/ExpressionSyntaxExtensions.cs | 2 -- .../AsyncUsageAnalyzers/Usage/DontUseThreadSleepAnalyzer.cs | 2 -- .../AsyncUsageAnalyzers/Usage/DontUseThreadSleepAnalyzerBase.cs | 2 -- .../Usage/DontUseThreadSleepInAsyncCodeAnalyzer.cs | 2 -- 8 files changed, 16 deletions(-) diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs index 7a6bff6..42c0706 100644 --- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs @@ -1,8 +1,6 @@ // 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. -/* Contributor: Tomasz Maczyński */ - namespace AsyncUsageAnalyzers.Usage { using System.Collections.Immutable; diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepInAsyncCodeTests.cs b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepInAsyncCodeTests.cs index 87da184..65099b4 100644 --- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepInAsyncCodeTests.cs +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepInAsyncCodeTests.cs @@ -1,8 +1,6 @@ // 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. -/* Contributor: Tomasz Maczyński */ - namespace AsyncUsageAnalyzers.Test.Usage { using System.Collections.Generic; diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepTests.cs b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepTests.cs index 9fa796a..8e8d659 100644 --- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepTests.cs +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepTests.cs @@ -1,8 +1,6 @@ // 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. -/* Contributor: Tomasz Maczyński */ - namespace AsyncUsageAnalyzers.Test.Usage { using System.Collections.Generic; diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepTestsBase.cs b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepTestsBase.cs index 81831cc..5147cd3 100644 --- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepTestsBase.cs +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.Test/Usage/DontUseThreadSleepTestsBase.cs @@ -1,8 +1,6 @@ // 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. -/* Contributor: Tomasz Maczyński */ - namespace AsyncUsageAnalyzers.Test.Usage { using System.Linq; diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Helpers/ExpressionSyntaxExtensions.cs b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Helpers/ExpressionSyntaxExtensions.cs index e3ef406..86035c4 100644 --- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Helpers/ExpressionSyntaxExtensions.cs +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Helpers/ExpressionSyntaxExtensions.cs @@ -1,8 +1,6 @@ // 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. -/* Contributor: Tomasz Maczyński */ - namespace AsyncUsageAnalyzers.Helpers { using System.Linq; diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/DontUseThreadSleepAnalyzer.cs b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/DontUseThreadSleepAnalyzer.cs index f24ef3a..5ec6cec 100644 --- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/DontUseThreadSleepAnalyzer.cs +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/DontUseThreadSleepAnalyzer.cs @@ -1,8 +1,6 @@ // 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. -/* Contributor: Tomasz Maczyński */ - namespace AsyncUsageAnalyzers.Usage { using System.Collections.Immutable; diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/DontUseThreadSleepAnalyzerBase.cs b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/DontUseThreadSleepAnalyzerBase.cs index 5b55727..0fd4cae 100644 --- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/DontUseThreadSleepAnalyzerBase.cs +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/DontUseThreadSleepAnalyzerBase.cs @@ -1,8 +1,6 @@ // 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. -/* Contributor: Tomasz Maczyński */ - namespace AsyncUsageAnalyzers.Usage { using AsyncUsageAnalyzers.Helpers; diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/DontUseThreadSleepInAsyncCodeAnalyzer.cs b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/DontUseThreadSleepInAsyncCodeAnalyzer.cs index dd3b518..0ec635e 100644 --- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/DontUseThreadSleepInAsyncCodeAnalyzer.cs +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Usage/DontUseThreadSleepInAsyncCodeAnalyzer.cs @@ -1,8 +1,6 @@ // 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. -/* Contributor: Tomasz Maczyński */ - namespace AsyncUsageAnalyzers.Usage { using System.Collections.Immutable; From b89641591946b887939a79aa59d8bca147270c74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Maczy=C5=84ski?= Date: Thu, 20 Oct 2016 01:10:40 +0200 Subject: [PATCH 15/22] fix typo in RegisterCodeFixForDiagnostic method name --- .../Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs index 42c0706..79a31a1 100644 --- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs @@ -37,7 +37,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) { if (diagnostic.Id == DontUseThreadSleepInAsyncCodeAnalyzer.DiagnosticId) { - RegisterCodeFixForDiagnosic(context, diagnostic); + RegisterCodeFixForDiagnostic(context, diagnostic); } else if (diagnostic.Id == DontUseThreadSleepAnalyzer.DiagnosticId) { @@ -53,13 +53,13 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) if (invocationExpression.IsInsideAsyncCode()) { - RegisterCodeFixForDiagnosic(context, diagnostic); + RegisterCodeFixForDiagnostic(context, diagnostic); } } } } - private static void RegisterCodeFixForDiagnosic(CodeFixContext context, Diagnostic diagnostic) + private static void RegisterCodeFixForDiagnostic(CodeFixContext context, Diagnostic diagnostic) { context.RegisterCodeFix( CodeAction.Create( From 9272f066b469b62059e2359ca61a6d913384d182 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Maczy=C5=84ski?= Date: Thu, 20 Oct 2016 01:27:16 +0200 Subject: [PATCH 16/22] DontUseThreadSleepCodeUniversalCodeFixProvider: don't export code fix provider for VisualBasic --- .../Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs index 79a31a1..d0c3b31 100644 --- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers.CodeFixes/Usage/DontUseThreadSleepCodeUniversalCodeFixProvider.cs @@ -16,7 +16,7 @@ namespace AsyncUsageAnalyzers.Usage using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; - [ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic, Name = nameof(DontUseThreadSleepCodeUniversalCodeFixProvider))] + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(DontUseThreadSleepCodeUniversalCodeFixProvider))] [Shared] internal class DontUseThreadSleepCodeUniversalCodeFixProvider : CodeFixProvider { From f173b05957c759435ec54ac749672fdc08d65ab0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Maczy=C5=84ski?= Date: Fri, 21 Oct 2016 15:56:26 +0200 Subject: [PATCH 17/22] update documentation for Thread.Sleep-related analyzers --- DontUseThreadSleep.md | 14 +++++++++----- DontUseThreadSleepInAsyncCode.md | 16 ++++++++++++---- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/DontUseThreadSleep.md b/DontUseThreadSleep.md index 85f6971..efdd693 100644 --- a/DontUseThreadSleep.md +++ b/DontUseThreadSleep.md @@ -22,15 +22,19 @@ System.Threading.Thread.Sleep() method is called in the code. ## Rule description System.Threading.Thread.Sleep() method is called in the code. -If this method is in code which can executed asynchronously, the code is not optimal - the thread that is sleeping cannot execute any other tasks. -Note that non-async method might be called from asynchronous code and in such circumstances the thread cannot execute other task. -In such case, consider refactoring your 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 -If identified method call is inside asynchronous method or function, consider using "await System.Threading.Tasks.Task.Delay(...)" as an alternative. -If you are sure that using Thread.Sleep() is approprate, suppress violations as described below. +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. You may use less strict rule (e.g. DontUseThreadSleepInAsyncCode) or opt-out of this rule completely. ## How to suppress violations diff --git a/DontUseThreadSleepInAsyncCode.md b/DontUseThreadSleepInAsyncCode.md index 974b392..b90ed53 100644 --- a/DontUseThreadSleepInAsyncCode.md +++ b/DontUseThreadSleepInAsyncCode.md @@ -21,13 +21,21 @@ System.Threading.Thread.Sleep() method is called in the async code (i.e. asynchr ## Rule description -System.Threading.Thread.Sleep() method is called in the asynchronous code. -The code is not optimal - the thread that is sleeping cannot execute any other tasks. +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 -Use "await System.Threading.Tasks.Task.Delay(...)" instead. -If a sleep is interrupted by some other thread, use overload of Task.Delay() which takes a cancallation token. +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 From afe96a5cdee0cb2942f15436ed348e1e2d92a7eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Maczy=C5=84ski?= Date: Sun, 6 Nov 2016 18:25:52 +0100 Subject: [PATCH 18/22] ExpressionSyntaxExtensions.cs: refactor TryGet... methods. Now they return true if and only if non-null value is assigned to out parameter --- .../Helpers/ExpressionSyntaxExtensions.cs | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Helpers/ExpressionSyntaxExtensions.cs b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Helpers/ExpressionSyntaxExtensions.cs index 86035c4..596782c 100644 --- a/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Helpers/ExpressionSyntaxExtensions.cs +++ b/AsyncUsageAnalyzers/AsyncUsageAnalyzers/Helpers/ExpressionSyntaxExtensions.cs @@ -17,14 +17,19 @@ public static bool TryGetMethodSymbolByTypeNameAndMethodName( string methodName, out IMethodSymbol methodSymbol) { - methodSymbol = ModelExtensions.GetSymbolInfo(semanticModel, invocationExpression).Symbol as IMethodSymbol; - if (methodSymbol == null) + var methodSymbolCandidate = ModelExtensions.GetSymbolInfo(semanticModel, invocationExpression).Symbol as IMethodSymbol; + if (methodSymbolCandidate != null) { - return false; + var typeMetadata = semanticModel.Compilation.GetTypeByMetadataName(fullyQualifiedName); + if (typeMetadata.Equals(methodSymbolCandidate.ReceiverType) && (methodSymbolCandidate.Name == methodName)) + { + methodSymbol = methodSymbolCandidate; + return true; + } } - var typeMetadata = semanticModel.Compilation.GetTypeByMetadataName(fullyQualifiedName); - return typeMetadata.Equals(methodSymbol.ReceiverType) && (methodSymbol.Name == methodName); + methodSymbol = null; + return false; } public static bool TryGetFieldSymbolByTypeNameAndMethodName( @@ -34,14 +39,19 @@ public static bool TryGetFieldSymbolByTypeNameAndMethodName( string propertyName, out IFieldSymbol propertySymbol) { - propertySymbol = ModelExtensions.GetSymbolInfo(semanticModel, invocationExpression).Symbol as IFieldSymbol; - if (propertySymbol == null) + var propertySymbolCandidate = ModelExtensions.GetSymbolInfo(semanticModel, invocationExpression).Symbol as IFieldSymbol; + if (propertySymbolCandidate != null) { - return false; + var typeMetadata = semanticModel.Compilation.GetTypeByMetadataName(fullyQualifiedName); + if (typeMetadata.Equals(propertySymbolCandidate.ContainingType) && (propertySymbolCandidate.Name == propertyName)) + { + propertySymbol = propertySymbolCandidate; + return true; + } } - var typeMetadata = semanticModel.Compilation.GetTypeByMetadataName(fullyQualifiedName); - return typeMetadata.Equals(propertySymbol.ContainingType) && (propertySymbol.Name == propertyName); + propertySymbol = null; + return false; } public static bool IsInsideAsyncCode(this ExpressionSyntax invocationExpression, ref SyntaxNode enclosingMethodOrFunctionDeclaration) From 2cdb5a0c51654384f8e242ef37e3537a138c48f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Maczy=C5=84ski?= Date: Wed, 3 May 2017 11:47:28 +0200 Subject: [PATCH 19/22] move DontUseThreadSleep.md and DontUseThreadSleepInAsyncCode.md to a documentation folder --- AsyncUsageAnalyzers.sln | 4 ++-- DontUseThreadSleep.md => documentation/DontUseThreadSleep.md | 0 .../DontUseThreadSleepInAsyncCode.md | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename DontUseThreadSleep.md => documentation/DontUseThreadSleep.md (100%) rename DontUseThreadSleepInAsyncCode.md => documentation/DontUseThreadSleepInAsyncCode.md (100%) diff --git a/AsyncUsageAnalyzers.sln b/AsyncUsageAnalyzers.sln index 2b9f008..621b362 100644 --- a/AsyncUsageAnalyzers.sln +++ b/AsyncUsageAnalyzers.sln @@ -32,8 +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 - DontUseThreadSleep.md = DontUseThreadSleep.md - DontUseThreadSleepInAsyncCode.md = DontUseThreadSleepInAsyncCode.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/DontUseThreadSleep.md b/documentation/DontUseThreadSleep.md similarity index 100% rename from DontUseThreadSleep.md rename to documentation/DontUseThreadSleep.md diff --git a/DontUseThreadSleepInAsyncCode.md b/documentation/DontUseThreadSleepInAsyncCode.md similarity index 100% rename from DontUseThreadSleepInAsyncCode.md rename to documentation/DontUseThreadSleepInAsyncCode.md From 28c4e5646abcf9bc3795ee524492310b9fccbe80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Maczy=C5=84ski?= Date: Sat, 6 May 2017 02:08:04 +0200 Subject: [PATCH 20/22] add new line in DontUseThreadSleepInAsyncCode.md --- documentation/DontUseThreadSleepInAsyncCode.md | 1 + 1 file changed, 1 insertion(+) diff --git a/documentation/DontUseThreadSleepInAsyncCode.md b/documentation/DontUseThreadSleepInAsyncCode.md index b90ed53..56ad7b0 100644 --- a/documentation/DontUseThreadSleepInAsyncCode.md +++ b/documentation/DontUseThreadSleepInAsyncCode.md @@ -34,6 +34,7 @@ There are cases when using Thread.Sleep() method is valid. 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. From 3b30f210a2d1bd6004846d32912a56d1bc98e289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Maczy=C5=84ski?= Date: Sat, 6 May 2017 02:13:17 +0200 Subject: [PATCH 21/22] DontUseThreadSleep.md: add note that using reset event might be an alternative to using Thread.Sleep() --- documentation/DontUseThreadSleep.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/documentation/DontUseThreadSleep.md b/documentation/DontUseThreadSleep.md index efdd693..ea55bfd 100644 --- a/documentation/DontUseThreadSleep.md +++ b/documentation/DontUseThreadSleep.md @@ -34,6 +34,9 @@ There are cases when using Thread.Sleep() method is valid. 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. From a49bd3a2fc70b67340c4cf672b9464c9774ac492 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Maczy=C5=84ski?= Date: Sat, 6 May 2017 02:46:02 +0200 Subject: [PATCH 22/22] use backticks to format code in md files --- documentation/DontUseThreadSleep.md | 22 +++++++++---------- .../DontUseThreadSleepInAsyncCode.md | 20 ++++++++--------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/documentation/DontUseThreadSleep.md b/documentation/DontUseThreadSleep.md index ea55bfd..53d8b21 100644 --- a/documentation/DontUseThreadSleep.md +++ b/documentation/DontUseThreadSleep.md @@ -17,27 +17,27 @@ ## Cause -System.Threading.Thread.Sleep() method is called in the code. +`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. +`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. +`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. +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. +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. +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 diff --git a/documentation/DontUseThreadSleepInAsyncCode.md b/documentation/DontUseThreadSleepInAsyncCode.md index 56ad7b0..b02b586 100644 --- a/documentation/DontUseThreadSleepInAsyncCode.md +++ b/documentation/DontUseThreadSleepInAsyncCode.md @@ -17,25 +17,25 @@ ## Cause -System.Threading.Thread.Sleep() method is called in the async code (i.e. asynchronous method, asynchronous anonymous function or asynchronous anonymous method). +`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. +`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. +`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. +`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. +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