diff --git a/doc/analyzers/VSTHRD001.md b/doc/analyzers/VSTHRD001.md index bf35078f..cb9dc48e 100644 --- a/doc/analyzers/VSTHRD001.md +++ b/doc/analyzers/VSTHRD001.md @@ -26,3 +26,8 @@ void Foo() { }); } ``` + +## Configuration + +This analyzer is configurable via the `vs-threading.LegacyThreadSwitchingMembers.txt` file. +See our [configuration](configuration.md) topic for more information. diff --git a/doc/analyzers/configuration.md b/doc/analyzers/configuration.md index 0020fde1..8da25090 100644 --- a/doc/analyzers/configuration.md +++ b/doc/analyzers/configuration.md @@ -66,3 +66,16 @@ These are identified as described below: Properties are specified by their name, not the name of their accessors. For example, a property should be specified by `PropertyName`, not `get_PropertyName`. + +## Legacy thread switching members + +Prior to Microsoft.VisualStudio.Threading, libraries provided additional ways to execute +code on the UI thread. These methods should be avoided, and code should update to using +`JoinableTaskFactory.SwitchToMainThreadAsync()` as the standard way to switch to the main +thread. + +**Filename:** `vs-threading.LegacyThreadSwitchingMembers.txt` + +**Line format:** `[Namespace.TypeName]::MethodName` + +**Sample:** `[System.Windows.Threading.Dispatcher]::Invoke` diff --git a/src/Microsoft.VisualStudio.Threading.Analyzers/CommonInterest.cs b/src/Microsoft.VisualStudio.Threading.Analyzers/CommonInterest.cs index bba9b76b..d4787327 100644 --- a/src/Microsoft.VisualStudio.Threading.Analyzers/CommonInterest.cs +++ b/src/Microsoft.VisualStudio.Threading.Analyzers/CommonInterest.cs @@ -18,6 +18,7 @@ namespace Microsoft.VisualStudio.Threading.Analyzers internal static class CommonInterest { + internal static readonly Regex FileNamePatternForLegacyThreadSwitchingMembers = new Regex(@"^vs-threading\.LegacyThreadSwitchingMembers(\..*)?.txt$", FileNamePatternRegexOptions); internal static readonly Regex FileNamePatternForMembersRequiringMainThread = new Regex(@"^vs-threading\.MembersRequiringMainThread(\..*)?.txt$", FileNamePatternRegexOptions); internal static readonly Regex FileNamePatternForMethodsThatAssertMainThread = new Regex(@"^vs-threading\.MainThreadAssertingMethods(\..*)?.txt$", FileNamePatternRegexOptions); internal static readonly Regex FileNamePatternForMethodsThatSwitchToMainThread = new Regex(@"^vs-threading\.MainThreadSwitchingMethods(\..*)?.txt$", FileNamePatternRegexOptions); @@ -40,18 +41,6 @@ namespace Microsoft.VisualStudio.Threading.Analyzers new SyncBlockingMethod(new QualifiedMember(new QualifiedType(Namespaces.MicrosoftVisualStudioShellInterop, "IVsTask"), "GetResult"), extensionMethodNamespace: Namespaces.MicrosoftVisualStudioShell), }); - internal static readonly IEnumerable LegacyThreadSwitchingMethods = new[] - { - new QualifiedMember(new QualifiedType(Namespaces.MicrosoftVisualStudioShell, Types.ThreadHelper.TypeName), Types.ThreadHelper.Invoke), - new QualifiedMember(new QualifiedType(Namespaces.MicrosoftVisualStudioShell, Types.ThreadHelper.TypeName), Types.ThreadHelper.InvokeAsync), - new QualifiedMember(new QualifiedType(Namespaces.MicrosoftVisualStudioShell, Types.ThreadHelper.TypeName), Types.ThreadHelper.BeginInvoke), - new QualifiedMember(new QualifiedType(Namespaces.SystemWindowsThreading, Types.Dispatcher.TypeName), Types.Dispatcher.Invoke), - new QualifiedMember(new QualifiedType(Namespaces.SystemWindowsThreading, Types.Dispatcher.TypeName), Types.Dispatcher.BeginInvoke), - new QualifiedMember(new QualifiedType(Namespaces.SystemWindowsThreading, Types.Dispatcher.TypeName), Types.Dispatcher.InvokeAsync), - new QualifiedMember(new QualifiedType(Namespaces.SystemThreading, Types.SynchronizationContext.TypeName), Types.SynchronizationContext.Send), - new QualifiedMember(new QualifiedType(Namespaces.SystemThreading, Types.SynchronizationContext.TypeName), Types.SynchronizationContext.Post), - }; - internal static readonly IReadOnlyList SyncBlockingProperties = new[] { new SyncBlockingMethod(new QualifiedMember(new QualifiedType(Namespaces.SystemThreadingTasks, nameof(Task)), nameof(Task.Result)), null), diff --git a/src/Microsoft.VisualStudio.Threading.Analyzers/VSTHRD001UseSwitchToMainThreadAsyncAnalyzer.cs b/src/Microsoft.VisualStudio.Threading.Analyzers/VSTHRD001UseSwitchToMainThreadAsyncAnalyzer.cs index 469d98ad..cfbd3940 100644 --- a/src/Microsoft.VisualStudio.Threading.Analyzers/VSTHRD001UseSwitchToMainThreadAsyncAnalyzer.cs +++ b/src/Microsoft.VisualStudio.Threading.Analyzers/VSTHRD001UseSwitchToMainThreadAsyncAnalyzer.cs @@ -1,11 +1,6 @@ namespace Microsoft.VisualStudio.Threading.Analyzers { - using System; - using System.Collections.Generic; using System.Collections.Immutable; - using System.Linq; - using System.Text; - using System.Threading.Tasks; using CodeAnalysis; using CodeAnalysis.CSharp; using CodeAnalysis.CSharp.Syntax; @@ -32,26 +27,41 @@ context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze); - context.RegisterSyntaxNodeAction(Utils.DebuggableWrapper(this.AnalyzeInvocation), SyntaxKind.InvocationExpression); + context.RegisterCompilationStartAction(compilationStartContext => + { + var legacyThreadSwitchingMembers = CommonInterest.ReadMethods(compilationStartContext.Options, CommonInterest.FileNamePatternForLegacyThreadSwitchingMembers, compilationStartContext.CancellationToken).ToImmutableArray(); + var analyzer = new Analyzer(legacyThreadSwitchingMembers); + compilationStartContext.RegisterSyntaxNodeAction(Utils.DebuggableWrapper(analyzer.AnalyzeInvocation), SyntaxKind.InvocationExpression); + }); } - private void AnalyzeInvocation(SyntaxNodeAnalysisContext context) + private class Analyzer { - var invocationSyntax = (InvocationExpressionSyntax)context.Node; - var invokeMethod = context.SemanticModel.GetSymbolInfo(context.Node).Symbol as IMethodSymbol; - if (invokeMethod != null) - { - foreach (var legacyMethod in CommonInterest.LegacyThreadSwitchingMethods) - { - context.CancellationToken.ThrowIfCancellationRequested(); + private readonly ImmutableArray legacyThreadSwitchingMembers; - if (legacyMethod.IsMatch(invokeMethod)) + public Analyzer(ImmutableArray legacyThreadSwitchingMembers) + { + this.legacyThreadSwitchingMembers = legacyThreadSwitchingMembers; + } + + internal void AnalyzeInvocation(SyntaxNodeAnalysisContext context) + { + var invocationSyntax = (InvocationExpressionSyntax)context.Node; + var invokeMethod = context.SemanticModel.GetSymbolInfo(context.Node).Symbol as IMethodSymbol; + if (invokeMethod != null) + { + foreach (var legacyMethod in this.legacyThreadSwitchingMembers) { - var diagnostic = Diagnostic.Create( - Descriptor, - invocationSyntax.Expression.GetLocation()); - context.ReportDiagnostic(diagnostic); - break; + context.CancellationToken.ThrowIfCancellationRequested(); + + if (legacyMethod.IsMatch(invokeMethod)) + { + var diagnostic = Diagnostic.Create( + Descriptor, + invocationSyntax.Expression.GetLocation()); + context.ReportDiagnostic(diagnostic); + break; + } } } } diff --git a/src/Microsoft.VisualStudio.Threading.Analyzers/build/AdditionalFiles/vs-threading.LegacyThreadSwitchingMembers.txt b/src/Microsoft.VisualStudio.Threading.Analyzers/build/AdditionalFiles/vs-threading.LegacyThreadSwitchingMembers.txt new file mode 100644 index 00000000..fd36f3e9 --- /dev/null +++ b/src/Microsoft.VisualStudio.Threading.Analyzers/build/AdditionalFiles/vs-threading.LegacyThreadSwitchingMembers.txt @@ -0,0 +1,8 @@ +[Microsoft.VisualStudio.Shell.ThreadHelper]::Invoke +[Microsoft.VisualStudio.Shell.ThreadHelper]::InvokeAsync +[Microsoft.VisualStudio.Shell.ThreadHelper]::BeginInvoke +[System.Windows.Threading.Dispatcher]::Invoke +[System.Windows.Threading.Dispatcher]::InvokeAsync +[System.Windows.Threading.Dispatcher]::BeginInvoke +[System.Threading.SynchronizationContext]::Send +[System.Threading.SynchronizationContext]::Post