├── .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 |
--------------------------------------------------------------------------------