├── .gitattributes ├── .gitignore ├── Asyncify.sln ├── Asyncify ├── Asyncify.Test │ ├── Asyncify.Test.csproj │ ├── BaseAnalyzerFixTest.cs │ ├── Helpers │ │ ├── CodeFixVerifier.Helper.cs │ │ ├── DiagnosticResult.cs │ │ └── DiagnosticVerifier.Helper.cs │ ├── InvocationAnalyzerFixTest.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── VariableAccessAnalyzerFixTest.cs │ ├── Verifiers │ │ ├── CodeFixVerifier.cs │ │ └── DiagnosticVerifier.cs │ └── packages.config ├── Asyncify.Vsix │ ├── Asyncify.Vsix.csproj │ └── source.extension.vsixmanifest └── Asyncify │ ├── Asyncify.csproj │ ├── Asyncify.nuspec │ ├── BaseAsyncifyFixer.cs │ ├── ExtensionMethods.cs │ ├── InvocationAnalyzer.cs │ ├── InvocationChecker.cs │ ├── InvocationFixProvider.cs │ ├── LambdaFixProvider.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── ReadMe.txt │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── VariableAccessAnalyzer.cs │ ├── VariableAccessChecker.cs │ ├── VariableAccessFixProvider.cs │ ├── packages.config │ └── tools │ ├── install.ps1 │ └── uninstall.ps1 ├── LICENSE.MD └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #OS junk files 2 | [Tt]humbs.db 3 | *.DS_Store 4 | 5 | #Visual Studio files 6 | *.[Oo]bj 7 | *.user 8 | *.aps 9 | *.pch 10 | *.vspscc 11 | *.vssscc 12 | *_i.c 13 | *_p.c 14 | *.ncb 15 | *.suo 16 | *.tlb 17 | *.tlh 18 | *.bak 19 | *.[Cc]ache 20 | *.ilk 21 | *.log 22 | *.lib 23 | *.sbr 24 | *.sdf 25 | *.opensdf 26 | *.unsuccessfulbuild 27 | ipch/ 28 | [Oo]bj/ 29 | [Bb]in 30 | [Dd]ebug*/ 31 | [Rr]elease*/ 32 | Ankh.NoLoad 33 | 34 | #MonoDevelop 35 | *.pidb 36 | *.userprefs 37 | 38 | #Tooling 39 | _ReSharper*/ 40 | *.resharper 41 | [Tt]est[Rr]esult* 42 | *.sass-cache 43 | 44 | #Project files 45 | [Bb]uild/ 46 | 47 | #Subversion files 48 | .svn 49 | 50 | # Office Temp Files 51 | ~$* 52 | 53 | # vim Temp Files 54 | *~ 55 | 56 | #NuGet 57 | packages/ 58 | *.nupkg 59 | 60 | #ncrunch 61 | *ncrunch* 62 | *crunch*.local.xml 63 | 64 | # visual studio database projects 65 | *.dbmdl 66 | 67 | #Test files 68 | *.testsettings -------------------------------------------------------------------------------- /Asyncify.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.23107.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Asyncify", "Asyncify\Asyncify\Asyncify.csproj", "{C15B2F4D-99C3-4833-A9D0-32FFD969C248}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Asyncify.Test", "Asyncify\Asyncify.Test\Asyncify.Test.csproj", "{081DE494-482A-4B63-8C58-3FF52B548258}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Asyncify.Vsix", "Asyncify\Asyncify.Vsix\Asyncify.Vsix.csproj", "{86D26DFA-92B1-4F33-B350-F3302CD80239}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {C15B2F4D-99C3-4833-A9D0-32FFD969C248}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {C15B2F4D-99C3-4833-A9D0-32FFD969C248}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {C15B2F4D-99C3-4833-A9D0-32FFD969C248}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {C15B2F4D-99C3-4833-A9D0-32FFD969C248}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {081DE494-482A-4B63-8C58-3FF52B548258}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {081DE494-482A-4B63-8C58-3FF52B548258}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {081DE494-482A-4B63-8C58-3FF52B548258}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {081DE494-482A-4B63-8C58-3FF52B548258}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {86D26DFA-92B1-4F33-B350-F3302CD80239}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {86D26DFA-92B1-4F33-B350-F3302CD80239}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {86D26DFA-92B1-4F33-B350-F3302CD80239}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {86D26DFA-92B1-4F33-B350-F3302CD80239}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /Asyncify/Asyncify.Test/Asyncify.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 8.0.30703 7 | 2.0 8 | {081DE494-482A-4B63-8C58-3FF52B548258} 9 | Library 10 | Properties 11 | Asyncify.Test 12 | Asyncify.Test 13 | v4.5.2 14 | 512 15 | 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | false 26 | 27 | 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | false 35 | 36 | 37 | 38 | ..\..\packages\Microsoft.CodeAnalysis.Common.1.0.0\lib\net45\Microsoft.CodeAnalysis.dll 39 | True 40 | 41 | 42 | ..\..\packages\Microsoft.CodeAnalysis.CSharp.1.0.0\lib\net45\Microsoft.CodeAnalysis.CSharp.dll 43 | True 44 | 45 | 46 | ..\..\packages\Microsoft.CodeAnalysis.CSharp.Workspaces.1.0.0\lib\net45\Microsoft.CodeAnalysis.CSharp.Workspaces.dll 47 | True 48 | 49 | 50 | ..\..\packages\Microsoft.CodeAnalysis.VisualBasic.1.0.0\lib\net45\Microsoft.CodeAnalysis.VisualBasic.dll 51 | True 52 | 53 | 54 | ..\..\packages\Microsoft.CodeAnalysis.VisualBasic.Workspaces.1.0.0\lib\net45\Microsoft.CodeAnalysis.VisualBasic.Workspaces.dll 55 | True 56 | 57 | 58 | ..\..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.0.0\lib\net45\Microsoft.CodeAnalysis.Workspaces.dll 59 | True 60 | 61 | 62 | ..\..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.0.0\lib\net45\Microsoft.CodeAnalysis.Workspaces.Desktop.dll 63 | True 64 | 65 | 66 | 67 | ..\..\packages\System.Collections.Immutable.1.1.36\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll 68 | True 69 | 70 | 71 | ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.AttributedModel.dll 72 | True 73 | 74 | 75 | ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Convention.dll 76 | True 77 | 78 | 79 | ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Hosting.dll 80 | True 81 | 82 | 83 | ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Runtime.dll 84 | True 85 | 86 | 87 | ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.TypedParts.dll 88 | True 89 | 90 | 91 | 92 | ..\..\packages\System.Reflection.Metadata.1.0.21\lib\portable-net45+win8\System.Reflection.Metadata.dll 93 | True 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | false 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | {C15B2F4D-99C3-4833-A9D0-32FFD969C248} 121 | Asyncify 122 | 123 | 124 | 125 | 126 | 127 | 128 | 135 | -------------------------------------------------------------------------------- /Asyncify/Asyncify.Test/BaseAnalyzerFixTest.cs: -------------------------------------------------------------------------------- 1 | using TestHelper; 2 | 3 | namespace Asyncify.Test 4 | { 5 | public abstract class BaseAnalyzerFixTest : CodeFixVerifier 6 | { 7 | public abstract string DiagnosticId { get; } 8 | 9 | protected static readonly DiagnosticResult[] EmptyExpectedResults = new DiagnosticResult[0]; 10 | protected const string AsyncTaskOfTMethod = @" 11 | public async Task CallAsync() 12 | { 13 | await Task.Delay(1000); 14 | return 0; 15 | }"; 16 | protected const string AsyncTaskMethod = @" 17 | public async Task CallAsync() 18 | { 19 | await Task.Delay(1000); 20 | }"; 21 | protected const string SyncTaskOfTMethod = @" 22 | public Task CallAsync() 23 | { 24 | return Task.FromResult(0); 25 | }"; 26 | protected const string SyncTaskMethod = @" 27 | public Task CallAsync() 28 | { 29 | return Task.FromResult(0); 30 | }"; 31 | 32 | protected const string FormatCode = @" 33 | using System.Linq; 34 | using System.Threading.Tasks; 35 | 36 | public class TapTest 37 | {{ 38 | {0} 39 | 40 | {1} 41 | }} 42 | 43 | public class AsyncClass 44 | {{ 45 | public async Task Call() 46 | {{ 47 | await Task.Delay(100); 48 | return 0; 49 | }} 50 | }} 51 | "; 52 | 53 | protected DiagnosticResult GetResultWithLocation(int line, int column) 54 | { 55 | var expected = new DiagnosticResult 56 | { 57 | Id = DiagnosticId, 58 | Locations = 59 | new[] 60 | { 61 | new DiagnosticResultLocation("Test0.cs", line, column) 62 | } 63 | }; 64 | return expected; 65 | } 66 | 67 | protected void VerifyCodeFixWithReturn(string test, string fix, params DiagnosticResult[] expectedResults) 68 | { 69 | var oldSource = string.Format(FormatCode, test, AsyncTaskOfTMethod); 70 | VerifyCSharpDiagnostic(oldSource, expectedResults); 71 | var fixSource = string.Format(FormatCode, fix, AsyncTaskOfTMethod); 72 | VerifyCSharpFix(oldSource, fixSource); 73 | 74 | oldSource = string.Format(FormatCode, test, SyncTaskOfTMethod); 75 | VerifyCSharpDiagnostic(oldSource, expectedResults); 76 | fixSource = string.Format(FormatCode, fix, SyncTaskOfTMethod); 77 | VerifyCSharpFix(oldSource, fixSource); 78 | } 79 | 80 | protected void VerifyCodeFixNoReturn(string test, string fix, params DiagnosticResult[] expectedResults) 81 | { 82 | var oldSource = string.Format(FormatCode, test, AsyncTaskMethod); 83 | VerifyCSharpDiagnostic(oldSource, expectedResults); 84 | var fixSource = string.Format(FormatCode, fix, AsyncTaskMethod); 85 | VerifyCSharpFix(oldSource, fixSource); 86 | 87 | oldSource = string.Format(FormatCode, test, SyncTaskMethod); 88 | VerifyCSharpDiagnostic(oldSource, expectedResults); 89 | fixSource = string.Format(FormatCode, fix, SyncTaskMethod); 90 | VerifyCSharpFix(oldSource, fixSource); 91 | } 92 | 93 | protected void VerifyCodeWithReturn(string test, params DiagnosticResult[] expectedResults) 94 | { 95 | VerifyCSharpDiagnostic(string.Format(FormatCode, test, AsyncTaskOfTMethod), expectedResults); 96 | VerifyCSharpDiagnostic(string.Format(FormatCode, test, SyncTaskOfTMethod), expectedResults); 97 | } 98 | 99 | protected void VerifyCodeNoReturn(string test, params DiagnosticResult[] expectedResults) 100 | { 101 | VerifyCSharpDiagnostic(string.Format(FormatCode, test, AsyncTaskMethod), expectedResults); 102 | VerifyCSharpDiagnostic(string.Format(FormatCode, test, SyncTaskMethod), expectedResults); 103 | } 104 | 105 | } 106 | } -------------------------------------------------------------------------------- /Asyncify/Asyncify.Test/Helpers/CodeFixVerifier.Helper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CodeActions; 3 | using Microsoft.CodeAnalysis.Formatting; 4 | using Microsoft.CodeAnalysis.Simplification; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading; 8 | 9 | namespace TestHelper 10 | { 11 | /// 12 | /// Diagnostic Producer class with extra methods dealing with applying codefixes 13 | /// All methods are static 14 | /// 15 | public abstract partial class CodeFixVerifier : DiagnosticVerifier 16 | { 17 | /// 18 | /// Apply the inputted CodeAction to the inputted document. 19 | /// Meant to be used to apply codefixes. 20 | /// 21 | /// The Document to apply the fix on 22 | /// A CodeAction that will be applied to the Document. 23 | /// A Document with the changes from the CodeAction 24 | private static Document ApplyFix(Document document, CodeAction codeAction) 25 | { 26 | var operations = codeAction.GetOperationsAsync(CancellationToken.None).Result; 27 | var solution = operations.OfType().Single().ChangedSolution; 28 | return solution.GetDocument(document.Id); 29 | } 30 | 31 | /// 32 | /// Compare two collections of Diagnostics,and return a list of any new diagnostics that appear only in the second collection. 33 | /// Note: Considers Diagnostics to be the same if they have the same Ids. In the case of multiple diagnostics with the same Id in a row, 34 | /// this method may not necessarily return the new one. 35 | /// 36 | /// The Diagnostics that existed in the code before the CodeFix was applied 37 | /// The Diagnostics that exist in the code after the CodeFix was applied 38 | /// A list of Diagnostics that only surfaced in the code after the CodeFix was applied 39 | private static IEnumerable GetNewDiagnostics(IEnumerable diagnostics, IEnumerable newDiagnostics) 40 | { 41 | var oldArray = diagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray(); 42 | var newArray = newDiagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray(); 43 | 44 | int oldIndex = 0; 45 | int newIndex = 0; 46 | 47 | while (newIndex < newArray.Length) 48 | { 49 | if (oldIndex < oldArray.Length && oldArray[oldIndex].Id == newArray[newIndex].Id) 50 | { 51 | ++oldIndex; 52 | ++newIndex; 53 | } 54 | else 55 | { 56 | yield return newArray[newIndex++]; 57 | } 58 | } 59 | } 60 | 61 | /// 62 | /// Get the existing compiler diagnostics on the inputted document. 63 | /// 64 | /// The Document to run the compiler diagnostic analyzers on 65 | /// The compiler diagnostics that were found in the code 66 | private static IEnumerable GetCompilerDiagnostics(Document document) 67 | { 68 | return document.GetSemanticModelAsync().Result.GetDiagnostics(); 69 | } 70 | 71 | /// 72 | /// Given a document, turn it into a string based on the syntax root 73 | /// 74 | /// The Document to be converted to a string 75 | /// A string containing the syntax of the Document after formatting 76 | private static string GetStringFromDocument(Document document) 77 | { 78 | var simplifiedDoc = Simplifier.ReduceAsync(document, Simplifier.Annotation).Result; 79 | var root = simplifiedDoc.GetSyntaxRootAsync().Result; 80 | root = Formatter.Format(root, Formatter.Annotation, simplifiedDoc.Project.Solution.Workspace); 81 | return root.GetText().ToString(); 82 | } 83 | } 84 | } 85 | 86 | -------------------------------------------------------------------------------- /Asyncify/Asyncify.Test/Helpers/DiagnosticResult.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using System; 3 | 4 | namespace TestHelper 5 | { 6 | /// 7 | /// Location where the diagnostic appears, as determined by path, line number, and column number. 8 | /// 9 | public struct DiagnosticResultLocation 10 | { 11 | public DiagnosticResultLocation(string path, int line, int column) 12 | { 13 | if (line < -1) 14 | { 15 | throw new ArgumentOutOfRangeException(nameof(line), "line must be >= -1"); 16 | } 17 | 18 | if (column < -1) 19 | { 20 | throw new ArgumentOutOfRangeException(nameof(line), "column must be >= -1"); 21 | } 22 | 23 | this.Path = path; 24 | this.Line = line; 25 | this.Column = column; 26 | } 27 | 28 | public string Path { get; } 29 | public int Line { get; } 30 | public int Column { get; } 31 | } 32 | 33 | /// 34 | /// Struct that stores information about a Diagnostic appearing in a source 35 | /// 36 | public struct DiagnosticResult 37 | { 38 | private DiagnosticResultLocation[] locations; 39 | 40 | public DiagnosticResultLocation[] Locations 41 | { 42 | get 43 | { 44 | if (this.locations == null) 45 | { 46 | this.locations = new DiagnosticResultLocation[] { }; 47 | } 48 | return this.locations; 49 | } 50 | 51 | set 52 | { 53 | this.locations = value; 54 | } 55 | } 56 | 57 | public DiagnosticSeverity Severity { get; set; } 58 | 59 | public string Id { get; set; } 60 | 61 | public string Message { get; set; } 62 | 63 | public string Path 64 | { 65 | get 66 | { 67 | return this.Locations.Length > 0 ? this.Locations[0].Path : ""; 68 | } 69 | } 70 | 71 | public int Line 72 | { 73 | get 74 | { 75 | return this.Locations.Length > 0 ? this.Locations[0].Line : -1; 76 | } 77 | } 78 | 79 | public int Column 80 | { 81 | get 82 | { 83 | return this.Locations.Length > 0 ? this.Locations[0].Column : -1; 84 | } 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /Asyncify/Asyncify.Test/Helpers/DiagnosticVerifier.Helper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CSharp; 3 | using Microsoft.CodeAnalysis.Diagnostics; 4 | using Microsoft.CodeAnalysis.Text; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Collections.Immutable; 8 | using System.Linq; 9 | using Microsoft.VisualStudio.TestTools.UnitTesting; 10 | 11 | namespace TestHelper 12 | { 13 | /// 14 | /// Class for turning strings into documents and getting the diagnostics on them 15 | /// All methods are static 16 | /// 17 | public abstract partial class DiagnosticVerifier 18 | { 19 | private static readonly MetadataReference CorlibReference = MetadataReference.CreateFromFile(typeof(object).Assembly.Location); 20 | private static readonly MetadataReference SystemCoreReference = MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location); 21 | private static readonly MetadataReference CSharpSymbolsReference = MetadataReference.CreateFromFile(typeof(CSharpCompilation).Assembly.Location); 22 | private static readonly MetadataReference CodeAnalysisReference = MetadataReference.CreateFromFile(typeof(Compilation).Assembly.Location); 23 | 24 | internal static string DefaultFilePathPrefix = "Test"; 25 | internal static string CSharpDefaultFileExt = "cs"; 26 | internal static string VisualBasicDefaultExt = "vb"; 27 | internal static string TestProjectName = "TestProject"; 28 | 29 | #region Get Diagnostics 30 | 31 | /// 32 | /// Given classes in the form of strings, their language, and an IDiagnosticAnlayzer to apply to it, return the diagnostics found in the string after converting it to a document. 33 | /// 34 | /// Classes in the form of strings 35 | /// The language the source classes are in 36 | /// The analyzer to be run on the sources 37 | /// An IEnumerable of Diagnostics that surfaced in the source code, sorted by Location 38 | private Diagnostic[] GetSortedDiagnostics(string[] sources, string language, DiagnosticAnalyzer analyzer) 39 | { 40 | return GetSortedDiagnosticsFromDocuments(analyzer, GetDocuments(sources, language)); 41 | } 42 | 43 | /// 44 | /// Given an analyzer and a document to apply it to, run the analyzer and gather an array of diagnostics found in it. 45 | /// The returned diagnostics are then ordered by location in the source document. 46 | /// 47 | /// The analyzer to run on the documents 48 | /// The Documents that the analyzer will be run on 49 | /// An IEnumerable of Diagnostics that surfaced in the source code, sorted by Location 50 | protected static Diagnostic[] GetSortedDiagnosticsFromDocuments(DiagnosticAnalyzer analyzer, Document[] documents) 51 | { 52 | var projects = new HashSet(); 53 | foreach (var document in documents) 54 | { 55 | projects.Add(document.Project); 56 | } 57 | 58 | var diagnostics = new List(); 59 | foreach (var project in projects) 60 | { 61 | var compilation = project.GetCompilationAsync().Result; 62 | CheckForCompilationErrors(documents, compilation); 63 | 64 | var compilationWithAnalyzers = compilation.WithAnalyzers(ImmutableArray.Create(analyzer)); 65 | 66 | var diags = compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync().Result; 67 | foreach (var diag in diags) 68 | { 69 | if (diag.Location == Location.None || diag.Location.IsInMetadata) 70 | { 71 | diagnostics.Add(diag); 72 | } 73 | else 74 | { 75 | for (int i = 0; i < documents.Length; i++) 76 | { 77 | var document = documents[i]; 78 | var tree = document.GetSyntaxTreeAsync().Result; 79 | if (tree == diag.Location.SourceTree) 80 | { 81 | diagnostics.Add(diag); 82 | } 83 | } 84 | } 85 | } 86 | } 87 | 88 | var results = SortDiagnostics(diagnostics); 89 | diagnostics.Clear(); 90 | return results; 91 | } 92 | 93 | private static void CheckForCompilationErrors(Document[] documents, Compilation compilation) 94 | { 95 | var compilerDiagnostics = compilation.GetDiagnostics() 96 | .Where(x => x.Severity == DiagnosticSeverity.Error).ToArray(); 97 | 98 | if (compilerDiagnostics.Any()) 99 | { 100 | Assert.Fail(@"Compilation errors in code: 101 | " + string.Join("\r\n", compilerDiagnostics.Select(x => x.GetMessage())) + @" 102 | 103 | Code compiled: 104 | " + string.Join("\r\n", documents.Select(x => x.GetTextAsync().Result))); 105 | } 106 | } 107 | 108 | /// 109 | /// Sort diagnostics by location in source document 110 | /// 111 | /// The list of Diagnostics to be sorted 112 | /// An IEnumerable containing the Diagnostics in order of Location 113 | private static Diagnostic[] SortDiagnostics(IEnumerable diagnostics) 114 | { 115 | return diagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray(); 116 | } 117 | 118 | #endregion 119 | 120 | #region Set up compilation and documents 121 | /// 122 | /// Given an array of strings as sources and a language, turn them into a project and return the documents and spans of it. 123 | /// 124 | /// Classes in the form of strings 125 | /// The language the source code is in 126 | /// A Tuple containing the Documents produced from the sources and their TextSpans if relevant 127 | private Document[] GetDocuments(string[] sources, string language) 128 | { 129 | if (language != LanguageNames.CSharp && language != LanguageNames.VisualBasic) 130 | { 131 | throw new ArgumentException("Unsupported Language"); 132 | } 133 | 134 | var project = CreateProject(sources, language); 135 | var documents = project.Documents.ToArray(); 136 | 137 | if (sources.Length != documents.Length) 138 | { 139 | throw new SystemException("Amount of sources did not match amount of Documents created"); 140 | } 141 | 142 | return documents; 143 | } 144 | 145 | /// 146 | /// Create a Document from a string through creating a project that contains it. 147 | /// 148 | /// Classes in the form of a string 149 | /// The language the source code is in 150 | /// A Document created from the source string 151 | protected Document CreateDocument(string source, string language = LanguageNames.CSharp) 152 | { 153 | return CreateProject(new[] { source }, language).Documents.First(); 154 | } 155 | 156 | /// 157 | /// Create a project using the inputted strings as sources. 158 | /// 159 | /// Classes in the form of strings 160 | /// The language the source code is in 161 | /// A Project created out of the Documents created from the source strings 162 | private Project CreateProject(string[] sources, string language = LanguageNames.CSharp) 163 | { 164 | string fileNamePrefix = DefaultFilePathPrefix; 165 | string fileExt = language == LanguageNames.CSharp ? CSharpDefaultFileExt : VisualBasicDefaultExt; 166 | 167 | var projectId = ProjectId.CreateNewId(debugName: TestProjectName); 168 | 169 | var solution = new AdhocWorkspace() 170 | .CurrentSolution 171 | .AddProject(projectId, TestProjectName, TestProjectName, language) 172 | .WithProjectCompilationOptions(projectId, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)) 173 | .AddMetadataReference(projectId, CorlibReference) 174 | .AddMetadataReference(projectId, SystemCoreReference) 175 | .AddMetadataReference(projectId, CSharpSymbolsReference) 176 | .AddMetadataReference(projectId, CodeAnalysisReference); 177 | 178 | int count = 0; 179 | foreach (var source in sources) 180 | { 181 | var newFileName = fileNamePrefix + count + "." + fileExt; 182 | var documentId = DocumentId.CreateNewId(projectId, debugName: newFileName); 183 | solution = solution.AddDocument(documentId, newFileName, SourceText.From(source)); 184 | count++; 185 | } 186 | return solution.GetProject(projectId); 187 | } 188 | #endregion 189 | } 190 | } 191 | 192 | -------------------------------------------------------------------------------- /Asyncify/Asyncify.Test/InvocationAnalyzerFixTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis.CodeFixes; 2 | using Microsoft.CodeAnalysis.Diagnostics; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | 5 | namespace Asyncify.Test 6 | { 7 | [TestClass] 8 | public class InvocationAnalyzerFixTest : BaseAnalyzerFixTest 9 | { 10 | //No diagnostics expected to show up 11 | [TestMethod] 12 | public void DoesNotViolateOnCorrectUseOfTap() 13 | { 14 | VerifyCodeWithReturn(@" 15 | public async Task Test() 16 | { 17 | await CallAsync(); 18 | }", EmptyExpectedResults); 19 | VerifyCodeWithReturn(@" 20 | public async Task Test() 21 | { 22 | CallAsync(); 23 | }", EmptyExpectedResults); 24 | } 25 | 26 | //No diagnostics expected to show up 27 | [TestMethod] 28 | public void DoesNotViolateOnNonTapUseWithinLock() 29 | { 30 | VerifyCodeWithReturn(@" 31 | public async Task Test() 32 | { 33 | var obj = new object(); 34 | lock(obj) 35 | { 36 | var result = CallAsync().Result; 37 | } 38 | }", EmptyExpectedResults); 39 | VerifyCodeWithReturn(@" 40 | public async Task Test() 41 | { 42 | var obj = new object(); 43 | lock(obj) 44 | { 45 | CallAsync(); 46 | } 47 | }", EmptyExpectedResults); 48 | } 49 | 50 | [TestMethod] 51 | public void CanFindMethodNotUsingTap() 52 | { 53 | VerifyCodeWithReturn(@" 54 | public void Test() 55 | { 56 | var result = CallAsync().Result; 57 | }", GetResultWithLocation(10, 22)); 58 | VerifyCodeFixWithReturn(@" 59 | public void Test() 60 | { 61 | var result = CallAsync().Result; 62 | }", @" 63 | public async System.Threading.Tasks.Task Test() 64 | { 65 | var result = await CallAsync(); 66 | }", GetResultWithLocation(10, 22)); 67 | } 68 | 69 | [TestMethod] 70 | public void CanFindViolationInMethodUsingTap() 71 | { 72 | VerifyCodeWithReturn(@" 73 | public async Task Test() 74 | { 75 | var temp = await CallAsync(); 76 | var result = CallAsync().Result; 77 | }", GetResultWithLocation(11, 22)); 78 | } 79 | 80 | [TestMethod] 81 | public void DoesNotViolateOnMethodsWithOutOrRef() 82 | { 83 | VerifyCodeWithReturn(@" 84 | public void Test(out string test) 85 | { 86 | test = string.Empty; 87 | var result = CallAsync().Result; 88 | }", EmptyExpectedResults); 89 | } 90 | 91 | [TestMethod] 92 | public void DoesNotAddAwaitToVoidMethods() 93 | { 94 | VerifyCodeWithReturn(@" 95 | public void Test() 96 | { 97 | CallAsync().Wait(); 98 | }", EmptyExpectedResults); 99 | } 100 | 101 | [TestMethod] 102 | public void CanFindMethodWhenUsingBraces() 103 | { 104 | VerifyCodeWithReturn(@" 105 | public void Test() 106 | { 107 | var result = (CallAsync()).Result; 108 | }", GetResultWithLocation(10, 23)); 109 | VerifyCodeFixWithReturn(@" 110 | public void Test() 111 | { 112 | var result = (CallAsync()).Result; 113 | }", @" 114 | public async System.Threading.Tasks.Task Test() 115 | { 116 | var result = (await CallAsync()); 117 | }", GetResultWithLocation(10, 23)); 118 | } 119 | 120 | [TestMethod] 121 | public void NoViolationOnAsyncMethodsWrappedInVoidCall() 122 | { 123 | VerifyCSharpDiagnostic(string.Format(FormatCode, @" 124 | public void FirstLevelUp() 125 | { 126 | Test().Wait(); 127 | } 128 | 129 | public Task Test() 130 | { 131 | return Task.FromResult(0); 132 | }", string.Empty), EmptyExpectedResults); 133 | } 134 | 135 | [TestMethod] 136 | public void FixIsAppliedUpCallTree() 137 | { 138 | var oldSource = string.Format(FormatCode, @" 139 | public int SecondLevelUp() 140 | { 141 | return FirstLevelUp(); 142 | } 143 | 144 | public int FirstLevelUp() 145 | { 146 | return Test(); 147 | } 148 | 149 | public int Test() 150 | { 151 | var test = new AsyncClass(); 152 | return test.Call().Result; 153 | }", string.Empty); 154 | var newSource = string.Format(FormatCode, @" 155 | public async System.Threading.Tasks.Task SecondLevelUp() 156 | { 157 | return await FirstLevelUp(); 158 | } 159 | 160 | public async System.Threading.Tasks.Task FirstLevelUp() 161 | { 162 | return await Test(); 163 | } 164 | 165 | public async System.Threading.Tasks.Task Test() 166 | { 167 | var test = new AsyncClass(); 168 | return await test.Call(); 169 | }", string.Empty); 170 | VerifyCSharpFix(oldSource, newSource); 171 | } 172 | 173 | 174 | 175 | [TestMethod] 176 | public void FixIsAppliedUpCallTreeStopsAtOutRefParams() 177 | { 178 | var oldSource = string.Format(FormatCode, @" 179 | public int SecondLevelUp(out string test) 180 | { 181 | test = string.Empty; 182 | return FirstLevelUp(); 183 | } 184 | 185 | public int FirstLevelUp() 186 | { 187 | return Test(); 188 | } 189 | 190 | public int Test() 191 | { 192 | var test = new AsyncClass(); 193 | return test.Call().Result; 194 | }", string.Empty); 195 | var newSource = string.Format(FormatCode, @" 196 | public int SecondLevelUp(out string test) 197 | { 198 | test = string.Empty; 199 | return FirstLevelUp().Result; 200 | } 201 | 202 | public async System.Threading.Tasks.Task FirstLevelUp() 203 | { 204 | return await Test(); 205 | } 206 | 207 | public async System.Threading.Tasks.Task Test() 208 | { 209 | var test = new AsyncClass(); 210 | return await test.Call(); 211 | }", string.Empty); 212 | VerifyCSharpFix(oldSource, newSource); 213 | } 214 | 215 | [TestMethod] 216 | public void TestCodeFixWithReturnType() 217 | { 218 | var oldSource = string.Format(FormatCode, @" 219 | public int Test() 220 | { 221 | var test = new AsyncClass(); 222 | return test.Call().Result; 223 | }", string.Empty); 224 | var newSource = string.Format(FormatCode, @" 225 | public async System.Threading.Tasks.Task Test() 226 | { 227 | var test = new AsyncClass(); 228 | return await test.Call(); 229 | }", string.Empty); 230 | VerifyCSharpFix(oldSource, newSource); 231 | } 232 | 233 | [TestMethod] 234 | public void TestCodeFixWithInstanceCall() 235 | { 236 | var oldSource = string.Format(FormatCode, @" 237 | public void Test() 238 | { 239 | var test = new AsyncClass(); 240 | var result = test.Call().Result; 241 | }", string.Empty); 242 | var newSource = string.Format(FormatCode, @" 243 | public async System.Threading.Tasks.Task Test() 244 | { 245 | var test = new AsyncClass(); 246 | var result = await test.Call(); 247 | }", string.Empty); 248 | VerifyCSharpFix(oldSource, newSource); 249 | } 250 | 251 | [TestMethod] 252 | public void TestCodeFixWithinLambda() 253 | { 254 | var oldSource = string.Format(FormatCode, @" 255 | public void Test() 256 | { 257 | int[] bla = null; 258 | bla.Select(x => Task.FromResult(100).Result); 259 | }", string.Empty); 260 | var newSource = string.Format(FormatCode, @" 261 | public void Test() 262 | { 263 | int[] bla = null; 264 | bla.Select(async x => await Task.FromResult(100)); 265 | }", string.Empty); 266 | VerifyCSharpFix(oldSource, newSource); 267 | } 268 | 269 | [TestMethod] 270 | public void TestCodeFixWithinParenthesizedLambda() 271 | { 272 | var oldSource = string.Format(FormatCode, @" 273 | public void Test() 274 | { 275 | System.Action a = () => CallAsync(); 276 | } 277 | 278 | public int CallAsync() 279 | { 280 | return Task.FromResult(0).Result; 281 | } 282 | ", string.Empty); 283 | var newSource = string.Format(FormatCode, @" 284 | public void Test() 285 | { 286 | System.Action a = async () => await CallAsync(); 287 | } 288 | 289 | public async System.Threading.Tasks.Task CallAsync() 290 | { 291 | return await Task.FromResult(0); 292 | } 293 | ", string.Empty); 294 | VerifyCSharpFix(oldSource, newSource); 295 | } 296 | 297 | [TestMethod] 298 | public void FixWillWrapInParenthesesIfNeeded() 299 | { 300 | var oldSource = string.Format(FormatCode, @" 301 | public void Test() 302 | { 303 | var test = new AsyncClass(); 304 | var result = test.Call().Result.ToString(); 305 | }", string.Empty); 306 | var newSource = string.Format(FormatCode, @" 307 | public async System.Threading.Tasks.Task Test() 308 | { 309 | var test = new AsyncClass(); 310 | var result = (await test.Call()).ToString(); 311 | }", string.Empty); 312 | VerifyCSharpFix(oldSource, newSource); 313 | } 314 | 315 | [TestMethod] 316 | public void WillAddAsyncToVoidMethodInCodefix() 317 | { 318 | var oldSource = string.Format(FormatCode, @" 319 | public void VoidCallingMethod() 320 | { 321 | Test(); 322 | } 323 | 324 | public void Test() 325 | { 326 | var test = new AsyncClass(); 327 | var result = test.Call().Result; 328 | }", string.Empty); 329 | var newSource = string.Format(FormatCode, @" 330 | public async System.Threading.Tasks.Task VoidCallingMethod() 331 | { 332 | await Test(); 333 | } 334 | 335 | public async System.Threading.Tasks.Task Test() 336 | { 337 | var test = new AsyncClass(); 338 | var result = await test.Call(); 339 | }", string.Empty); 340 | VerifyCSharpFix(oldSource, newSource); 341 | } 342 | 343 | [TestMethod] 344 | public void TestRefactoringOverInterfaces() 345 | { 346 | VerifyCSharpFix(@" 347 | using System.Threading.Tasks; 348 | 349 | public class ConsumingClass 350 | { 351 | public int Test(IInterface i) 352 | { 353 | return i.Call(); 354 | } 355 | } 356 | 357 | public interface IInterface 358 | { 359 | int Call(); 360 | } 361 | 362 | 363 | public class DerivedClass : IInterface 364 | { 365 | public int Call() 366 | { 367 | return AsyncMethod().Result; 368 | } 369 | 370 | public Task AsyncMethod() 371 | { 372 | return Task.FromResult(0); 373 | } 374 | } 375 | ", @" 376 | using System.Threading.Tasks; 377 | 378 | public class ConsumingClass 379 | { 380 | public async System.Threading.Tasks.Task Test(IInterface i) 381 | { 382 | return await i.Call(); 383 | } 384 | } 385 | 386 | public interface IInterface 387 | { 388 | System.Threading.Tasks.Task Call(); 389 | } 390 | 391 | 392 | public class DerivedClass : IInterface 393 | { 394 | public async System.Threading.Tasks.Task Call() 395 | { 396 | return await AsyncMethod(); 397 | } 398 | 399 | public Task AsyncMethod() 400 | { 401 | return Task.FromResult(0); 402 | } 403 | } 404 | "); 405 | } 406 | 407 | protected override CodeFixProvider GetCSharpCodeFixProvider() 408 | { 409 | return new InvocationFixProvider(); 410 | } 411 | 412 | protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() 413 | { 414 | return new InvocationAnalyzer(); 415 | } 416 | 417 | public override string DiagnosticId => InvocationAnalyzer.DiagnosticId; 418 | } 419 | } -------------------------------------------------------------------------------- /Asyncify/Asyncify.Test/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("Asyncify.Test")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("Asyncify.Test")] 12 | [assembly: AssemblyCopyright("Copyright © 2015")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // Version information for an assembly consists of the following four values: 22 | // 23 | // Major Version 24 | // Minor Version 25 | // Build Number 26 | // Revision 27 | // 28 | // You can specify all the values or you can default the Build and Revision Numbers 29 | // by using the '*' as shown below: 30 | // [assembly: AssemblyVersion("1.0.*")] 31 | [assembly: AssemblyVersion("1.0.0.0")] 32 | [assembly: AssemblyFileVersion("1.0.0.0")] -------------------------------------------------------------------------------- /Asyncify/Asyncify.Test/VariableAccessAnalyzerFixTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis.CodeFixes; 2 | using Microsoft.CodeAnalysis.Diagnostics; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | 5 | namespace Asyncify.Test 6 | { 7 | [TestClass] 8 | public class VariableAccessAnalyzerFixTest : BaseAnalyzerFixTest 9 | { 10 | [TestMethod] 11 | public void CanFindMethodNotUsingTapWithVariable() 12 | { 13 | var expected = GetResultWithLocation(11, 22); 14 | VerifyCodeWithReturn(@" 15 | public void Test() 16 | { 17 | var temp = CallAsync(); 18 | var result = temp.Result; 19 | }", expected); 20 | VerifyCodeFixWithReturn(@" 21 | public void Test() 22 | { 23 | var temp = CallAsync(); 24 | var result = temp.Result; 25 | }", @" 26 | public async System.Threading.Tasks.Task Test() 27 | { 28 | var temp = CallAsync(); 29 | var result = await temp; 30 | }", expected); 31 | } 32 | [TestMethod] 33 | public void WillWrapVariableInParenthesesIfNeeded() 34 | { 35 | var expected = GetResultWithLocation(11, 22); 36 | VerifyCodeWithReturn(@" 37 | public void Test() 38 | { 39 | var temp = CallAsync(); 40 | var result = temp.Result.ToString(); 41 | }", expected); 42 | VerifyCodeFixWithReturn(@" 43 | public void Test() 44 | { 45 | var temp = CallAsync(); 46 | var result = temp.Result.ToString(); 47 | }", @" 48 | public async System.Threading.Tasks.Task Test() 49 | { 50 | var temp = CallAsync(); 51 | var result = (await temp).ToString(); 52 | }", expected); 53 | } 54 | 55 | [TestMethod] 56 | public void CanFindViolationInMethodUsingTap() 57 | { 58 | var expected = GetResultWithLocation(11, 22); 59 | VerifyCodeWithReturn(@" 60 | public async Task Test() 61 | { 62 | var temp = CallAsync(); 63 | var result = temp.Result; 64 | 65 | var temp2 = CallAsync(); 66 | var result2 = await temp2; 67 | }", expected); 68 | } 69 | 70 | [TestMethod] 71 | public void DoesNotViolateOnLackOfTapUseWithinLock() 72 | { 73 | VerifyCodeWithReturn(@" 74 | public void Test() 75 | { 76 | var obj = new object(); 77 | lock(obj) 78 | { 79 | var temp = CallAsync(); 80 | var result = temp.Result; 81 | } 82 | }", EmptyExpectedResults); 83 | } 84 | 85 | [TestMethod] 86 | public void DoesNotViolateOnMethodsWithOutOrRef() 87 | { 88 | VerifyCodeWithReturn(@" 89 | public void Test(out string test) 90 | { 91 | test = string.Empty; 92 | var temp = CallAsync(); 93 | var result = temp.Result; 94 | }", EmptyExpectedResults); 95 | } 96 | 97 | [TestMethod] 98 | public void CanFixVariableAccessInLambda() 99 | { 100 | VerifyCodeFixWithReturn(@" 101 | public void Test(out string outVariable) 102 | { 103 | System.Action test = () => 104 | { 105 | var t = Task.FromResult(100); 106 | var result = t.Result; 107 | }; 108 | 109 | outVariable = string.Empty; 110 | }", @" 111 | public void Test(out string outVariable) 112 | { 113 | System.Action test = async () => 114 | { 115 | var t = Task.FromResult(100); 116 | var result = await t; 117 | }; 118 | 119 | outVariable = string.Empty; 120 | }", GetResultWithLocation(13, 26)); 121 | } 122 | 123 | [TestMethod] 124 | public void CanFindMethodNotUsingTapWithVariableInBraces() 125 | { 126 | var expected = GetResultWithLocation(11, 22); 127 | VerifyCodeWithReturn(@" 128 | public void Test() 129 | { 130 | var temp = CallAsync(); 131 | var result = ((Task)temp).Result; 132 | }", expected); 133 | VerifyCodeFixWithReturn(@" 134 | public void Test() 135 | { 136 | var temp = CallAsync(); 137 | var result = ((Task)temp).Result; 138 | }", @" 139 | public async System.Threading.Tasks.Task Test() 140 | { 141 | var temp = CallAsync(); 142 | var result = await ((Task)temp); 143 | }", expected); 144 | } 145 | 146 | protected override CodeFixProvider GetCSharpCodeFixProvider() 147 | { 148 | return new VariableAccessFixProvider(); 149 | } 150 | 151 | protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() 152 | { 153 | return new VariableAccessAnalyzer(); 154 | } 155 | 156 | public override string DiagnosticId => VariableAccessAnalyzer.DiagnosticId; 157 | } 158 | } -------------------------------------------------------------------------------- /Asyncify/Asyncify.Test/Verifiers/CodeFixVerifier.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CodeActions; 3 | using Microsoft.CodeAnalysis.CodeFixes; 4 | using Microsoft.CodeAnalysis.Diagnostics; 5 | using Microsoft.CodeAnalysis.Formatting; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Threading; 10 | 11 | namespace TestHelper 12 | { 13 | /// 14 | /// Superclass of all Unit tests made for diagnostics with codefixes. 15 | /// Contains methods used to verify correctness of codefixes 16 | /// 17 | public abstract partial class CodeFixVerifier : DiagnosticVerifier 18 | { 19 | /// 20 | /// Returns the codefix being tested (C#) - to be implemented in non-abstract class 21 | /// 22 | /// The CodeFixProvider to be used for CSharp code 23 | protected virtual CodeFixProvider GetCSharpCodeFixProvider() 24 | { 25 | return null; 26 | } 27 | 28 | /// 29 | /// Returns the codefix being tested (VB) - to be implemented in non-abstract class 30 | /// 31 | /// The CodeFixProvider to be used for VisualBasic code 32 | protected virtual CodeFixProvider GetBasicCodeFixProvider() 33 | { 34 | return null; 35 | } 36 | 37 | /// 38 | /// Called to test a C# codefix when applied on the inputted string as a source 39 | /// 40 | /// A class in the form of a string before the CodeFix was applied to it 41 | /// A class in the form of a string after the CodeFix was applied to it 42 | /// Index determining which codefix to apply if there are multiple 43 | /// A bool controlling whether or not the test will fail if the CodeFix introduces other warnings after being applied 44 | protected void VerifyCSharpFix(string oldSource, string newSource, int? codeFixIndex = null, bool allowNewCompilerDiagnostics = false) 45 | { 46 | VerifyFix(LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), GetCSharpCodeFixProvider(), oldSource, newSource, codeFixIndex, allowNewCompilerDiagnostics); 47 | } 48 | 49 | /// 50 | /// Called to test a VB codefix when applied on the inputted string as a source 51 | /// 52 | /// A class in the form of a string before the CodeFix was applied to it 53 | /// A class in the form of a string after the CodeFix was applied to it 54 | /// Index determining which codefix to apply if there are multiple 55 | /// A bool controlling whether or not the test will fail if the CodeFix introduces other warnings after being applied 56 | protected void VerifyBasicFix(string oldSource, string newSource, int? codeFixIndex = null, bool allowNewCompilerDiagnostics = false) 57 | { 58 | VerifyFix(LanguageNames.VisualBasic, GetBasicDiagnosticAnalyzer(), GetBasicCodeFixProvider(), oldSource, newSource, codeFixIndex, allowNewCompilerDiagnostics); 59 | } 60 | 61 | /// 62 | /// General verifier for codefixes. 63 | /// Creates a Document from the source string, then gets diagnostics on it and applies the relevant codefixes. 64 | /// Then gets the string after the codefix is applied and compares it with the expected result. 65 | /// Note: If any codefix causes new diagnostics to show up, the test fails unless allowNewCompilerDiagnostics is set to true. 66 | /// 67 | /// The language the source code is in 68 | /// The analyzer to be applied to the source code 69 | /// The codefix to be applied to the code wherever the relevant Diagnostic is found 70 | /// A class in the form of a string before the CodeFix was applied to it 71 | /// A class in the form of a string after the CodeFix was applied to it 72 | /// Index determining which codefix to apply if there are multiple 73 | /// A bool controlling whether or not the test will fail if the CodeFix introduces other warnings after being applied 74 | private void VerifyFix(string language, DiagnosticAnalyzer analyzer, CodeFixProvider codeFixProvider, string oldSource, string newSource, int? codeFixIndex, bool allowNewCompilerDiagnostics) 75 | { 76 | var document = CreateDocument(oldSource, language); 77 | var analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(analyzer, new[] { document }); 78 | var compilerDiagnostics = GetCompilerDiagnostics(document); 79 | var attempts = analyzerDiagnostics.Length; 80 | 81 | for (int i = 0; i < attempts; ++i) 82 | { 83 | var actions = new List(); 84 | var context = new CodeFixContext(document, analyzerDiagnostics[0], (a, d) => actions.Add(a), CancellationToken.None); 85 | codeFixProvider.RegisterCodeFixesAsync(context).Wait(); 86 | 87 | if (!actions.Any()) 88 | { 89 | break; 90 | } 91 | 92 | if (codeFixIndex != null) 93 | { 94 | document = ApplyFix(document, actions.ElementAt((int)codeFixIndex)); 95 | break; 96 | } 97 | 98 | document = ApplyFix(document, actions.ElementAt(0)); 99 | analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(analyzer, new[] { document }); 100 | 101 | var newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, GetCompilerDiagnostics(document)); 102 | 103 | //check if applying the code fix introduced any new compiler diagnostics 104 | if (!allowNewCompilerDiagnostics && newCompilerDiagnostics.Any()) 105 | { 106 | // Format and get the compiler diagnostics again so that the locations make sense in the output 107 | document = document.WithSyntaxRoot(Formatter.Format(document.GetSyntaxRootAsync().Result, Formatter.Annotation, document.Project.Solution.Workspace)); 108 | newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, GetCompilerDiagnostics(document)); 109 | 110 | Assert.IsTrue(false, 111 | string.Format("Fix introduced new compiler diagnostics:\r\n{0}\r\n\r\nNew document:\r\n{1}\r\n", 112 | string.Join("\r\n", newCompilerDiagnostics.Select(d => d.ToString())), 113 | document.GetSyntaxRootAsync().Result.ToFullString())); 114 | } 115 | 116 | //check if there are analyzer diagnostics left after the code fix 117 | if (!analyzerDiagnostics.Any()) 118 | { 119 | break; 120 | } 121 | } 122 | 123 | //after applying all of the code fixes, compare the resulting string to the inputted one 124 | var actual = GetStringFromDocument(document); 125 | Assert.AreEqual(newSource, actual); 126 | } 127 | } 128 | } -------------------------------------------------------------------------------- /Asyncify/Asyncify.Test/Verifiers/DiagnosticVerifier.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CSharp; 3 | using Microsoft.CodeAnalysis.Diagnostics; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | 9 | namespace TestHelper 10 | { 11 | /// 12 | /// Superclass of all Unit Tests for DiagnosticAnalyzers 13 | /// 14 | public abstract partial class DiagnosticVerifier 15 | { 16 | #region To be implemented by Test classes 17 | /// 18 | /// Get the CSharp analyzer being tested - to be implemented in non-abstract class 19 | /// 20 | protected virtual DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() 21 | { 22 | return null; 23 | } 24 | 25 | /// 26 | /// Get the Visual Basic analyzer being tested (C#) - to be implemented in non-abstract class 27 | /// 28 | protected virtual DiagnosticAnalyzer GetBasicDiagnosticAnalyzer() 29 | { 30 | return null; 31 | } 32 | #endregion 33 | 34 | #region Verifier wrappers 35 | 36 | /// 37 | /// Called to test a C# DiagnosticAnalyzer when applied on the single inputted string as a source 38 | /// Note: input a DiagnosticResult for each Diagnostic expected 39 | /// 40 | /// A class in the form of a string to run the analyzer on 41 | /// DiagnosticResults that should appear after the analyzer is run on the source 42 | protected void VerifyCSharpDiagnostic(string source, params DiagnosticResult[] expected) 43 | { 44 | VerifyDiagnostics(new[] { source }, LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), expected); 45 | } 46 | 47 | /// 48 | /// Called to test a VB DiagnosticAnalyzer when applied on the single inputted string as a source 49 | /// Note: input a DiagnosticResult for each Diagnostic expected 50 | /// 51 | /// A class in the form of a string to run the analyzer on 52 | /// DiagnosticResults that should appear after the analyzer is run on the source 53 | protected void VerifyBasicDiagnostic(string source, params DiagnosticResult[] expected) 54 | { 55 | VerifyDiagnostics(new[] { source }, LanguageNames.VisualBasic, GetBasicDiagnosticAnalyzer(), expected); 56 | } 57 | 58 | /// 59 | /// Called to test a C# DiagnosticAnalyzer when applied on the inputted strings as a source 60 | /// Note: input a DiagnosticResult for each Diagnostic expected 61 | /// 62 | /// An array of strings to create source documents from to run the analyzers on 63 | /// DiagnosticResults that should appear after the analyzer is run on the sources 64 | protected void VerifyCSharpDiagnostic(string[] sources, params DiagnosticResult[] expected) 65 | { 66 | VerifyDiagnostics(sources, LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), expected); 67 | } 68 | 69 | /// 70 | /// Called to test a VB DiagnosticAnalyzer when applied on the inputted strings as a source 71 | /// Note: input a DiagnosticResult for each Diagnostic expected 72 | /// 73 | /// An array of strings to create source documents from to run the analyzers on 74 | /// DiagnosticResults that should appear after the analyzer is run on the sources 75 | protected void VerifyBasicDiagnostic(string[] sources, params DiagnosticResult[] expected) 76 | { 77 | VerifyDiagnostics(sources, LanguageNames.VisualBasic, GetBasicDiagnosticAnalyzer(), expected); 78 | } 79 | 80 | /// 81 | /// General method that gets a collection of actual diagnostics found in the source after the analyzer is run, 82 | /// then verifies each of them. 83 | /// 84 | /// An array of strings to create source documents from to run the analyzers on 85 | /// The language of the classes represented by the source strings 86 | /// The analyzer to be run on the source code 87 | /// DiagnosticResults that should appear after the analyzer is run on the sources 88 | private void VerifyDiagnostics(string[] sources, string language, DiagnosticAnalyzer analyzer, params DiagnosticResult[] expected) 89 | { 90 | var diagnostics = GetSortedDiagnostics(sources, language, analyzer); 91 | VerifyDiagnosticResults(diagnostics, analyzer, expected); 92 | } 93 | 94 | #endregion 95 | 96 | #region Actual comparisons and verifications 97 | /// 98 | /// Checks each of the actual Diagnostics found and compares them with the corresponding DiagnosticResult in the array of expected results. 99 | /// Diagnostics are considered equal only if the DiagnosticResultLocation, Id, Severity, and Message of the DiagnosticResult match the actual diagnostic. 100 | /// 101 | /// The Diagnostics found by the compiler after running the analyzer on the source code 102 | /// The analyzer that was being run on the sources 103 | /// Diagnostic Results that should have appeared in the code 104 | private static void VerifyDiagnosticResults(IEnumerable actualResults, DiagnosticAnalyzer analyzer, params DiagnosticResult[] expectedResults) 105 | { 106 | int expectedCount = expectedResults.Count(); 107 | int actualCount = actualResults.Count(); 108 | 109 | if (expectedCount != actualCount) 110 | { 111 | string diagnosticsOutput = actualResults.Any() ? FormatDiagnostics(analyzer, actualResults.ToArray()) : " NONE."; 112 | 113 | Assert.IsTrue(false, 114 | string.Format("Mismatch between number of diagnostics returned, expected \"{0}\" actual \"{1}\"\r\n\r\nDiagnostics:\r\n{2}\r\n", expectedCount, actualCount, diagnosticsOutput)); 115 | } 116 | 117 | for (int i = 0; i < expectedResults.Length; i++) 118 | { 119 | var actual = actualResults.ElementAt(i); 120 | var expected = expectedResults[i]; 121 | 122 | if (expected.Line == -1 && expected.Column == -1) 123 | { 124 | if (actual.Location != Location.None) 125 | { 126 | Assert.IsTrue(false, 127 | string.Format("Expected:\nA project diagnostic with No location\nActual:\n{0}", 128 | FormatDiagnostics(analyzer, actual))); 129 | } 130 | } 131 | else 132 | { 133 | VerifyDiagnosticLocation(analyzer, actual, actual.Location, expected.Locations.First()); 134 | var additionalLocations = actual.AdditionalLocations.ToArray(); 135 | 136 | if (additionalLocations.Length != expected.Locations.Length - 1) 137 | { 138 | Assert.IsTrue(false, 139 | string.Format("Expected {0} additional locations but got {1} for Diagnostic:\r\n {2}\r\n", 140 | expected.Locations.Length - 1, additionalLocations.Length, 141 | FormatDiagnostics(analyzer, actual))); 142 | } 143 | 144 | for (int j = 0; j < additionalLocations.Length; ++j) 145 | { 146 | VerifyDiagnosticLocation(analyzer, actual, additionalLocations[j], expected.Locations[j + 1]); 147 | } 148 | } 149 | 150 | if (actual.Id != expected.Id) 151 | { 152 | Assert.IsTrue(false, 153 | string.Format("Expected diagnostic id to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", 154 | expected.Id, actual.Id, FormatDiagnostics(analyzer, actual))); 155 | } 156 | 157 | //Disabled check for severity and message. Irrelevant in this case. 158 | //if (actual.Severity != expected.Severity) 159 | //{ 160 | // Assert.IsTrue(false, 161 | // string.Format("Expected diagnostic severity to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", 162 | // expected.Severity, actual.Severity, FormatDiagnostics(analyzer, actual))); 163 | //} 164 | 165 | //if (actual.GetMessage() != expected.Message) 166 | //{ 167 | // Assert.IsTrue(false, 168 | // string.Format("Expected diagnostic message to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", 169 | // expected.Message, actual.GetMessage(), FormatDiagnostics(analyzer, actual))); 170 | //} 171 | } 172 | } 173 | 174 | /// 175 | /// Helper method to VerifyDiagnosticResult that checks the location of a diagnostic and compares it with the location in the expected DiagnosticResult. 176 | /// 177 | /// The analyzer that was being run on the sources 178 | /// The diagnostic that was found in the code 179 | /// The Location of the Diagnostic found in the code 180 | /// The DiagnosticResultLocation that should have been found 181 | private static void VerifyDiagnosticLocation(DiagnosticAnalyzer analyzer, Diagnostic diagnostic, Location actual, DiagnosticResultLocation expected) 182 | { 183 | var actualSpan = actual.GetLineSpan(); 184 | 185 | Assert.IsTrue(actualSpan.Path == expected.Path || (actualSpan.Path != null && actualSpan.Path.Contains("Test0.") && expected.Path.Contains("Test.")), 186 | string.Format("Expected diagnostic to be in file \"{0}\" was actually in file \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", 187 | expected.Path, actualSpan.Path, FormatDiagnostics(analyzer, diagnostic))); 188 | 189 | var actualLinePosition = actualSpan.StartLinePosition; 190 | 191 | // Only check line position if there is an actual line in the real diagnostic 192 | if (actualLinePosition.Line > 0) 193 | { 194 | if (actualLinePosition.Line + 1 != expected.Line) 195 | { 196 | Assert.IsTrue(false, 197 | string.Format("Expected diagnostic to be on line \"{0}\" was actually on line \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", 198 | expected.Line, actualLinePosition.Line + 1, FormatDiagnostics(analyzer, diagnostic))); 199 | } 200 | } 201 | 202 | // Only check column position if there is an actual column position in the real diagnostic 203 | if (actualLinePosition.Character > 0) 204 | { 205 | if (actualLinePosition.Character + 1 != expected.Column) 206 | { 207 | Assert.IsTrue(false, 208 | string.Format("Expected diagnostic to start at column \"{0}\" was actually at column \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", 209 | expected.Column, actualLinePosition.Character + 1, FormatDiagnostics(analyzer, diagnostic))); 210 | } 211 | } 212 | } 213 | #endregion 214 | 215 | #region Formatting Diagnostics 216 | /// 217 | /// Helper method to format a Diagnostic into an easily readable string 218 | /// 219 | /// The analyzer that this verifier tests 220 | /// The Diagnostics to be formatted 221 | /// The Diagnostics formatted as a string 222 | private static string FormatDiagnostics(DiagnosticAnalyzer analyzer, params Diagnostic[] diagnostics) 223 | { 224 | var builder = new StringBuilder(); 225 | for (int i = 0; i < diagnostics.Length; ++i) 226 | { 227 | builder.AppendLine("// " + diagnostics[i].ToString()); 228 | 229 | var analyzerType = analyzer.GetType(); 230 | var rules = analyzer.SupportedDiagnostics; 231 | 232 | foreach (var rule in rules) 233 | { 234 | if (rule != null && rule.Id == diagnostics[i].Id) 235 | { 236 | var location = diagnostics[i].Location; 237 | if (location == Location.None) 238 | { 239 | builder.AppendFormat("GetGlobalResult({0}.{1})", analyzerType.Name, rule.Id); 240 | } 241 | else 242 | { 243 | Assert.IsTrue(location.IsInSource, 244 | $"Test base does not currently handle diagnostics in metadata locations. Diagnostic in metadata: {diagnostics[i]}\r\n"); 245 | 246 | string resultMethodName = diagnostics[i].Location.SourceTree.FilePath.EndsWith(".cs") ? "GetCSharpResultAt" : "GetBasicResultAt"; 247 | var linePosition = diagnostics[i].Location.GetLineSpan().StartLinePosition; 248 | 249 | builder.AppendFormat("{0}({1}, {2}, {3}.{4})", 250 | resultMethodName, 251 | linePosition.Line + 1, 252 | linePosition.Character + 1, 253 | analyzerType.Name, 254 | rule.Id); 255 | } 256 | 257 | if (i != diagnostics.Length - 1) 258 | { 259 | builder.Append(','); 260 | } 261 | 262 | builder.AppendLine(); 263 | break; 264 | } 265 | } 266 | } 267 | return builder.ToString(); 268 | } 269 | #endregion 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /Asyncify/Asyncify.Test/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Asyncify/Asyncify.Vsix/Asyncify.Vsix.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | Debug 10 | AnyCPU 11 | 2.0 12 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 13 | {86D26DFA-92B1-4F33-B350-F3302CD80239} 14 | Library 15 | Properties 16 | Asyncify 17 | Asyncify 18 | v4.5.2 19 | false 20 | false 21 | false 22 | false 23 | false 24 | false 25 | Roslyn 26 | 27 | 28 | true 29 | full 30 | false 31 | bin\Debug\ 32 | DEBUG;TRACE 33 | prompt 34 | 4 35 | 36 | 37 | pdbonly 38 | true 39 | bin\Release\ 40 | TRACE 41 | prompt 42 | 4 43 | 44 | 45 | Program 46 | $(DevEnvDir)devenv.exe 47 | /rootsuffix Roslyn 48 | 49 | 50 | 51 | Designer 52 | 53 | 54 | 55 | 56 | {C15B2F4D-99C3-4833-A9D0-32FFD969C248} 57 | Asyncify 58 | 59 | 60 | 61 | 62 | 69 | -------------------------------------------------------------------------------- /Asyncify/Asyncify.Vsix/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Asyncify.Vsix 6 | This is a sample diagnostic extension for the .NET Compiler Platform ("Roslyn"). 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Asyncify/Asyncify/Asyncify.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 11.0 6 | Debug 7 | AnyCPU 8 | {C15B2F4D-99C3-4833-A9D0-32FFD969C248} 9 | Library 10 | Properties 11 | Asyncify 12 | Asyncify 13 | {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | Profile7 15 | v4.5 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | True 47 | True 48 | Resources.resx 49 | 50 | 51 | 52 | 53 | ResXFileCodeGenerator 54 | Resources.Designer.cs 55 | 56 | 57 | 58 | 59 | Designer 60 | PreserveNewest 61 | 62 | 63 | 64 | PreserveNewest 65 | 66 | 67 | PreserveNewest 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | ..\..\packages\Microsoft.CodeAnalysis.Common.1.0.0\lib\portable-net45+win8\Microsoft.CodeAnalysis.dll 78 | False 79 | 80 | 81 | ..\..\packages\Microsoft.CodeAnalysis.CSharp.1.0.0\lib\portable-net45+win8\Microsoft.CodeAnalysis.CSharp.dll 82 | False 83 | 84 | 85 | ..\..\packages\Microsoft.CodeAnalysis.CSharp.Workspaces.1.0.0\lib\portable-net45+win8\Microsoft.CodeAnalysis.CSharp.Workspaces.dll 86 | False 87 | 88 | 89 | ..\..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.0.0\lib\portable-net45+win8\Microsoft.CodeAnalysis.Workspaces.dll 90 | False 91 | 92 | 93 | ..\..\packages\System.Collections.Immutable.1.1.36\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll 94 | False 95 | 96 | 97 | ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.AttributedModel.dll 98 | False 99 | 100 | 101 | ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Convention.dll 102 | False 103 | 104 | 105 | ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Hosting.dll 106 | False 107 | 108 | 109 | ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Runtime.dll 110 | False 111 | 112 | 113 | ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.TypedParts.dll 114 | False 115 | 116 | 117 | ..\..\packages\System.Reflection.Metadata.1.0.21\lib\portable-net45+win8\System.Reflection.Metadata.dll 118 | False 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 137 | -------------------------------------------------------------------------------- /Asyncify/Asyncify/Asyncify.nuspec: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Asyncify 5 | $version$ 6 | Asyncify 7 | Asncify is an analyzer that allows you to quickly update your code to use the Task Asynchronous Programming model. 8 | Asyncify-CSharp is an analyzer and codefix that allows you to quickly update your code to use the Task Asynchronous Programming model. This model, introduced in C# 5, adds an intuitive way of handling asynchronous calls within C#. 9 | The analyzer allows large codebases to be easily modified to use the TAP model by finding violations and applying fixes up the call tree. 10 | 11 | Hans van Bakel 12 | Hans van Bakel 13 | https://raw.githubusercontent.com/hvanbakel/Asyncify-CSharp/master/LICENSE.MD 14 | https://github.com/hvanbakel/Asyncify-CSharp 15 | false 16 | 17 | --- 0.9.7 18 | Added interface refactoring 19 | --- 0.9.6 20 | Bugfix for invocation not calling .Result 21 | Bugfix for .Result calls outside of a method 22 | Bugfix for variable access within a Lambda expression 23 | --- 0.9.5707.38527 24 | First version of the nuget package. 25 | Copyright Hans van Bakel 2015 26 | Asyncify, analyzers, roslyn, async, await, task, asynchronous, TAP 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Asyncify/Asyncify/BaseAsyncifyFixer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Microsoft.CodeAnalysis; 6 | using Microsoft.CodeAnalysis.CodeActions; 7 | using Microsoft.CodeAnalysis.CodeFixes; 8 | using Microsoft.CodeAnalysis.CSharp; 9 | using Microsoft.CodeAnalysis.CSharp.Syntax; 10 | using Microsoft.CodeAnalysis.FindSymbols; 11 | using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; 12 | 13 | namespace Asyncify 14 | { 15 | public abstract class BaseAsyncifyFixer : CodeFixProvider 16 | where TSyntaxType : SyntaxNode 17 | { 18 | protected abstract string Title { get; } 19 | 20 | public sealed override FixAllProvider GetFixAllProvider() 21 | { 22 | return WellKnownFixAllProviders.BatchFixer; 23 | } 24 | 25 | public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) 26 | { 27 | var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); 28 | 29 | foreach (var diagnostic in context.Diagnostics) 30 | { 31 | var diagnosticSpan = diagnostic.Location.SourceSpan; 32 | var nodeToFix = root.FindNode(diagnosticSpan) as TSyntaxType; 33 | 34 | // Register a code action that will invoke the fix. 35 | context.RegisterCodeFix( 36 | CodeAction.Create( 37 | Title, 38 | c => AsyncifyMethod(context.Document, nodeToFix, c), 39 | Title), 40 | diagnostic); 41 | } 42 | } 43 | 44 | private async Task AsyncifyMethod(Document document, TSyntaxType nodeToFix, CancellationToken cancellationToken) 45 | { 46 | var semanticModel = await document.GetSemanticModelAsync(cancellationToken); 47 | var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken); 48 | 49 | var method = nodeToFix.FirstAncestorOrSelf(); 50 | var returnTypeSymbol = semanticModel.GetDeclaredSymbol(method).ReturnType; 51 | var newMethod = this.ApplyFix(method, nodeToFix, syntaxRoot); 52 | 53 | var lambda = nodeToFix.FirstAncestorOrSelf(); 54 | Solution newSolution = document.Project.Solution; 55 | if (lambda == null) 56 | { 57 | 58 | document = newSolution.GetDocument(document.Id); 59 | var matchingMembers = await FindImplementedInterfaceMembers(document, method); 60 | foreach (var matchingMember in matchingMembers) 61 | { 62 | var interfaceDoc = document.Project.Solution.GetDocument(matchingMember.SyntaxTree); 63 | newSolution = await FixSignatureAndCallers(matchingMember, interfaceDoc, returnTypeSymbol, cancellationToken); 64 | } 65 | 66 | document = newSolution.GetDocument(document.Id); 67 | syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken); 68 | method = RefindMethod(method, syntaxRoot); 69 | syntaxRoot = syntaxRoot.ReplaceNode(method, newMethod); 70 | newSolution = newSolution.WithDocumentSyntaxRoot(document.Id, syntaxRoot); 71 | 72 | document = newSolution.GetDocument(document.Id); 73 | syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken); 74 | method = RefindMethod(method, syntaxRoot); 75 | newSolution = await FixSignatureAndCallers(method, document, returnTypeSymbol, cancellationToken); 76 | } 77 | else 78 | { 79 | syntaxRoot = syntaxRoot.ReplaceNode(method, newMethod); 80 | newSolution = document.Project.Solution.WithDocumentSyntaxRoot(document.Id, syntaxRoot); 81 | } 82 | 83 | return newSolution; 84 | } 85 | 86 | private static async Task FindImplementedInterfaceMembers(Document document, 87 | MethodDeclarationSyntax method) 88 | { 89 | var semanticModel = await document.GetSemanticModelAsync(); 90 | var syntaxRoot = await document.GetSyntaxRootAsync(); 91 | 92 | method = RefindMethod(method, syntaxRoot); 93 | var symbol = semanticModel.GetDeclaredSymbol(method); 94 | var type = symbol.ContainingType; 95 | var matchingMembers = type.AllInterfaces 96 | .SelectMany(x => x.GetMembers(method.Identifier.ValueText).OfType()) 97 | .Select(x => 98 | { 99 | var implementedSymbol = type.FindImplementationForInterfaceMember(x); 100 | if (implementedSymbol != null) 101 | { 102 | return x; 103 | } 104 | return null; 105 | }) 106 | .Where(x => x != null) 107 | .Select(x => x.DeclaringSyntaxReferences.First().GetSyntax() as MethodDeclarationSyntax) 108 | .ToArray(); 109 | return matchingMembers; 110 | } 111 | 112 | private static async Task FixSignatureAndCallers(MethodDeclarationSyntax method, Document document, ITypeSymbol returnTypeSymbol, CancellationToken cancellationToken) 113 | { 114 | var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken); 115 | method = RefindMethod(method, syntaxRoot); 116 | syntaxRoot = FixMethodSignature(ref method, returnTypeSymbol, syntaxRoot); 117 | 118 | var newSolution = document.Project.Solution.WithDocumentSyntaxRoot(document.Id, syntaxRoot); 119 | 120 | var newDocument = newSolution.GetDocument(document.Id); 121 | newSolution = await FixCallingMembersAsync(newSolution, newDocument, method, cancellationToken); 122 | return newSolution; 123 | } 124 | 125 | private static async Task FixCallingMembersAsync(Solution solution, Document newDocument, MethodDeclarationSyntax method, CancellationToken cancellationToken) 126 | { 127 | var methodSymbol = await FindMethodSymbolInSolution(newDocument, method, cancellationToken); 128 | 129 | var callers = await SymbolFinder.FindCallersAsync(methodSymbol, solution, cancellationToken); 130 | var callersByDocumentId = callers.SelectMany(x => x.Locations).GroupBy(x => solution.GetDocumentId(x.SourceTree)); 131 | 132 | foreach (var documentId in callersByDocumentId) 133 | { 134 | var document = solution.GetDocument(documentId.Key); 135 | if (document == null) 136 | { 137 | continue; 138 | } 139 | 140 | var semanticModel = await document.GetSemanticModelAsync(cancellationToken); 141 | var callerRoot = await document.GetSyntaxRootAsync(cancellationToken); 142 | var referencesInDocument = documentId.Select(x => callerRoot.FindNode(x.SourceSpan)).ToArray(); 143 | var returnTypeSymbols = referencesInDocument 144 | .Select(x => x.FirstAncestorOrSelf()) 145 | .Select(x => semanticModel.GetDeclaredSymbol(x).ReturnType).ToArray(); 146 | 147 | var numInvocations = referencesInDocument.Length; 148 | 149 | //Iterate the invocations 150 | for (var i = 0; i < numInvocations; i++) 151 | { 152 | //Track all nodes in use 153 | var trackedRoot = callerRoot.TrackNodes(referencesInDocument); 154 | //Get the current node from the tracking system 155 | var callingNode = trackedRoot.GetCurrentNode(referencesInDocument[i]); 156 | 157 | var invocation = callingNode.FirstAncestorOrSelf(); 158 | if (invocation == null) 159 | continue;//Broken code case 160 | 161 | //If it's already in an await expression, leave it 162 | if (invocation.FirstAncestorOrSelf() == null) 163 | { 164 | var fixProvider = new InvocationFixProvider(); 165 | var lambda = invocation.FirstAncestorOrSelf(); 166 | var tempMethod = invocation.FirstAncestorOrSelf(); 167 | var hasOutOrRefParameters = tempMethod.HasOutOrRefParameters(); 168 | if (hasOutOrRefParameters) 169 | { 170 | callerRoot = WrapInvocationInResultCall(invocation, trackedRoot); 171 | } 172 | else 173 | { 174 | var newMethod = fixProvider.ApplyFix(tempMethod, invocation, trackedRoot); 175 | callerRoot = trackedRoot.ReplaceNode(tempMethod, newMethod); 176 | tempMethod = RefindMethod(tempMethod, callerRoot); 177 | } 178 | 179 | //Check for a lambda, if we're refactoring a lambda, we don't need to update the signature of the method 180 | if (lambda == null && !hasOutOrRefParameters) 181 | { 182 | callerRoot = FixMethodSignature(ref tempMethod, returnTypeSymbols[i], callerRoot); 183 | } 184 | 185 | referencesInDocument = callerRoot 186 | .GetCurrentNodes(referencesInDocument) 187 | .ToArray(); 188 | } 189 | } 190 | 191 | solution = solution.WithDocumentSyntaxRoot(document.Id, callerRoot); 192 | 193 | solution = await RecurseUpCallTree(solution, referencesInDocument, document, cancellationToken); 194 | } 195 | 196 | 197 | return solution; 198 | } 199 | 200 | private static SyntaxNode WrapInvocationInResultCall(InvocationExpressionSyntax invocation, SyntaxNode trackedRoot) 201 | { 202 | var newMemberAccess = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, invocation, 203 | IdentifierName("Result")); 204 | 205 | return trackedRoot.ReplaceNode(invocation, newMemberAccess); 206 | } 207 | 208 | private static async Task RecurseUpCallTree(Solution solution, SyntaxNode[] referencesInDocument, 209 | Document document, CancellationToken cancellationToken) 210 | { 211 | var refactoredMethods = referencesInDocument.Select(x => x.FirstAncestorOrSelf()).ToArray(); 212 | foreach (var refactoredMethod in refactoredMethods.Where(x => !x.HasOutOrRefParameters())) 213 | { 214 | solution = await FixCallingMembersAsync(solution, solution.GetDocument(document.Id), refactoredMethod, cancellationToken); 215 | } 216 | return solution; 217 | } 218 | 219 | private static async Task FindMethodSymbolInSolution(Document newDocument, MethodDeclarationSyntax method, CancellationToken cancellationToken) 220 | { 221 | var syntaxTree = await newDocument.GetSyntaxTreeAsync(cancellationToken); 222 | var compilation = await newDocument.Project.GetCompilationAsync(cancellationToken); 223 | var semanticModel = compilation.GetSemanticModel(syntaxTree); 224 | var root = await syntaxTree.GetRootAsync(cancellationToken); 225 | var node = root.FindNode(method.GetLocation().SourceSpan); 226 | var methodSymbol = semanticModel.GetDeclaredSymbol(node) as IMethodSymbol; 227 | return methodSymbol; 228 | } 229 | 230 | private static SyntaxNode FixMethodSignature(ref MethodDeclarationSyntax method, ITypeSymbol returnTypeSymbol, SyntaxNode syntaxRoot) 231 | { 232 | if (method.Modifiers.Any(SyntaxKind.AsyncKeyword)) 233 | { 234 | return syntaxRoot; 235 | } 236 | 237 | TypeSyntax typeSyntax; 238 | if (returnTypeSymbol.SpecialType == SpecialType.System_Void) 239 | { 240 | typeSyntax = ParseTypeName(typeof(Task).FullName); 241 | } 242 | else 243 | { 244 | typeSyntax = ParseTypeName(typeof(Task).FullName + "<" + method.ReturnType.WithoutTrivia().ToFullString() + ">"); 245 | } 246 | 247 | var newMethod = method 248 | .WithReturnType(typeSyntax.WithTrailingTrivia(Space)); 249 | 250 | if (method.FirstAncestorOrSelf() == null) 251 | { 252 | newMethod = newMethod.AddModifiers(Token(SyntaxKind.AsyncKeyword).WithTrailingTrivia(Space)); 253 | } 254 | 255 | //var trackedRoot = syntaxRoot.TrackNodes(method); 256 | //var tempRoot = trackedRoot.ReplaceNode(typeSyntax, typeSyntax.WithTrailingTrivia(Space)); 257 | //var temp = tempRoot.GetCurrentNode(method); 258 | 259 | syntaxRoot = syntaxRoot.ReplaceNode(method, newMethod); 260 | method = RefindMethod(method, syntaxRoot); 261 | return syntaxRoot; 262 | } 263 | 264 | private static MethodDeclarationSyntax RefindMethod(MethodDeclarationSyntax method, SyntaxNode syntaxRoot) 265 | { 266 | return syntaxRoot 267 | .DescendantNodes() 268 | .OfType() 269 | .Single(x => 270 | x.Identifier.ValueText == method.Identifier.ValueText && 271 | (x.ParameterList == method.ParameterList || x.ParameterList.ToFullString() == method.ParameterList.ToFullString())); 272 | } 273 | 274 | protected abstract SyntaxNode ApplyFix(MethodDeclarationSyntax method, TSyntaxType node, SyntaxNode syntaxRoot); 275 | } 276 | } -------------------------------------------------------------------------------- /Asyncify/Asyncify/ExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Microsoft.CodeAnalysis; 3 | using Microsoft.CodeAnalysis.CSharp; 4 | using Microsoft.CodeAnalysis.CSharp.Syntax; 5 | 6 | namespace Asyncify 7 | { 8 | internal static class ExtensionMethods 9 | { 10 | internal static bool IsWrappedInLock(this SyntaxNode node) 11 | { 12 | return node?.FirstAncestorOrSelf() != null; 13 | } 14 | 15 | internal static bool IsWrappedInAwaitExpression(this SyntaxNode node) 16 | { 17 | return node?.FirstAncestorOrSelf() != null; 18 | } 19 | 20 | internal static bool HasOutOrRefParameters(this MethodDeclarationSyntax node) 21 | { 22 | return node.ParameterList != null && 23 | node.ParameterList.Parameters.Any(x => 24 | x.Modifiers.Any(SyntaxKind.RefKeyword) || 25 | x.Modifiers.Any(SyntaxKind.OutKeyword) 26 | ); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Asyncify/Asyncify/InvocationAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Threading.Tasks; 3 | using Microsoft.CodeAnalysis; 4 | using Microsoft.CodeAnalysis.CSharp; 5 | using Microsoft.CodeAnalysis.CSharp.Syntax; 6 | using Microsoft.CodeAnalysis.Diagnostics; 7 | 8 | namespace Asyncify 9 | { 10 | [DiagnosticAnalyzer(LanguageNames.CSharp)] 11 | public class InvocationAnalyzer : DiagnosticAnalyzer 12 | { 13 | public const string DiagnosticId = "AsyncifyInvocation"; 14 | 15 | private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.AsyncifyInvocationTitle), Resources.ResourceManager, typeof(Resources)); 16 | private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.AsyncifyInvocationMessageFormat), Resources.ResourceManager, typeof(Resources)); 17 | private static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.AsyncifyInvocationDescription), Resources.ResourceManager, typeof(Resources)); 18 | private const string HelpUrl = "https://msdn.microsoft.com/en-us/library/hh873175(v=vs.110).aspx"; 19 | private const string Category = "Async"; 20 | 21 | public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); 22 | 23 | private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, true, Description, HelpUrl); 24 | 25 | public override void Initialize(AnalysisContext context) 26 | { 27 | context.RegisterSyntaxNodeAction(CheckInvocation, SyntaxKind.InvocationExpression); 28 | } 29 | 30 | private void CheckInvocation(SyntaxNodeAnalysisContext context) 31 | { 32 | var invocationExpression = context.Node as InvocationExpressionSyntax; 33 | 34 | if (invocationExpression == null) 35 | { 36 | return; 37 | } 38 | 39 | var invocationAnalyzer = new InvocationChecker(context.SemanticModel); 40 | 41 | if (invocationAnalyzer.ShouldUseTap(invocationExpression)) 42 | { 43 | context.ReportDiagnostic(Diagnostic.Create(Rule, invocationExpression.GetLocation())); 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Asyncify/Asyncify/InvocationChecker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.CodeAnalysis; 4 | using Microsoft.CodeAnalysis.CSharp.Syntax; 5 | 6 | namespace Asyncify 7 | { 8 | internal class InvocationChecker 9 | { 10 | private readonly SemanticModel semanticModel; 11 | 12 | private Lazy taskSymbol; 13 | private Lazy taskOfTSymbol; 14 | 15 | public InvocationChecker(SemanticModel semanticModel) 16 | { 17 | this.semanticModel = semanticModel; 18 | } 19 | 20 | internal bool ShouldUseTap(InvocationExpressionSyntax invocation) 21 | { 22 | var method = invocation.FirstAncestorOrSelf(); 23 | if (invocation.IsWrappedInAwaitExpression() || invocation.IsWrappedInLock() || method == null || IsFollowedByCallReturningVoid(invocation)) 24 | { 25 | return false; 26 | } 27 | 28 | taskSymbol = new Lazy(() => semanticModel.Compilation.GetTypeByMetadataName(typeof(Task).FullName)); 29 | taskOfTSymbol = new Lazy(() => semanticModel.Compilation.GetTypeByMetadataName(typeof(Task).FullName + "`1")); 30 | 31 | if (method.HasOutOrRefParameters()) 32 | { 33 | return false; 34 | } 35 | 36 | var symbolToCheck = semanticModel.GetSymbolInfo(invocation).Symbol as IMethodSymbol; 37 | if (symbolToCheck == null) 38 | return false;//Broken code case 39 | 40 | return IsAwaitableMethod(symbolToCheck) && this.InvocationCallsIsWrappedInResultCall(invocation); 41 | } 42 | 43 | private bool IsFollowedByCallReturningVoid(InvocationExpressionSyntax invocation) 44 | { 45 | var parentMemberAccess = invocation.Parent as MemberAccessExpressionSyntax; 46 | var parentIdentifier = parentMemberAccess?.Name as IdentifierNameSyntax; 47 | if (parentIdentifier == null) 48 | { 49 | return false; 50 | } 51 | 52 | var symbol = semanticModel.GetSymbolInfo(parentIdentifier).Symbol as IMethodSymbol; 53 | return symbol?.ReturnType.SpecialType == SpecialType.System_Void; 54 | } 55 | 56 | private bool InvocationCallsIsWrappedInResultCall(InvocationExpressionSyntax invocation) 57 | { 58 | SyntaxNode node = invocation; 59 | while (node.Parent != null) 60 | { 61 | node = node.Parent; 62 | 63 | var memberAccess = node as MemberAccessExpressionSyntax; 64 | var identifierName = memberAccess?.Name as IdentifierNameSyntax; 65 | if (identifierName != null && identifierName.Identifier.ValueText == nameof(Task.Result)) 66 | { 67 | return true; 68 | } 69 | } 70 | return false; 71 | } 72 | 73 | private bool IsAwaitableMethod(IMethodSymbol invokedSymbol) 74 | { 75 | return invokedSymbol.IsAsync || IsTask(invokedSymbol.ReturnType as INamedTypeSymbol); 76 | } 77 | 78 | private bool IsTask(INamedTypeSymbol returnType) 79 | { 80 | if (returnType == null) 81 | { 82 | return false; 83 | } 84 | 85 | return returnType.IsGenericType ? 86 | returnType.ConstructedFrom.Equals(taskOfTSymbol.Value) : 87 | returnType.Equals(taskSymbol.Value); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Asyncify/Asyncify/InvocationFixProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.Immutable; 3 | using System.Composition; 4 | using System.Threading.Tasks; 5 | using Microsoft.CodeAnalysis; 6 | using Microsoft.CodeAnalysis.CodeFixes; 7 | using Microsoft.CodeAnalysis.CSharp; 8 | using Microsoft.CodeAnalysis.CSharp.Syntax; 9 | using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; 10 | 11 | namespace Asyncify 12 | { 13 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(InvocationFixProvider)), Shared] 14 | public class InvocationFixProvider : BaseAsyncifyFixer 15 | { 16 | protected override string Title => "Asyncify Method"; 17 | 18 | public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(InvocationAnalyzer.DiagnosticId); 19 | 20 | protected override SyntaxNode ApplyFix(MethodDeclarationSyntax method, InvocationExpressionSyntax invocation, SyntaxNode syntaxRoot) 21 | { 22 | var lambda = invocation.FirstAncestorOrSelf(); 23 | 24 | SyntaxNode oldNode = invocation; 25 | SyntaxNode newNode = AwaitExpression(invocation.WithLeadingTrivia(Space)); 26 | 27 | SyntaxNode node = oldNode.Parent; 28 | while (node != null) 29 | { 30 | var memberAccess = node as MemberAccessExpressionSyntax; 31 | var identifierName = memberAccess?.Name as IdentifierNameSyntax; 32 | if (identifierName != null && identifierName.Identifier.ValueText == nameof(Task.Result)) 33 | { 34 | newNode = memberAccess.Expression.ReplaceNode(oldNode, newNode); 35 | 36 | if (memberAccess.Parent is MemberAccessExpressionSyntax) 37 | { 38 | newNode = ParenthesizedExpression((ExpressionSyntax) newNode); 39 | } 40 | 41 | oldNode = memberAccess; 42 | break; 43 | } 44 | 45 | node = node.Parent; 46 | } 47 | 48 | if (lambda != null) 49 | { 50 | var newLambda = LambdaFixProvider.FixLambda(method, lambda, lambda.Body.ReplaceNode(oldNode, newNode)); 51 | return method.ReplaceNode(method, newLambda); 52 | } 53 | else 54 | { 55 | return method.ReplaceNode(oldNode, newNode); 56 | } 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /Asyncify/Asyncify/LambdaFixProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CSharp; 3 | using Microsoft.CodeAnalysis.CSharp.Syntax; 4 | using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; 5 | 6 | namespace Asyncify 7 | { 8 | internal static class LambdaFixProvider 9 | { 10 | public static SyntaxNode FixLambda(SyntaxNode root, LambdaExpressionSyntax lambda, CSharpSyntaxNode newBody) 11 | { 12 | var simpleLambda = lambda as SimpleLambdaExpressionSyntax; 13 | var parenthesizedLambda = lambda as ParenthesizedLambdaExpressionSyntax; 14 | if (simpleLambda != null) 15 | { 16 | return root.ReplaceNode(lambda, simpleLambda 17 | .WithAsyncKeyword(Token(SyntaxKind.AsyncKeyword).WithTrailingTrivia(Space)) 18 | .WithBody(newBody)); 19 | } 20 | else 21 | { 22 | return root.ReplaceNode(lambda, parenthesizedLambda 23 | .WithAsyncKeyword(Token(SyntaxKind.AsyncKeyword).WithTrailingTrivia(Space)) 24 | .WithBody(newBody)); 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Asyncify/Asyncify/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("C# Asyncify")] 8 | [assembly: AssemblyDescription("Visual Studio Analyzer To Asyncify Your Code")] 9 | [assembly: AssemblyProduct("Asyncify-CSharp")] 10 | [assembly: AssemblyCopyright("Copyright © 2015")] 11 | 12 | // Setting ComVisible to false makes the types in this assembly not visible 13 | // to COM components. If you need to access a type in this assembly from 14 | // COM, set the ComVisible attribute to true on that type. 15 | [assembly: ComVisible(false)] 16 | 17 | // Version information for an assembly consists of the following four values: 18 | // 19 | // Major Version 20 | // Minor Version 21 | // Build Number 22 | // Revision 23 | // 24 | // You can specify all the values or you can default the Build and Revision Numbers 25 | // by using the '*' as shown below: 26 | [assembly: AssemblyVersion("0.9.7")] 27 | [assembly: AssemblyFileVersion("0.9.7")] 28 | -------------------------------------------------------------------------------- /Asyncify/Asyncify/ReadMe.txt: -------------------------------------------------------------------------------- 1 |  2 | Building this project will produce an analyzer .dll, as well as the 3 | following two ways you may wish to package that analyzer: 4 | * A NuGet package (.nupkg file) that will add your assembly as a 5 | project-local analyzer that participates in builds. 6 | * A VSIX extension (.vsix file) that will apply your analyzer to all projects 7 | and works just in the IDE. 8 | 9 | To debug your analyzer, make sure the default project is the VSIX project and 10 | start debugging. This will deploy the analyzer as a VSIX into another instance 11 | of Visual Studio, which is useful for debugging, even if you intend to produce 12 | a NuGet package. 13 | 14 | 15 | TRYING OUT YOUR NUGET PACKAGE 16 | 17 | To try out the NuGet package: 18 | 1. Create a local NuGet feed by following the instructions here: 19 | > http://docs.nuget.org/docs/creating-packages/hosting-your-own-nuget-feeds 20 | 2. Copy the .nupkg file into that folder. 21 | 3. Open the target project in Visual Studio 2015. 22 | 4. Right-click on the project node in Solution Explorer and choose Manage 23 | NuGet Packages. 24 | 5. Select the NuGet feed you created on the left. 25 | 6. Choose your analyzer from the list and click Install. 26 | 27 | If you want to automatically deploy the .nupkg file to the local feed folder 28 | when you build this project, follow these steps: 29 | 1. Right-click on this project in Solution Explorer and choose 'Unload Project'. 30 | 2. Right-click on this project and click "Edit". 31 | 3. Scroll down to the "AfterBuild" target. 32 | 4. In the "Exec" task, change the value inside "Command" after the -OutputDirectory 33 | path to point to your local NuGet feed folder. -------------------------------------------------------------------------------- /Asyncify/Asyncify/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Asyncify { 12 | using System; 13 | using System.Reflection; 14 | 15 | 16 | /// 17 | /// A strongly-typed resource class, for looking up localized strings, etc. 18 | /// 19 | // This class was auto-generated by the StronglyTypedResourceBuilder 20 | // class via a tool like ResGen or Visual Studio. 21 | // To add or remove a member, edit your .ResX file then rerun ResGen 22 | // with the /str option, or rebuild your VS project. 23 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 24 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 25 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 26 | internal class Resources { 27 | 28 | private static global::System.Resources.ResourceManager resourceMan; 29 | 30 | private static global::System.Globalization.CultureInfo resourceCulture; 31 | 32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 33 | internal Resources() { 34 | } 35 | 36 | /// 37 | /// Returns the cached ResourceManager instance used by this class. 38 | /// 39 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 40 | internal static global::System.Resources.ResourceManager ResourceManager { 41 | get { 42 | if (object.ReferenceEquals(resourceMan, null)) { 43 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Asyncify.Resources", typeof(Resources).GetTypeInfo().Assembly); 44 | resourceMan = temp; 45 | } 46 | return resourceMan; 47 | } 48 | } 49 | 50 | /// 51 | /// Overrides the current thread's CurrentUICulture property for all 52 | /// resource lookups using this strongly typed resource class. 53 | /// 54 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 55 | internal static global::System.Globalization.CultureInfo Culture { 56 | get { 57 | return resourceCulture; 58 | } 59 | set { 60 | resourceCulture = value; 61 | } 62 | } 63 | 64 | /// 65 | /// Looks up a localized string similar to Analyzes code for potential usage of the Task Asynchronous Programming model.. 66 | /// 67 | internal static string AsyncifyInvocationDescription { 68 | get { 69 | return ResourceManager.GetString("AsyncifyInvocationDescription", resourceCulture); 70 | } 71 | } 72 | 73 | /// 74 | /// Looks up a localized string similar to This invocation could benefit from the use of Task async.. 75 | /// 76 | internal static string AsyncifyInvocationMessageFormat { 77 | get { 78 | return ResourceManager.GetString("AsyncifyInvocationMessageFormat", resourceCulture); 79 | } 80 | } 81 | 82 | /// 83 | /// Looks up a localized string similar to Use Task Async. 84 | /// 85 | internal static string AsyncifyInvocationTitle { 86 | get { 87 | return ResourceManager.GetString("AsyncifyInvocationTitle", resourceCulture); 88 | } 89 | } 90 | 91 | /// 92 | /// Looks up a localized string similar to Analyzes code for potential usage of the Task Asynchronous Programming model.. 93 | /// 94 | internal static string AsyncifyVariableAccessDescription { 95 | get { 96 | return ResourceManager.GetString("AsyncifyVariableAccessDescription", resourceCulture); 97 | } 98 | } 99 | 100 | /// 101 | /// Looks up a localized string similar to This variable access could benefit from the use of Task async.. 102 | /// 103 | internal static string AsyncifyVariableAccessMessageFormat { 104 | get { 105 | return ResourceManager.GetString("AsyncifyVariableAccessMessageFormat", resourceCulture); 106 | } 107 | } 108 | 109 | /// 110 | /// Looks up a localized string similar to Use Task Async. 111 | /// 112 | internal static string AsyncifyVariableAccessTitle { 113 | get { 114 | return ResourceManager.GetString("AsyncifyVariableAccessTitle", resourceCulture); 115 | } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Asyncify/Asyncify/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | Analyzes code for potential usage of the Task Asynchronous Programming model. 122 | 123 | 124 | This invocation could benefit from the use of Task async. 125 | 126 | 127 | Use Task Async 128 | 129 | 130 | Analyzes code for potential usage of the Task Asynchronous Programming model. 131 | 132 | 133 | This variable access could benefit from the use of Task async. 134 | 135 | 136 | Use Task Async 137 | 138 | -------------------------------------------------------------------------------- /Asyncify/Asyncify/VariableAccessAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using Microsoft.CodeAnalysis; 3 | using Microsoft.CodeAnalysis.CSharp; 4 | using Microsoft.CodeAnalysis.CSharp.Syntax; 5 | using Microsoft.CodeAnalysis.Diagnostics; 6 | 7 | namespace Asyncify 8 | { 9 | [DiagnosticAnalyzer(LanguageNames.CSharp)] 10 | public class VariableAccessAnalyzer : DiagnosticAnalyzer 11 | { 12 | public const string DiagnosticId = "AsyncifyVariable"; 13 | 14 | private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.AsyncifyVariableAccessTitle), Resources.ResourceManager, typeof (Resources)); 15 | private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.AsyncifyVariableAccessMessageFormat), Resources.ResourceManager, typeof (Resources)); 16 | private static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.AsyncifyVariableAccessDescription), Resources.ResourceManager, typeof (Resources)); 17 | 18 | private const string HelpUrl = "https://msdn.microsoft.com/en-us/library/hh873175(v=vs.110).aspx"; 19 | private const string Category = "Async"; 20 | 21 | public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); 22 | 23 | private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, 24 | Category, DiagnosticSeverity.Warning, true, Description, HelpUrl); 25 | 26 | public override void Initialize(AnalysisContext context) 27 | { 28 | context.RegisterSyntaxNodeAction(CheckMemberAccess, SyntaxKind.SimpleMemberAccessExpression); 29 | } 30 | 31 | private void CheckMemberAccess(SyntaxNodeAnalysisContext context) 32 | { 33 | var memberAccessExpression = context.Node as MemberAccessExpressionSyntax; 34 | 35 | if (memberAccessExpression == null) 36 | { 37 | return; 38 | } 39 | 40 | var memberAccessAnalyzer = new VariableAccessChecker(context.SemanticModel); 41 | if (memberAccessAnalyzer.ShouldUseTap(memberAccessExpression)) 42 | { 43 | context.ReportDiagnostic(Diagnostic.Create(Rule, memberAccessExpression.GetLocation())); 44 | } 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /Asyncify/Asyncify/VariableAccessChecker.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.CodeAnalysis; 3 | using Microsoft.CodeAnalysis.CSharp.Syntax; 4 | 5 | namespace Asyncify 6 | { 7 | internal class VariableAccessChecker 8 | { 9 | private readonly SemanticModel semanticModel; 10 | 11 | public VariableAccessChecker(SemanticModel semanticModel) 12 | { 13 | this.semanticModel = semanticModel; 14 | } 15 | 16 | public bool ShouldUseTap(MemberAccessExpressionSyntax memberAccessExpression) 17 | { 18 | if (memberAccessExpression.IsWrappedInAwaitExpression() || memberAccessExpression.IsWrappedInLock()) 19 | { 20 | return false; 21 | } 22 | 23 | var identifierName = memberAccessExpression.Name as IdentifierNameSyntax; 24 | if (identifierName?.Identifier.ValueText != nameof(Task.Result)) 25 | { 26 | return false; 27 | } 28 | 29 | var lambdaExpression = memberAccessExpression.FirstAncestorOrSelf(); 30 | if (lambdaExpression == null) 31 | { 32 | var methodDeclaration = memberAccessExpression.FirstAncestorOrSelf(); 33 | if (methodDeclaration == null || methodDeclaration.HasOutOrRefParameters()) 34 | { 35 | return false; 36 | } 37 | } 38 | 39 | var symbol = FindSymbol(memberAccessExpression.Expression); 40 | if (symbol == null) 41 | { 42 | return false; 43 | } 44 | var taskSymbol = semanticModel.Compilation.GetTypeByMetadataName(typeof(Task).FullName); 45 | var taskOfTSymbol = semanticModel.Compilation.GetTypeByMetadataName(typeof(Task).FullName + "`1"); 46 | 47 | return symbol.IsGenericType ? 48 | symbol.ConstructedFrom.Equals(taskOfTSymbol) : 49 | symbol.Equals(taskSymbol); 50 | } 51 | 52 | private INamedTypeSymbol FindSymbol(ExpressionSyntax expression) 53 | { 54 | while (true) 55 | { 56 | var parenthesizedExpression = expression as ParenthesizedExpressionSyntax; 57 | if (parenthesizedExpression != null) 58 | { 59 | expression = parenthesizedExpression.Expression; 60 | continue; 61 | } 62 | 63 | var castExpression = expression as CastExpressionSyntax; 64 | if (castExpression != null) 65 | { 66 | return semanticModel.GetTypeInfo(castExpression.Type).Type as INamedTypeSymbol; 67 | } 68 | 69 | if (expression is InvocationExpressionSyntax)//Handled by invocationanalzyer 70 | { 71 | return null; 72 | } 73 | 74 | var localSymbol = semanticModel.GetSymbolInfo(expression).Symbol as ILocalSymbol; 75 | return localSymbol?.Type as INamedTypeSymbol; 76 | } 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /Asyncify/Asyncify/VariableAccessFixProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Composition; 3 | using Microsoft.CodeAnalysis; 4 | using Microsoft.CodeAnalysis.CodeFixes; 5 | using Microsoft.CodeAnalysis.CSharp.Syntax; 6 | using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; 7 | 8 | namespace Asyncify 9 | { 10 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(VariableAccessFixProvider)), Shared] 11 | public class VariableAccessFixProvider : BaseAsyncifyFixer 12 | { 13 | protected override string Title => "Asyncify Variable Access"; 14 | 15 | public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(VariableAccessAnalyzer.DiagnosticId); 16 | 17 | protected override SyntaxNode ApplyFix(MethodDeclarationSyntax method, MemberAccessExpressionSyntax variableAccess, SyntaxNode syntaxRoot) 18 | { 19 | ExpressionSyntax newAccess = AwaitExpression(variableAccess.Expression.WithLeadingTrivia(Space)); 20 | if (variableAccess.Parent is MemberAccessExpressionSyntax) 21 | { 22 | newAccess = ParenthesizedExpression(newAccess); 23 | } 24 | var lambdaExpression = variableAccess.FirstAncestorOrSelf(); 25 | if (lambdaExpression != null) 26 | { 27 | var newBody = lambdaExpression.Body.ReplaceNode(variableAccess, newAccess); 28 | var newLambda = LambdaFixProvider.FixLambda(method, lambdaExpression, newBody); 29 | return method.ReplaceNode(method, newLambda); 30 | } 31 | else 32 | { 33 | return method.ReplaceNode(variableAccess, newAccess); 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /Asyncify/Asyncify/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Asyncify/Asyncify/tools/install.ps1: -------------------------------------------------------------------------------- 1 | param($installPath, $toolsPath, $package, $project) 2 | 3 | $analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers" ) * -Resolve 4 | 5 | foreach($analyzersPath in $analyzersPaths) 6 | { 7 | # Install the language agnostic analyzers. 8 | if (Test-Path $analyzersPath) 9 | { 10 | foreach ($analyzerFilePath in Get-ChildItem $analyzersPath -Filter *.dll) 11 | { 12 | if($project.Object.AnalyzerReferences) 13 | { 14 | $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) 15 | } 16 | } 17 | } 18 | } 19 | 20 | # $project.Type gives the language name like (C# or VB.NET) 21 | $languageFolder = "" 22 | if($project.Type -eq "C#") 23 | { 24 | $languageFolder = "cs" 25 | } 26 | if($project.Type -eq "VB.NET") 27 | { 28 | $languageFolder = "vb" 29 | } 30 | if($languageFolder -eq "") 31 | { 32 | return 33 | } 34 | 35 | foreach($analyzersPath in $analyzersPaths) 36 | { 37 | # Install language specific analyzers. 38 | $languageAnalyzersPath = join-path $analyzersPath $languageFolder 39 | if (Test-Path $languageAnalyzersPath) 40 | { 41 | foreach ($analyzerFilePath in Get-ChildItem $languageAnalyzersPath -Filter *.dll) 42 | { 43 | if($project.Object.AnalyzerReferences) 44 | { 45 | $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) 46 | } 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /Asyncify/Asyncify/tools/uninstall.ps1: -------------------------------------------------------------------------------- 1 | param($installPath, $toolsPath, $package, $project) 2 | 3 | $analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers" ) * -Resolve 4 | 5 | foreach($analyzersPath in $analyzersPaths) 6 | { 7 | # Uninstall the language agnostic analyzers. 8 | if (Test-Path $analyzersPath) 9 | { 10 | foreach ($analyzerFilePath in Get-ChildItem $analyzersPath -Filter *.dll) 11 | { 12 | if($project.Object.AnalyzerReferences) 13 | { 14 | $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) 15 | } 16 | } 17 | } 18 | } 19 | 20 | # $project.Type gives the language name like (C# or VB.NET) 21 | $languageFolder = "" 22 | if($project.Type -eq "C#") 23 | { 24 | $languageFolder = "cs" 25 | } 26 | if($project.Type -eq "VB.NET") 27 | { 28 | $languageFolder = "vb" 29 | } 30 | if($languageFolder -eq "") 31 | { 32 | return 33 | } 34 | 35 | foreach($analyzersPath in $analyzersPaths) 36 | { 37 | # Uninstall language specific analyzers. 38 | $languageAnalyzersPath = join-path $analyzersPath $languageFolder 39 | if (Test-Path $languageAnalyzersPath) 40 | { 41 | foreach ($analyzerFilePath in Get-ChildItem $languageAnalyzersPath -Filter *.dll) 42 | { 43 | if($project.Object.AnalyzerReferences) 44 | { 45 | try 46 | { 47 | $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) 48 | } 49 | catch 50 | { 51 | 52 | } 53 | } 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /LICENSE.MD: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Hans van Bakel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Asyncify-CSharp 2 | Asyncify-CSharp is an analyzer and codefix that allows you to quickly update your code to use the [Task Asynchronous Programming model](https://msdn.microsoft.com/en-us/library/hh873175(v=vs.110).aspx). This model, introduced in C# 5, adds an intuitive way of handling asynchronous calls within C#. 3 | 4 | The analyzer allows large codebases to be easily modified to use the TAP model by finding violations and applying fixes up the call tree. 5 | 6 | ## Analyzer 7 | The analyzer will throw warnings on use cases like below: 8 | ```CSharp 9 | public void AnotherMethod() 10 | { 11 | var result = Test(); 12 | } 13 | 14 | public int Test() 15 | { 16 | var result = AsyncMethod().Result; // Warning 17 | var awaitable = AsyncMethod(); 18 | var result2 = awaitable.Result.ToString(); // Warning 19 | (new int[0]).Select(x => AsyncMethod().Result); // Warning 20 | return 0; 21 | } 22 | 23 | public async Task AsyncMethod() 24 | { 25 | await Task.Delay(10); 26 | return 0; 27 | } 28 | ``` 29 | 30 | ## Code Fix 31 | The code fix will fix the following things. 32 | - Remove the call to `.Result` 33 | - Wrap it in an `await` expression (potentially wrapping in parentheses 34 | - Update the method signature to become `async` and either `Task` or `Task` 35 | - Recursively find calls to this method and refactor those to use `await` and update their signature. 36 | 37 | So the given code sample above becomes: 38 | ```CSharp 39 | public async Task AnotherMethod() 40 | { 41 | var result = await Test(); 42 | } 43 | 44 | public async Task Test() 45 | { 46 | var result = await AsyncMethod(); 47 | var awaitable = AsyncMethod(); 48 | var result2 = (await awaitable).ToString(); 49 | (new int[0]).Select(async x => await AsyncMethod()); 50 | return 0; 51 | } 52 | 53 | public async Task AsyncMethod() 54 | { 55 | await Task.Delay(10); 56 | return 0; 57 | } 58 | ``` 59 | 60 | ## Conditions 61 | A violation will not be thrown if a refactoring is not possible if: 62 | - The violation is within a lock statement 63 | - The method contains out or ref parameters 64 | 65 | ## Caution 66 | The model of analyzers in Visual Studio is constrained to a single project. As the analyzers hook into the compilation, they are only capable of refactoring within a project. 67 | However, calls from outside the project can be suffixed with `.Result` causing a new violation in that project to which the code fix can be applied. 68 | 69 | ## What about VB? 70 | Currently only C# is supported, mostly because I only have tests running on C#. Also, one thing that is specific to C# is the refactoring of the return type. 71 | --------------------------------------------------------------------------------