diff --git a/src/xunit.analyzers.tests/Analyzers/X1000/DoNotUseBlockingTaskOperationsTests.cs b/src/xunit.analyzers.tests/Analyzers/X1000/DoNotUseBlockingTaskOperationsTests.cs index 34211b20..0f222c8a 100644 --- a/src/xunit.analyzers.tests/Analyzers/X1000/DoNotUseBlockingTaskOperationsTests.cs +++ b/src/xunit.analyzers.tests/Analyzers/X1000/DoNotUseBlockingTaskOperationsTests.cs @@ -114,6 +114,23 @@ public void TestMethod() { await Verify.VerifyAnalyzer(source); } + + [Fact] + public async void SuccessCase_GetAwaiterGetResult_InContinueWithLambda() + { + var source = @" +using System.Threading.Tasks; +using Xunit; + +public class TestClass { + [Fact] + public void TestMethod() { + Task.CompletedTask.ContinueWith(x => x.GetAwaiter().GetResult()); + } +}"; + + await Verify.VerifyAnalyzer(source); + } } public class Task_Generic @@ -135,6 +152,23 @@ public void TestMethod() { await Verify.VerifyAnalyzer(source); } + [Fact] + public async void SuccessCase_Result_InContinueWithLambda() + { + var source = @" +using System.Threading.Tasks; +using Xunit; + +public class TestClass { + [Fact] + public void TestMethod() { + var _ = Task.FromResult(42).ContinueWith(x => x.Result); + } +}"; + + await Verify.VerifyAnalyzer(source); + } + [Fact] public async void FailureCase_GetAwaiterGetResult() { @@ -151,6 +185,23 @@ public void TestMethod() { await Verify.VerifyAnalyzer(source); } + + [Fact] + public async void SuccessCase_GetAwaiterGetResult_InContinueWithLambda() + { + var source = @" +using System.Threading.Tasks; +using Xunit; + +public class TestClass { + [Fact] + public void TestMethod() { + var _ = Task.FromResult(42).ContinueWith(x => x.GetAwaiter().GetResult()); + } +}"; + + await Verify.VerifyAnalyzer(source); + } } public class ValueTask_NonGeneric diff --git a/src/xunit.analyzers/X1000/DoNotUseBlockingTaskOperations.cs b/src/xunit.analyzers/X1000/DoNotUseBlockingTaskOperations.cs index 46ae76e3..0355071a 100644 --- a/src/xunit.analyzers/X1000/DoNotUseBlockingTaskOperations.cs +++ b/src/xunit.analyzers/X1000/DoNotUseBlockingTaskOperations.cs @@ -31,6 +31,10 @@ public class DoNotUseBlockingTaskOperations : XunitDiagnosticAnalyzer // These are on both Task and ValueTask nameof(Task.Result), }; + static readonly string[] continueWith = new[] + { + nameof(Task.ContinueWith), + }; public DoNotUseBlockingTaskOperations() : base(Descriptors.X1031_DoNotUseBlockingTaskOperations) @@ -66,6 +70,9 @@ public override void AnalyzeCompilation( if (!foundSymbol) return; + if (WrappedInContinueWith(invocation, taskType, xunitContext)) + return; + // Should have two child nodes: "(some other code).(target method)" and the arguments var invocationChildren = invocation.Syntax.ChildNodes().ToList(); if (invocationChildren.Count != 2) @@ -95,6 +102,9 @@ public override void AnalyzeCompilation( if (!foundSymbol) return; + if (WrappedInContinueWith(reference, taskType, xunitContext)) + return; + // Should have two child nodes: "(some other code)" and "(property name)" var propertyChildren = reference.Syntax.ChildNodes().ToList(); if (propertyChildren.Count != 2) @@ -131,4 +141,24 @@ static bool FindSymbol( // Only trigger when you're inside a test method return operation.IsInTestMethod(xunitContext); } + + static bool WrappedInContinueWith( + IOperation? operation, + INamedTypeSymbol? taskType, + XunitContext xunitContext) + { + if (taskType is null) + return false; + + for (; operation != null; operation = operation.Parent) + { + if (operation is not IInvocationOperation invocation) + continue; + + if (FindSymbol(invocation.TargetMethod, invocation, taskType, continueWith, xunitContext)) + return true; + } + + return false; + } }