Merge pull request #339 from sharwell/customize-legacy-apis
Allow extensions to the set of legacy thread switching members
This commit is contained in:
Коммит
ad33237fbe
|
@ -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.
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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<QualifiedMember> 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<SyncBlockingMethod> SyncBlockingProperties = new[]
|
||||
{
|
||||
new SyncBlockingMethod(new QualifiedMember(new QualifiedType(Namespaces.SystemThreadingTasks, nameof(Task)), nameof(Task<int>.Result)), null),
|
||||
|
|
|
@ -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<CommonInterest.QualifiedMember> legacyThreadSwitchingMembers;
|
||||
|
||||
if (legacyMethod.IsMatch(invokeMethod))
|
||||
public Analyzer(ImmutableArray<CommonInterest.QualifiedMember> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
Загрузка…
Ссылка в новой задаче