From 2c65353de8d0aae01dee0fc6e9893da1e1afb07f Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Sat, 29 Jul 2017 10:54:27 -0700 Subject: [PATCH 1/2] Preserve leading trivia in VSTHRD103 code fix Fixes #168 --- .../VSTHRD103UseAsyncOptionAnalyzerTests.cs | 88 +++++++++++++++++++ .../VSTHRD103UseAsyncOptionCodeFix.cs | 8 +- 2 files changed, 94 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.VisualStudio.Threading.Analyzers.Tests/VSTHRD103UseAsyncOptionAnalyzerTests.cs b/src/Microsoft.VisualStudio.Threading.Analyzers.Tests/VSTHRD103UseAsyncOptionAnalyzerTests.cs index c5629ecdc..693b9b951 100644 --- a/src/Microsoft.VisualStudio.Threading.Analyzers.Tests/VSTHRD103UseAsyncOptionAnalyzerTests.cs +++ b/src/Microsoft.VisualStudio.Threading.Analyzers.Tests/VSTHRD103UseAsyncOptionAnalyzerTests.cs @@ -923,6 +923,94 @@ internal static void Foo() { } this.VerifyCSharpFix(test, withFix); } + [Fact] + public void AsyncAlternative_CodeFixRespectsTrivia() + { + var test = @" +using System; +using System.Threading.Tasks; + +class Test { + void Foo() { } + Task FooAsync() => Task.CompletedTask; + + async Task DoWorkAsync() + { + await Task.Yield(); + Console.WriteLine(""Foo""); + + // Some comment + Foo(); // another comment + } +} +"; + var withFix = @" +using System; +using System.Threading.Tasks; + +class Test { + void Foo() { } + Task FooAsync() => Task.CompletedTask; + + async Task DoWorkAsync() + { + await Task.Yield(); + Console.WriteLine(""Foo""); + + // Some comment + await FooAsync(); // another comment + } +} +"; + this.expect.Locations = new[] { new DiagnosticResultLocation("Test0.cs", 15, 9, 15, 12) }; + this.VerifyCSharpDiagnostic(test, this.expect); + this.VerifyCSharpFix(test, withFix); + } + + [Fact] + public void AwaitRatherThanWait_CodeFixRespectsTrivia() + { + var test = @" +using System; +using System.Threading.Tasks; + +class Test { + void Foo() { } + Task FooAsync() => Task.CompletedTask; + + async Task DoWorkAsync() + { + await Task.Yield(); + Console.WriteLine(""Foo""); + + // Some comment + FooAsync().Wait(); // another comment + } +} +"; + var withFix = @" +using System; +using System.Threading.Tasks; + +class Test { + void Foo() { } + Task FooAsync() => Task.CompletedTask; + + async Task DoWorkAsync() + { + await Task.Yield(); + Console.WriteLine(""Foo""); + + // Some comment + await FooAsync(); // another comment + } +} +"; + this.expect.Locations = new[] { new DiagnosticResultLocation("Test0.cs", 15, 20, 15, 24) }; + this.VerifyCSharpDiagnostic(test, this.expect); + this.VerifyCSharpFix(test, withFix); + } + [Fact] public void XunitThrowAsyncNotSuggestedInAsyncTestMethod() { diff --git a/src/Microsoft.VisualStudio.Threading.Analyzers/VSTHRD103UseAsyncOptionCodeFix.cs b/src/Microsoft.VisualStudio.Threading.Analyzers/VSTHRD103UseAsyncOptionCodeFix.cs index f3b5c5687..0fadad67e 100644 --- a/src/Microsoft.VisualStudio.Threading.Analyzers/VSTHRD103UseAsyncOptionCodeFix.cs +++ b/src/Microsoft.VisualStudio.Threading.Analyzers/VSTHRD103UseAsyncOptionCodeFix.cs @@ -135,8 +135,11 @@ protected override async Task GetChangedSolutionAsync(CancellationToke if (this.AlternativeAsyncMethod != string.Empty) { // Replace the member being called and await the invocation expression. + // While doing so, move leading trivia to the surrounding await expression. var asyncMethodName = syncMethodName.WithIdentifier(SyntaxFactory.Identifier(this.diagnostic.Properties[AsyncMethodKeyName])); - awaitExpression = SyntaxFactory.AwaitExpression(syncExpression.ReplaceNode(syncMethodName, asyncMethodName)); + awaitExpression = SyntaxFactory.AwaitExpression( + syncExpression.ReplaceNode(syncMethodName, asyncMethodName).WithoutLeadingTrivia()) + .WithLeadingTrivia(syncExpression.GetLeadingTrivia()); if (!(syncExpression.Parent is ExpressionStatementSyntax)) { awaitExpression = SyntaxFactory.ParenthesizedExpression(awaitExpression) @@ -156,7 +159,8 @@ protected override async Task GetChangedSolutionAsync(CancellationToke syncMemberStrippedExpression = expressionMethodCall.Expression; } - awaitExpression = SyntaxFactory.AwaitExpression(syncMemberStrippedExpression); + awaitExpression = SyntaxFactory.AwaitExpression(syncMemberStrippedExpression.WithoutLeadingTrivia()) + .WithLeadingTrivia(syncMemberStrippedExpression.GetLeadingTrivia()); } updatedMethod = updatedMethod From 92825b06521b16a5979e86c682b48dddf13ae74c Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Mon, 31 Jul 2017 11:58:47 -0700 Subject: [PATCH 2/2] Add more trivia placements to test --- .../VSTHRD103UseAsyncOptionAnalyzerTests.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.VisualStudio.Threading.Analyzers.Tests/VSTHRD103UseAsyncOptionAnalyzerTests.cs b/src/Microsoft.VisualStudio.Threading.Analyzers.Tests/VSTHRD103UseAsyncOptionAnalyzerTests.cs index 693b9b951..3ef5e2620 100644 --- a/src/Microsoft.VisualStudio.Threading.Analyzers.Tests/VSTHRD103UseAsyncOptionAnalyzerTests.cs +++ b/src/Microsoft.VisualStudio.Threading.Analyzers.Tests/VSTHRD103UseAsyncOptionAnalyzerTests.cs @@ -940,7 +940,7 @@ async Task DoWorkAsync() Console.WriteLine(""Foo""); // Some comment - Foo(); // another comment + Foo(/*argcomment*/); // another comment } } "; @@ -958,7 +958,7 @@ async Task DoWorkAsync() Console.WriteLine(""Foo""); // Some comment - await FooAsync(); // another comment + await FooAsync(/*argcomment*/); // another comment } } "; @@ -984,7 +984,7 @@ async Task DoWorkAsync() Console.WriteLine(""Foo""); // Some comment - FooAsync().Wait(); // another comment + FooAsync(/*argcomment*/).Wait(); // another comment } } "; @@ -1002,11 +1002,11 @@ async Task DoWorkAsync() Console.WriteLine(""Foo""); // Some comment - await FooAsync(); // another comment + await FooAsync(/*argcomment*/); // another comment } } "; - this.expect.Locations = new[] { new DiagnosticResultLocation("Test0.cs", 15, 20, 15, 24) }; + this.expect.Locations = new[] { new DiagnosticResultLocation("Test0.cs", 15, 34, 15, 38) }; this.VerifyCSharpDiagnostic(test, this.expect); this.VerifyCSharpFix(test, withFix); }