diff --git a/src/Microsoft.VisualStudio.Threading.Analyzers.CodeFixes/VSTHRD010MainThreadUsageCodeFix.cs b/src/Microsoft.VisualStudio.Threading.Analyzers.CodeFixes/VSTHRD010MainThreadUsageCodeFix.cs index c1f5136ad..afc728e91 100644 --- a/src/Microsoft.VisualStudio.Threading.Analyzers.CodeFixes/VSTHRD010MainThreadUsageCodeFix.cs +++ b/src/Microsoft.VisualStudio.Threading.Analyzers.CodeFixes/VSTHRD010MainThreadUsageCodeFix.cs @@ -62,8 +62,11 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) Regex lookupKey = (container.IsAsync || convertToAsync) ? CommonInterest.FileNamePatternForMethodsThatSwitchToMainThread : CommonInterest.FileNamePatternForMethodsThatAssertMainThread; - string[]? options = diagnostic.Properties[lookupKey.ToString()]?.Split('\n'); - if (options?.Length > 0) + string[] options = diagnostic.Properties[lookupKey.ToString()]? + .Split('\n') + .Where(option => !string.IsNullOrWhiteSpace(option)) + .ToArray() ?? Array.Empty(); + if (options.Length > 0) { // For any symbol lookups, we want to consider the position of the very first statement in the block. int positionForLookup = container.BlockOrExpression.GetLocation().SourceSpan.Start + 1; diff --git a/test/Microsoft.VisualStudio.Threading.Analyzers.Tests/VSTHRD010MainThreadUsageAnalyzerTests.cs b/test/Microsoft.VisualStudio.Threading.Analyzers.Tests/VSTHRD010MainThreadUsageAnalyzerTests.cs index be4f8eb14..f003f8f0b 100644 --- a/test/Microsoft.VisualStudio.Threading.Analyzers.Tests/VSTHRD010MainThreadUsageAnalyzerTests.cs +++ b/test/Microsoft.VisualStudio.Threading.Analyzers.Tests/VSTHRD010MainThreadUsageAnalyzerTests.cs @@ -870,6 +870,66 @@ async Task F() { await CSVerify.VerifyAnalyzerAsync(test); } + [Fact] + public async Task InvokeVsSolutionAfterSwitchedToMainThreadAsyncInAnonymousMethod() + { + var test = @" +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Threading; + +namespace Microsoft.VisualStudio.Shell.Interop { + interface IVsInvokablePrivate { + int Invoke(); + } +} + +class Test { + private JoinableTaskContext joinableTaskContext; + + int Invoke(IVsInvokablePrivate invokable) { + return this.joinableTaskContext.Factory.Run(async delegate { + await this.joinableTaskContext.Factory.SwitchToMainThreadAsync(); + return invokable.Invoke(); + }); + } +} +"; + await CSVerify.VerifyAnalyzerAsync(test); + } + + [Fact] + public async Task CodeFixWithNoMainThreadSwitchingMethodsInAnonymousMethod() + { + var test = @" +using System; +using System.Threading.Tasks; + +namespace TestNS { + interface IThreadAffinitized { + int Invoke(); + } +} + +class Test { + void F(TestNS.IThreadAffinitized value) { + Func> callback = async delegate { + await Task.Yield(); + return value.Invoke(); + }; + } +} +"; + var verifier = new CSVerify.Test + { + TestCode = test, + ExpectedDiagnostics = { CSVerify.Diagnostic(DescriptorAsync).WithSpan(15, 26, 15, 32).WithArguments("IThreadAffinitized") }, + FixedCode = test, + }; + verifier.TestState.AdditionalFilesFactories.Clear(); + verifier.TestState.AdditionalFiles.Add(("vs-threading.MembersRequiringMainThread.txt", "[TestNS.IThreadAffinitized]")); + await verifier.RunAsync(); + } + [Fact] public async Task InvokeVsSolutionInLambda() {