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

Don't use Thread.Sleep() analyzers - initial implementation #50

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
133c5f2
AsyncUsageAnalyzers.sln: update version of VisualStudio from 14.0.247…
tmaczynski Sep 4, 2016
969c1f3
update version of Microsoft.CodeAnalysis.Analyzers and dependent pack…
tmaczynski Sep 4, 2016
466b00b
Initial implementation of DontUseThreadSleep and DontUseThreadSleepIn…
tmaczynski Sep 4, 2016
ddc42f4
add test which checks if `Thread.Sleep(0)` is changed to` await Task.…
tmaczynski Oct 16, 2016
f4b7d8b
update DontUseThreadSleepCodeUniversalCodeFixProvider CodeAction's title
tmaczynski Oct 16, 2016
9982504
basic syntax-based heuristic to handle Thread.Sleep(0) correctly
tmaczynski Oct 16, 2016
e64d692
add additional test cases when 'Thread.Sleep()' which should be conve…
tmaczynski Oct 16, 2016
f637170
DontUseThreadSleepTestsBase: add more InlineData which are semanticly…
tmaczynski Oct 18, 2016
3fecffa
DontUseThreadSleepCodeUniversalCodeFixProvider: correctly handle valu…
tmaczynski Oct 18, 2016
76f050a
move extension methods from InvocationExpressionSyntaxExtensions to E…
tmaczynski Oct 18, 2016
e9cd752
ExpressionSyntaxExtensions: simplify if statements to boolean expresion
tmaczynski Oct 18, 2016
875fa4f
first working implementation checking if System.TimeSpan.Zero is used…
tmaczynski Oct 19, 2016
f0e2c3d
DontUseThreadSleepInAsyncCodeAnalyzer: reorder a method and a class
tmaczynski Oct 19, 2016
7ca2925
delete comments with contributor name
tmaczynski Oct 19, 2016
b896415
fix typo in RegisterCodeFixForDiagnostic method name
tmaczynski Oct 19, 2016
9272f06
DontUseThreadSleepCodeUniversalCodeFixProvider: don't export code fix…
tmaczynski Oct 19, 2016
f173b05
update documentation for Thread.Sleep-related analyzers
tmaczynski Oct 21, 2016
afe96a5
ExpressionSyntaxExtensions.cs: refactor TryGet... methods. Now they r…
tmaczynski Nov 6, 2016
2cdb5a0
move DontUseThreadSleep.md and DontUseThreadSleepInAsyncCode.md to a …
tmaczynski May 3, 2017
28c4e56
add new line in DontUseThreadSleepInAsyncCode.md
tmaczynski May 6, 2017
3b30f21
DontUseThreadSleep.md: add note that using reset event might be an al…
tmaczynski May 6, 2017
a49bd3a
use backticks to format code in md files
tmaczynski May 6, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion AsyncUsageAnalyzers.sln
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -32,6 +32,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "documentation", "documentat
ProjectSection(SolutionItems) = preProject
documentation\AvoidAsyncSuffix.md = documentation\AvoidAsyncSuffix.md
documentation\AvoidAsyncVoid.md = documentation\AvoidAsyncVoid.md
documentation\DontUseThreadSleep.md = documentation\DontUseThreadSleep.md
documentation\DontUseThreadSleepInAsyncCode.md = documentation\DontUseThreadSleepInAsyncCode.md
documentation\UseAsyncSuffix.md = documentation\UseAsyncSuffix.md
documentation\UseConfigureAwait.md = documentation\UseConfigureAwait.md
EndProjectSection
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
<Compile Include="Naming\AvoidAsyncSuffixCodeFixProvider.cs" />
<Compile Include="Naming\UseAsyncSuffixCodeFixProvider.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Usage\DontUseThreadSleepCodeUniversalCodeFixProvider.cs" />
<Compile Include="Usage\UseConfigureAwaitCodeFixProvider.cs" />
</ItemGroup>
<ItemGroup>
Expand Down Expand Up @@ -80,24 +81,24 @@
</AdditionalFiles>
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.CodeAnalysis, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.CodeAnalysis.Common.1.0.0\lib\portable-net45+win8\Microsoft.CodeAnalysis.dll</HintPath>
<Reference Include="Microsoft.CodeAnalysis, Version=1.3.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.CodeAnalysis.Common.1.3.2\lib\portable-net45+win8\Microsoft.CodeAnalysis.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.CodeAnalysis.CSharp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.CodeAnalysis.CSharp.1.0.0\lib\portable-net45+win8\Microsoft.CodeAnalysis.CSharp.dll</HintPath>
<Reference Include="Microsoft.CodeAnalysis.CSharp, Version=1.3.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.CodeAnalysis.CSharp.1.3.2\lib\portable-net45+win8\Microsoft.CodeAnalysis.CSharp.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.CodeAnalysis.CSharp.Workspaces, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.CodeAnalysis.CSharp.Workspaces.1.0.0\lib\portable-net45+win8\Microsoft.CodeAnalysis.CSharp.Workspaces.dll</HintPath>
<Reference Include="Microsoft.CodeAnalysis.CSharp.Workspaces, Version=1.3.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.CodeAnalysis.CSharp.Workspaces.1.3.2\lib\portable-net45+win8\Microsoft.CodeAnalysis.CSharp.Workspaces.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.CodeAnalysis.Workspaces, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.0.0\lib\portable-net45+win8\Microsoft.CodeAnalysis.Workspaces.dll</HintPath>
<Reference Include="Microsoft.CodeAnalysis.Workspaces, Version=1.3.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.3.2\lib\portable-net45+win8\Microsoft.CodeAnalysis.Workspaces.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Collections.Immutable, Version=1.1.36.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Collections.Immutable.1.1.36\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll</HintPath>
<Reference Include="System.Collections.Immutable, Version=1.1.37.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Collections.Immutable.1.1.37\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Composition.AttributedModel, Version=1.0.27.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
Expand All @@ -120,15 +121,15 @@
<HintPath>..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.TypedParts.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="System.Reflection.Metadata, Version=1.0.21.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Reflection.Metadata.1.0.21\lib\portable-net45+win8\System.Reflection.Metadata.dll</HintPath>
<Reference Include="System.Reflection.Metadata, Version=1.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Reflection.Metadata.1.2.0\lib\portable-net45+win8\System.Reflection.Metadata.dll</HintPath>
<Private>True</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Analyzer Include="..\..\packages\AsyncUsageAnalyzers.1.0.0-alpha003\analyzers\dotnet\AsyncUsageAnalyzers.dll" />
<Analyzer Include="..\..\packages\Microsoft.CodeAnalysis.Analyzers.1.0.0\analyzers\dotnet\cs\Microsoft.CodeAnalysis.Analyzers.dll" />
<Analyzer Include="..\..\packages\Microsoft.CodeAnalysis.Analyzers.1.0.0\analyzers\dotnet\cs\Microsoft.CodeAnalysis.CSharp.Analyzers.dll" />
<Analyzer Include="..\..\packages\Microsoft.CodeAnalysis.Analyzers.1.1.0\analyzers\dotnet\cs\Microsoft.CodeAnalysis.Analyzers.dll" />
<Analyzer Include="..\..\packages\Microsoft.CodeAnalysis.Analyzers.1.1.0\analyzers\dotnet\cs\Microsoft.CodeAnalysis.CSharp.Analyzers.dll" />
<Analyzer Include="..\..\packages\StyleCop.Analyzers.1.0.0-rc3\analyzers\dotnet\cs\Newtonsoft.Json.dll" />
<Analyzer Include="..\..\packages\StyleCop.Analyzers.1.0.0-rc3\analyzers\dotnet\cs\StyleCop.Analyzers.CodeFixes.dll" />
<Analyzer Include="..\..\packages\StyleCop.Analyzers.1.0.0-rc3\analyzers\dotnet\cs\StyleCop.Analyzers.dll" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.

namespace AsyncUsageAnalyzers.Usage
{
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Helpers;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;

[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(DontUseThreadSleepCodeUniversalCodeFixProvider))]
[Shared]
internal class DontUseThreadSleepCodeUniversalCodeFixProvider : CodeFixProvider
{
private static readonly ImmutableArray<string> FixableDiagnostics =
ImmutableArray.Create(DontUseThreadSleepAnalyzer.DiagnosticId, DontUseThreadSleepInAsyncCodeAnalyzer.DiagnosticId);

public override ImmutableArray<string> FixableDiagnosticIds => FixableDiagnostics;

/// <inheritdoc/>
public override FixAllProvider GetFixAllProvider()
{
return CustomFixAllProviders.BatchFixer;
}

public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
foreach (var diagnostic in context.Diagnostics)
{
if (diagnostic.Id == DontUseThreadSleepInAsyncCodeAnalyzer.DiagnosticId)
{
RegisterCodeFixForDiagnostic(context, diagnostic);
}
else if (diagnostic.Id == DontUseThreadSleepAnalyzer.DiagnosticId)
{
var document = context.Document;

var root = await document.GetSyntaxRootAsync().ConfigureAwait(false);
var invocationExpression = root.FindNode(TextSpan.FromBounds(diagnostic.Location.SourceSpan.Start, diagnostic.Location.SourceSpan.End), getInnermostNodeForTie: true) as InvocationExpressionSyntax;

if (invocationExpression == null)
{
return;
}

if (invocationExpression.IsInsideAsyncCode())
{
RegisterCodeFixForDiagnostic(context, diagnostic);
}
}
}
}

private static void RegisterCodeFixForDiagnostic(CodeFixContext context, Diagnostic diagnostic)
{
context.RegisterCodeFix(
CodeAction.Create(
"Use `await Task.Delay(...)` or `await Task.Yield()`",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason for having the same message for both here? How does VS render the backticks?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there were two separate messages for code fixes, semantic model would had to be awaited when the code fix is registered which would degrade performance. Currently, semantic model is awaited only if a user applies code fix. I don't think that user experience will be much better if there are two separate message.

I can change the code to include two separate code fix messages if you do think that it's worth it.

Backticks are displayed literally in VS2015 Update 2.

I believe that either quotes or backticks should be used to quote code - IMHO it improves readability. I used backticks because I wanted to be consistent with the AvoidAsyncSuffixCodeFixProvider. On the other hand no quotation characters are used in UseConfigureAwaitCodeFixProvider.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No idea if it is worth it, just looked a bit strange with a roulette type code fix :)

cancellationToken => GetTransformedDocumentAsync(context.Document, diagnostic, cancellationToken)),
diagnostic);
}

private static async Task<Document> 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()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know Roslyn well enough to know if firstNodeWithCorrectSpan is always not null here.

Copy link
Contributor Author

@tmaczynski tmaczynski Nov 6, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know Roslyn very well either, but I think that I can justify that expression and firstNodeWithCorrectSpan are not null.

  1. In DontUseThreadSleepAnalyzerBase, the SyntaxNodeAction is registered on SyntaxKind.InvocationExpression and reports analysis on InvocationExpression.Syntax's location
  2. I use diangnostic's location to assign value tofirstNodeWithCorrectSpan. firstNodeWithCorrectSpan is assigned using SyntaxNode.FindNode method which (by default) returns outermost node encompassing the given span (source: http://www.coderesx.com/roslyn/html/6497A5DA.htm).
  3. There must be a node of type InvocationExpressionSyntax among firstNodeWithCorrectSpan's descendant nodes because diagnostic was reported on using InvocationExpressionSyntax's location, so expression is not null.

.OfType<InvocationExpressionSyntax>()
.First();

var arguments = expression.ArgumentList;

var newExpression = await IsArgumentListWithZeroValueAsync(document, arguments).ConfigureAwait(true)
? GenerateTaskYieldExpression()
: GenerateTaskDelayExpression(arguments);

SyntaxNode newRoot = root.ReplaceNode(expression, newExpression.WithTriviaFrom(expression));
var newDocument = document.WithSyntaxRoot(newRoot);
return newDocument;
}

private static async Task<bool> IsArgumentListWithZeroValueAsync(Document document, ArgumentListSyntax argumentListSyntax)
{
// all valid overloads of Thread.Sleep() method take exactly one argument
if (argumentListSyntax.Arguments.Count != 1)
{
return false;
}

var argumentExpression = argumentListSyntax.Arguments.First().Expression;

var argumentString = argumentExpression.ToString().Trim();
if (argumentString == "0" || argumentString == "TimeSpan.Zero")
{
return true;
}

var semanticModel = await document.GetSemanticModelAsync().ConfigureAwait(false);
var optionalValue = semanticModel.GetConstantValue(argumentExpression);
if (optionalValue.HasValue && optionalValue.Value.Equals(0))
{
return true;
}

var memberAccessExpression = argumentExpression as MemberAccessExpressionSyntax;
if (memberAccessExpression != null)
{
IFieldSymbol propertySymbol = null;
return memberAccessExpression.TryGetFieldSymbolByTypeNameAndMethodName(semanticModel, "System.TimeSpan", "Zero", out propertySymbol);
}

return false;
}

private static AwaitExpressionSyntax GenerateTaskDelayExpression(ArgumentListSyntax methodArgumentList) =>
SyntaxFactory.AwaitExpression(
SyntaxFactory.InvocationExpression(
SyntaxFactory.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
SyntaxFactory.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
SyntaxFactory.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
SyntaxFactory.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
SyntaxFactory.IdentifierName("System"),
SyntaxFactory.IdentifierName("Threading")),
SyntaxFactory.IdentifierName("Tasks")),
SyntaxFactory.IdentifierName("Task")),
SyntaxFactory.IdentifierName("Delay")))
.WithArgumentList(methodArgumentList));

private static AwaitExpressionSyntax GenerateTaskYieldExpression() =>
SyntaxFactory.AwaitExpression(
SyntaxFactory.InvocationExpression(
SyntaxFactory.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
SyntaxFactory.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
SyntaxFactory.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
SyntaxFactory.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
SyntaxFactory.IdentifierName("System"),
SyntaxFactory.IdentifierName("Threading")),
SyntaxFactory.IdentifierName("Tasks")),
SyntaxFactory.IdentifierName("Task")),
SyntaxFactory.IdentifierName("Yield"))));
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="AsyncUsageAnalyzers" version="1.0.0-alpha003" targetFramework="portable45-net45+win8" developmentDependency="true" />
<package id="Microsoft.CodeAnalysis.Analyzers" version="1.0.0" targetFramework="portable45-net45+win8" />
<package id="Microsoft.CodeAnalysis.Common" version="1.0.0" targetFramework="portable45-net45+win8" />
<package id="Microsoft.CodeAnalysis.CSharp" version="1.0.0" targetFramework="portable45-net45+win8" />
<package id="Microsoft.CodeAnalysis.CSharp.Workspaces" version="1.0.0" targetFramework="portable45-net45+win8" />
<package id="Microsoft.CodeAnalysis.Workspaces.Common" version="1.0.0" targetFramework="portable45-net45+win8" />
<package id="Microsoft.CodeAnalysis.Analyzers" version="1.1.0" targetFramework="portable45-net45+win8" />
<package id="Microsoft.CodeAnalysis.Common" version="1.3.2" targetFramework="portable45-net45+win8" />
<package id="Microsoft.CodeAnalysis.CSharp" version="1.3.2" targetFramework="portable45-net45+win8" />
<package id="Microsoft.CodeAnalysis.CSharp.Workspaces" version="1.3.2" targetFramework="portable45-net45+win8" />
<package id="Microsoft.CodeAnalysis.Workspaces.Common" version="1.3.2" targetFramework="portable45-net45+win8" />
<package id="Microsoft.Composition" version="1.0.27" targetFramework="portable45-net45+win8" />
<package id="NuGet.CommandLine" version="2.8.3" targetFramework="portable45-net45+win8" />
<package id="StyleCop.Analyzers" version="1.0.0-rc3" targetFramework="portable45-net45+win8" developmentDependency="true" />
<package id="System.Collections.Immutable" version="1.1.36" targetFramework="portable45-net45+win8" />
<package id="System.Reflection.Metadata" version="1.0.21" targetFramework="portable45-net45+win8" />
<package id="System.Collections.Immutable" version="1.1.37" targetFramework="portable45-net45+win8" />
<package id="System.Reflection.Metadata" version="1.2.0" targetFramework="portable45-net45+win8" />
<package id="Tvl.NuGet.BuildTasks" version="1.0.0-alpha002" targetFramework="portable45-net45+win8" />
</packages>
Loading