未验证 提交 c5ba1d8f 编写于 作者: T Tomáš Matoušek 提交者: GitHub

Separate DiagnosticAnalyzer descriptor cache from host analyzers (#42163)

* Remove IsCompilerDiagnostic, IsCompilerDiagnosticAnalyzer from DiagnosticAnalyzerInfoCache

* Separate DiagnosticAnalyzer descriptor cache from host analyzers

* Split and rename file
上级 a4e28751
......@@ -148,7 +148,7 @@ public async Task AnalyzerOptionsArePassedToAllAnalyzers()
private void AccessSupportedDiagnostics(DiagnosticAnalyzer analyzer)
{
var diagnosticService = new TestDiagnosticAnalyzerService(LanguageNames.CSharp, analyzer);
diagnosticService.AnalyzerInfoCache.GetDiagnosticDescriptorsPerReference();
diagnosticService.GetDiagnosticDescriptorsPerReference();
}
private class ThrowingDoNotCatchDiagnosticAnalyzer<TLanguageKindEnum> : ThrowingDiagnosticAnalyzer<TLanguageKindEnum>, IBuiltInAnalyzer where TLanguageKindEnum : struct
......
......@@ -548,13 +548,14 @@ internal MyDiagnosticAnalyzerService(DiagnosticAnalyzer analyzer, IAsynchronousO
}
internal MyDiagnosticAnalyzerService(IEnumerable<DiagnosticAnalyzer> analyzers, IAsynchronousOperationListener listener, string language = LanguageNames.CSharp)
: base(new DiagnosticAnalyzerInfoCache(
: base(new DiagnosticAnalyzerInfoCache(),
new HostDiagnosticAnalyzers(
ImmutableArray.Create<AnalyzerReference>(
new TestAnalyzerReferenceByLanguage(
ImmutableDictionary<string, ImmutableArray<DiagnosticAnalyzer>>.Empty.Add(language, ImmutableArray.CreateRange(analyzers))))),
hostDiagnosticUpdateSource: null,
registrationService: new MockDiagnosticUpdateSourceRegistrationService(),
listener: listener)
hostDiagnosticUpdateSource: null,
registrationService: new MockDiagnosticUpdateSourceRegistrationService(),
listener: listener)
{
}
}
......
......@@ -147,7 +147,8 @@ private class MyDiagnosticAnalyzerService : DiagnosticAnalyzerService
internal MyDiagnosticAnalyzerService(
ImmutableDictionary<string, ImmutableArray<DiagnosticAnalyzer>> analyzersMap,
IAsynchronousOperationListener listener)
: base(new DiagnosticAnalyzerInfoCache(ImmutableArray.Create<AnalyzerReference>(new TestAnalyzerReferenceByLanguage(analyzersMap))),
: base(new DiagnosticAnalyzerInfoCache(),
new HostDiagnosticAnalyzers(ImmutableArray.Create<AnalyzerReference>(new TestAnalyzerReferenceByLanguage(analyzersMap))),
hostDiagnosticUpdateSource: null,
registrationService: new MockDiagnosticUpdateSourceRegistrationService(),
listener: listener)
......
......@@ -150,7 +150,7 @@ private Checksum[] AddGlobalAssets(Workspace workspace)
var snapshotService = workspace.Services.GetService<CodeAnalysis.Execution.IRemotableDataService>();
var assetBuilder = new CodeAnalysis.Execution.CustomAssetBuilder(workspace);
foreach (var (_, reference) in _diagnosticAnalyzerService.AnalyzerInfoCache.GetHostAnalyzerReferencesMap())
foreach (var (_, reference) in _diagnosticAnalyzerService.HostAnalyzers.GetHostAnalyzerReferencesMap())
{
if (!(reference is AnalyzerFileReference))
{
......
......@@ -18,7 +18,7 @@ internal sealed class TestDiagnosticAnalyzerService : DiagnosticAnalyzerService
string language,
DiagnosticAnalyzer analyzer,
AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource = null)
: this(CreateHostAnalyzerManager(language, analyzer), hostDiagnosticUpdateSource)
: this(CreateHostDiagnosticAnalyzers(language, ImmutableArray.Create(analyzer)), hostDiagnosticUpdateSource)
{
}
......@@ -27,7 +27,7 @@ internal sealed class TestDiagnosticAnalyzerService : DiagnosticAnalyzerService
ImmutableArray<DiagnosticAnalyzer> analyzers,
AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource = null,
IAsynchronousOperationListener listener = null)
: this(CreateHostAnalyzerManager(language, analyzers), hostDiagnosticUpdateSource, listener: listener)
: this(CreateHostDiagnosticAnalyzers(language, analyzers), hostDiagnosticUpdateSource, listener: listener)
{
}
......@@ -35,23 +35,23 @@ internal sealed class TestDiagnosticAnalyzerService : DiagnosticAnalyzerService
ImmutableDictionary<string, ImmutableArray<DiagnosticAnalyzer>> analyzersMap,
AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource = null,
IDiagnosticUpdateSourceRegistrationService registrationService = null)
: this(CreateHostAnalyzerManager(analyzersMap), hostDiagnosticUpdateSource, registrationService)
: this(CreateHostDiagnosticAnalyzers(analyzersMap), hostDiagnosticUpdateSource, registrationService)
{
}
internal TestDiagnosticAnalyzerService(
ImmutableArray<AnalyzerReference> hostAnalyzerReferences,
AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource = null)
: this(CreateHostAnalyzerManager(hostAnalyzerReferences), hostDiagnosticUpdateSource)
: this(new HostDiagnosticAnalyzers(hostAnalyzerReferences), hostDiagnosticUpdateSource)
{
}
private TestDiagnosticAnalyzerService(
DiagnosticAnalyzerInfoCache analyzerInfoCache,
HostDiagnosticAnalyzers hostAnalyzers,
AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource,
IDiagnosticUpdateSourceRegistrationService registrationService = null,
IAsynchronousOperationListener listener = null)
: base(analyzerInfoCache, hostDiagnosticUpdateSource, registrationService ?? new MockDiagnosticUpdateSourceRegistrationService(), listener)
: base(new DiagnosticAnalyzerInfoCache(), hostAnalyzers, hostDiagnosticUpdateSource, registrationService ?? new MockDiagnosticUpdateSourceRegistrationService(), listener)
{
}
......@@ -63,26 +63,22 @@ internal sealed class TestDiagnosticAnalyzerService : DiagnosticAnalyzerService
{
}
private static DiagnosticAnalyzerInfoCache CreateHostAnalyzerManager(string language, DiagnosticAnalyzer analyzer)
{
return CreateHostAnalyzerManager(language, ImmutableArray.Create(analyzer));
}
private static DiagnosticAnalyzerInfoCache CreateHostAnalyzerManager(string language, ImmutableArray<DiagnosticAnalyzer> analyzers)
private static HostDiagnosticAnalyzers CreateHostDiagnosticAnalyzers(string language, ImmutableArray<DiagnosticAnalyzer> analyzers)
{
var map = ImmutableDictionary.CreateRange(SpecializedCollections.SingletonEnumerable(KeyValuePairUtil.Create(language, analyzers)));
return CreateHostAnalyzerManager(map);
return CreateHostDiagnosticAnalyzers(map);
}
private static DiagnosticAnalyzerInfoCache CreateHostAnalyzerManager(ImmutableDictionary<string, ImmutableArray<DiagnosticAnalyzer>> analyzersMap)
private static HostDiagnosticAnalyzers CreateHostDiagnosticAnalyzers(ImmutableDictionary<string, ImmutableArray<DiagnosticAnalyzer>> analyzersMap)
{
var analyzerReferences = ImmutableArray.Create<AnalyzerReference>(new TestAnalyzerReferenceByLanguage(analyzersMap));
return CreateHostAnalyzerManager(analyzerReferences);
return new HostDiagnosticAnalyzers(analyzerReferences);
}
private static DiagnosticAnalyzerInfoCache CreateHostAnalyzerManager(ImmutableArray<AnalyzerReference> hostAnalyzerReferences)
{
return new DiagnosticAnalyzerInfoCache(hostAnalyzerReferences);
}
public ImmutableDictionary<string, ImmutableArray<DiagnosticDescriptor>> GetDiagnosticDescriptorsPerReference()
=> HostAnalyzers.GetDiagnosticDescriptorsPerReference(AnalyzerInfoCache);
public ImmutableDictionary<string, ImmutableArray<DiagnosticDescriptor>> GetDiagnosticDescriptorsPerReference(Project project)
=> HostAnalyzers.GetDiagnosticDescriptorsPerReference(AnalyzerInfoCache, project);
}
}
......@@ -81,7 +81,7 @@ End Class
Private Sub AccessSupportedDiagnostics(analyzer As DiagnosticAnalyzer)
Dim diagnosticService = New TestDiagnosticAnalyzerService(LanguageNames.VisualBasic, analyzer)
diagnosticService.AnalyzerInfoCache.GetDiagnosticDescriptorsPerReference()
diagnosticService.GetDiagnosticDescriptorsPerReference()
End Sub
<Fact>
......
......@@ -17,6 +17,7 @@
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
......@@ -67,7 +68,7 @@ public static ReportDiagnostic GetEffectiveSeverity(this DiagnosticDescriptor de
public static bool IsCompilerAnalyzer(this DiagnosticAnalyzer analyzer)
{
// TODO: find better way.
var typeString = analyzer.GetType().ToString();
var typeString = analyzer.GetType().FullName;
if (typeString == CSharpCompilerAnalyzerTypeName)
{
return true;
......@@ -308,7 +309,6 @@ private static void AssertCompilation(Project project, Compilation compilation1)
DiagnosticAnalyzer analyzer,
Document document,
AnalysisKind kind,
DiagnosticAnalyzerInfoCache analyzerInfoCache,
CompilationWithAnalyzers? compilationWithAnalyzers,
TextSpan? span,
CancellationToken cancellationToken)
......@@ -336,7 +336,7 @@ private static void AssertCompilation(Project project, Compilation compilation1)
}
// quick optimization to reduce allocations.
if (compilationWithAnalyzers == null || !analyzerInfoCache.SupportAnalysisKind(analyzer, document.Project.Language, kind))
if (compilationWithAnalyzers == null || !analyzer.SupportAnalysisKind(kind))
{
if (kind == AnalysisKind.Syntax)
{
......@@ -348,8 +348,7 @@ private static void AssertCompilation(Project project, Compilation compilation1)
}
// if project is not loaded successfully then, we disable semantic errors for compiler analyzers
if (kind != AnalysisKind.Syntax &&
analyzerInfoCache.IsCompilerDiagnosticAnalyzer(document.Project.Language, analyzer))
if (kind != AnalysisKind.Syntax && analyzer.IsCompilerAnalyzer())
{
var isEnabled = await document.Project.HasSuccessfullyLoadedAsync(cancellationToken).ConfigureAwait(false);
......
......@@ -22,14 +22,14 @@ namespace Microsoft.CodeAnalysis.Diagnostics
[ExportIncrementalAnalyzerProvider(WellKnownSolutionCrawlerAnalyzers.Diagnostic, workspaceKinds: null)]
internal partial class DefaultDiagnosticAnalyzerService : IIncrementalAnalyzerProvider, IDiagnosticUpdateSource
{
private readonly DiagnosticAnalyzerInfoCache _analyzerInfoCache;
private readonly HostDiagnosticAnalyzers _hostAnalyzers;
[ImportingConstructor]
public DefaultDiagnosticAnalyzerService(
IDiagnosticAnalyzerService analyzerService,
IDiagnosticUpdateSourceRegistrationService registrationService)
{
_analyzerInfoCache = analyzerService.AnalyzerInfoCache;
_hostAnalyzers = analyzerService.HostAnalyzers;
registrationService.Register(this);
}
......@@ -155,8 +155,7 @@ private async Task AnalyzeForKindAsync(Document document, AnalysisKind kind, Can
return ImmutableArray.Create(DiagnosticData.Create(loadDiagnostic, document));
}
var analyzerInfoCache = _service._analyzerInfoCache;
var analyzers = GetAnalyzers(analyzerInfoCache, document.Project);
var analyzers = GetAnalyzers(_service._hostAnalyzers, document.Project);
var compilationWithAnalyzers = await AnalyzerHelper.CreateCompilationWithAnalyzersAsync(
document.Project, analyzers, includeSuppressedDiagnostics: false, cancellationToken).ConfigureAwait(false);
......@@ -165,23 +164,23 @@ private async Task AnalyzeForKindAsync(Document document, AnalysisKind kind, Can
foreach (var analyzer in analyzers)
{
builder.AddRange(await AnalyzerHelper.ComputeDiagnosticsAsync(analyzer,
document, kind, analyzerInfoCache, compilationWithAnalyzers, span: null, cancellationToken).ConfigureAwait(false));
document, kind, compilationWithAnalyzers, span: null, cancellationToken).ConfigureAwait(false));
}
return builder.ToImmutableAndFree();
}
private static IEnumerable<DiagnosticAnalyzer> GetAnalyzers(DiagnosticAnalyzerInfoCache analyzerInfoCache, Project project)
private static IEnumerable<DiagnosticAnalyzer> GetAnalyzers(HostDiagnosticAnalyzers hostAnalyzers, Project project)
{
// C# or VB document that supports compiler
var compilerAnalyzer = analyzerInfoCache.GetCompilerDiagnosticAnalyzer(project.Language);
var compilerAnalyzer = hostAnalyzers.GetCompilerDiagnosticAnalyzer(project.Language);
if (compilerAnalyzer != null)
{
return SpecializedCollections.SingletonEnumerable(compilerAnalyzer);
}
// document that doesn't support compiler diagnostics such as FSharp or TypeScript
return analyzerInfoCache.CreateDiagnosticAnalyzersPerReference(project).Values.SelectMany(v => v);
return hostAnalyzers.CreateDiagnosticAnalyzersPerReference(project).Values.SelectMany(v => v);
}
public void RemoveDocument(DocumentId documentId)
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
using System.Collections.Immutable;
using System.Linq;
using System.Runtime.CompilerServices;
using Microsoft.CodeAnalysis;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Diagnostics
{
/// <summary>
/// Provides and caches information about diagnostic analyzers such as <see cref="AnalyzerReference"/>,
/// <see cref="DiagnosticAnalyzer"/> instance, <see cref="DiagnosticDescriptor"/>s.
/// Thread-safe.
/// </summary>
internal sealed class DiagnosticAnalyzerInfoCache
{
/// <summary>
/// Supported descriptors of each <see cref="DiagnosticAnalyzer"/>.
/// </summary>
/// <remarks>
/// Holds on <see cref="DiagnosticAnalyzer"/> instances weakly so that we don't keep analyzers coming from package references alive.
/// They need to be released when the project stops referencing the analyzer.
///
/// The purpose of this map is to avoid multiple calls to <see cref="DiagnosticAnalyzer.SupportedDiagnostics"/> that might return different values
/// (they should not but we need a guarantee to function correctly).
/// </remarks>
private readonly ConditionalWeakTable<DiagnosticAnalyzer, DiagnosticDescriptorsInfo> _descriptorsInfo;
private sealed class DiagnosticDescriptorsInfo
{
public readonly ImmutableArray<DiagnosticDescriptor> SupportedDescriptors;
public readonly bool TelemetryAllowed;
public DiagnosticDescriptorsInfo(ImmutableArray<DiagnosticDescriptor> supportedDescriptors, bool telemetryAllowed)
{
SupportedDescriptors = supportedDescriptors;
TelemetryAllowed = telemetryAllowed;
}
}
internal DiagnosticAnalyzerInfoCache()
{
_descriptorsInfo = new ConditionalWeakTable<DiagnosticAnalyzer, DiagnosticDescriptorsInfo>();
}
/// <summary>
/// Returns <see cref="DiagnosticAnalyzer.SupportedDiagnostics"/> of given <paramref name="analyzer"/>.
/// </summary>
public ImmutableArray<DiagnosticDescriptor> GetDiagnosticDescriptors(DiagnosticAnalyzer analyzer)
=> GetOrCreateDescriptorsInfo(analyzer).SupportedDescriptors;
/// <summary>
/// Determine whether collection of telemetry is allowed for given <paramref name="analyzer"/>.
/// </summary>
public bool IsTelemetryCollectionAllowed(DiagnosticAnalyzer analyzer)
=> GetOrCreateDescriptorsInfo(analyzer).TelemetryAllowed;
private DiagnosticDescriptorsInfo GetOrCreateDescriptorsInfo(DiagnosticAnalyzer analyzer)
=> _descriptorsInfo.GetValue(analyzer, CalculateDescriptorsInfo);
private DiagnosticDescriptorsInfo CalculateDescriptorsInfo(DiagnosticAnalyzer analyzer)
{
ImmutableArray<DiagnosticDescriptor> descriptors;
try
{
// SupportedDiagnostics is user code and can throw an exception.
descriptors = analyzer.SupportedDiagnostics.NullToEmpty();
}
catch
{
// No need to report the exception to the user.
// Eventually, when the analyzer runs the compiler analyzer driver will report a diagnostic.
descriptors = ImmutableArray<DiagnosticDescriptor>.Empty;
}
bool telemetryAllowed = IsTelemetryCollectionAllowed(analyzer, descriptors);
return new DiagnosticDescriptorsInfo(descriptors, telemetryAllowed);
}
private static bool IsTelemetryCollectionAllowed(DiagnosticAnalyzer analyzer, ImmutableArray<DiagnosticDescriptor> descriptors)
=> analyzer.IsCompilerAnalyzer() ||
analyzer is IBuiltInAnalyzer ||
descriptors.Length > 0 && descriptors[0].CustomTags.Any(t => t == WellKnownDiagnosticTags.Telemetry);
/// <summary>
/// Return true if the given <paramref name="analyzer"/> is suppressed for the given project.
/// NOTE: This API is intended to be used only for performance optimization.
/// </summary>
public bool IsAnalyzerSuppressed(DiagnosticAnalyzer analyzer, Project project)
{
var options = project.CompilationOptions;
if (options == null || analyzer == FileContentLoadAnalyzer.Instance || analyzer.IsCompilerAnalyzer())
{
return false;
}
// If user has disabled analyzer execution for this project, we only want to execute required analyzers
// that report diagnostics with category "Compiler".
if (!project.State.RunAnalyzers &&
GetDiagnosticDescriptors(analyzer).All(d => d.Category != DiagnosticCategory.Compiler))
{
return true;
}
// NOTE: Previously we used to return "CompilationWithAnalyzers.IsDiagnosticAnalyzerSuppressed(options)"
// on this code path, which returns true if analyzer is suppressed through compilation options.
// However, this check is no longer correct as analyzers can be enabled/disabled for individual
// documents through .editorconfig files. So we pessimistically assume analyzer is not suppressed
// and let the core analyzer driver in the compiler layer handle skipping redundant analysis callbacks.
return false;
}
}
}
......@@ -24,6 +24,7 @@ namespace Microsoft.CodeAnalysis.Diagnostics
internal partial class DiagnosticAnalyzerService : IDiagnosticAnalyzerService
{
public DiagnosticAnalyzerInfoCache AnalyzerInfoCache { get; private set; }
public HostDiagnosticAnalyzers HostAnalyzers { get; private set; }
private readonly AbstractHostDiagnosticUpdateSource? _hostDiagnosticUpdateSource;
......@@ -53,7 +54,8 @@ internal partial class DiagnosticAnalyzerService : IDiagnosticAnalyzerService
PrimaryWorkspace primaryWorkspace,
IDiagnosticUpdateSourceRegistrationService registrationService,
IAsynchronousOperationListener? listener = null)
: this(new DiagnosticAnalyzerInfoCache(workspaceAnalyzerPackages, hostAnalyzerAssemblyLoader, hostDiagnosticUpdateSource, primaryWorkspace),
: this(new DiagnosticAnalyzerInfoCache(),
new HostDiagnosticAnalyzers(workspaceAnalyzerPackages, hostAnalyzerAssemblyLoader, hostDiagnosticUpdateSource, primaryWorkspace),
hostDiagnosticUpdateSource,
registrationService,
listener)
......@@ -63,12 +65,14 @@ internal partial class DiagnosticAnalyzerService : IDiagnosticAnalyzerService
// protected for testing purposes.
protected DiagnosticAnalyzerService(
DiagnosticAnalyzerInfoCache analyzerInfoCache,
HostDiagnosticAnalyzers hostAnalyzers,
AbstractHostDiagnosticUpdateSource? hostDiagnosticUpdateSource,
IDiagnosticUpdateSourceRegistrationService registrationService,
IAsynchronousOperationListener? listener = null)
: this(registrationService)
{
AnalyzerInfoCache = analyzerInfoCache;
HostAnalyzers = hostAnalyzers;
_hostDiagnosticUpdateSource = hostDiagnosticUpdateSource;
Listener = listener ?? AsynchronousOperationListenerProvider.NullListener;
}
......
......@@ -49,7 +49,7 @@ private DiagnosticIncrementalAnalyzer CreateIncrementalAnalyzerCallback(Workspac
// subscribe to active context changed event for new workspace
workspace.DocumentActiveContextChanged += OnDocumentActiveContextChanged;
return new DiagnosticIncrementalAnalyzer(this, LogAggregator.GetNextId(), workspace, AnalyzerInfoCache, _hostDiagnosticUpdateSource);
return new DiagnosticIncrementalAnalyzer(this, LogAggregator.GetNextId(), workspace, HostAnalyzers, AnalyzerInfoCache, _hostDiagnosticUpdateSource);
}
private void OnDocumentActiveContextChanged(object sender, DocumentActiveContextChangedEventArgs e)
......
......@@ -54,7 +54,7 @@ internal partial class DiagnosticIncrementalAnalyzer
return new DocumentAnalysisData(version, existingData.Items, ImmutableArray<DiagnosticData>.Empty);
}
var diagnostics = await AnalyzerHelper.ComputeDiagnosticsAsync(stateSet.Analyzer, document, kind, DiagnosticAnalyzerInfoCache, compilation, span: null, cancellationToken).ConfigureAwait(false);
var diagnostics = await AnalyzerHelper.ComputeDiagnosticsAsync(stateSet.Analyzer, document, kind, compilation, span: null, cancellationToken).ConfigureAwait(false);
// this is no-op in product. only run in test environment
Logger.Log(functionId, (t, d, a, ds) => $"{GetDocumentLogMessage(t, d, a)}, {string.Join(Environment.NewLine, ds)}",
......@@ -144,7 +144,7 @@ private static bool CompilationHasOpenFileOnlyAnalyzers(CompilationWithAnalyzers
return result;
}
var compilerAnalyzer = DiagnosticAnalyzerInfoCache.GetCompilerDiagnosticAnalyzer(project.Language);
var compilerAnalyzer = HostAnalyzers.GetCompilerDiagnosticAnalyzer(project.Language);
if (compilerAnalyzer == null)
{
// this language doesn't support compiler analyzer
......
......@@ -21,17 +21,17 @@ public IEnumerable<StateSet> GetAllHostStateSets()
private HostAnalyzerStateSets GetOrCreateHostStateSets(string language)
{
static HostAnalyzerStateSets CreateLanguageSpecificAnalyzerMap(string language, DiagnosticAnalyzerInfoCache analyzerInfoCache)
static HostAnalyzerStateSets CreateLanguageSpecificAnalyzerMap(string language, HostDiagnosticAnalyzers hostAnalyzers)
{
var analyzersPerReference = analyzerInfoCache.GetOrCreateHostDiagnosticAnalyzersPerReference(language);
var analyzersPerReference = hostAnalyzers.GetOrCreateHostDiagnosticAnalyzersPerReference(language);
var analyzerMap = CreateStateSetMap(language, analyzersPerReference.Values, includeFileContentLoadAnalyzer: true);
VerifyUniqueStateNames(analyzerMap.Values);
return new HostAnalyzerStateSets(analyzerInfoCache, language, analyzerMap);
return new HostAnalyzerStateSets(analyzerMap);
}
return ImmutableInterlocked.GetOrAdd(ref _hostAnalyzerStateMap, language, CreateLanguageSpecificAnalyzerMap, _analyzerInfoCache);
return ImmutableInterlocked.GetOrAdd(ref _hostAnalyzerStateMap, language, CreateLanguageSpecificAnalyzerMap, _hostAnalyzers);
}
private sealed class HostAnalyzerStateSets
......@@ -40,19 +40,15 @@ private sealed class HostAnalyzerStateSets
private const int BuiltInCompilerPriority = -2;
private const int RegularDiagnosticAnalyzerPriority = -1;
private readonly DiagnosticAnalyzer? _compilerAnalyzer;
// ordered by priority
public readonly ImmutableArray<StateSet> OrderedStateSets;
public readonly ImmutableDictionary<DiagnosticAnalyzer, StateSet> StateSetMap;
public HostAnalyzerStateSets(DiagnosticAnalyzerInfoCache analyzerInfoCache, string language, ImmutableDictionary<DiagnosticAnalyzer, StateSet> analyzerMap)
public HostAnalyzerStateSets(ImmutableDictionary<DiagnosticAnalyzer, StateSet> analyzerMap)
{
StateSetMap = analyzerMap;
_compilerAnalyzer = analyzerInfoCache.GetCompilerDiagnosticAnalyzer(language);
// order statesets
// order will be in this order
// BuiltIn Compiler Analyzer (C#/VB) < Regular DiagnosticAnalyzers < Document/ProjectDiagnosticAnalyzers
......@@ -67,7 +63,7 @@ private int PriorityComparison(StateSet state1, StateSet state2)
private int GetPriority(StateSet state)
{
// compiler gets highest priority
if (state.Analyzer == _compilerAnalyzer)
if (state.Analyzer.IsCompilerAnalyzer())
{
return BuiltInCompilerPriority;
}
......
......@@ -74,7 +74,7 @@ public IEnumerable<StateSet> GetAllProjectStateSets()
return ImmutableDictionary<DiagnosticAnalyzer, StateSet>.Empty;
}
var analyzersPerReference = _analyzerInfoCache.CreateProjectDiagnosticAnalyzersPerReference(project);
var analyzersPerReference = _hostAnalyzers.CreateProjectDiagnosticAnalyzersPerReference(project);
if (analyzersPerReference.Count == 0)
{
return ImmutableDictionary<DiagnosticAnalyzer, StateSet>.Empty;
......@@ -91,7 +91,7 @@ public IEnumerable<StateSet> GetAllProjectStateSets()
/// </summary>
private ImmutableDictionary<DiagnosticAnalyzer, StateSet> UpdateProjectAnalyzerMap(Project project)
{
var newAnalyzersPerReference = _analyzerInfoCache.CreateProjectDiagnosticAnalyzersPerReference(project);
var newAnalyzersPerReference = _hostAnalyzers.CreateProjectDiagnosticAnalyzersPerReference(project);
var newMap = CreateStateSetMap(project.Language, newAnalyzersPerReference.Values, includeFileContentLoadAnalyzer: false);
RaiseProjectAnalyzerReferenceChangedIfNeeded(project, newAnalyzersPerReference, newMap);
......@@ -153,9 +153,8 @@ public IEnumerable<StateSet> GetAllProjectStateSets()
var builder = ImmutableArray.CreateBuilder<StateSet>();
foreach (var reference in references)
{
var referenceIdentity = _analyzerInfoCache.GetAnalyzerReferenceIdentity(reference);
// check duplication
if (!mapPerReference.TryGetValue(referenceIdentity, out var analyzers))
if (!mapPerReference.TryGetValue(reference.Id, out var analyzers))
{
continue;
}
......
......@@ -23,8 +23,8 @@ internal partial class DiagnosticIncrementalAnalyzer
/// </summary>
private partial class StateManager
{
private readonly DiagnosticAnalyzerInfoCache _analyzerInfoCache;
private readonly IPersistentStorageService _persistentStorageService;
private readonly HostDiagnosticAnalyzers _hostAnalyzers;
/// <summary>
/// Analyzers supplied by the host (IDE). These are built-in to the IDE, the compiler, or from an installed IDE extension (VSIX).
......@@ -42,9 +42,9 @@ private partial class StateManager
/// </summary>
public event EventHandler<ProjectAnalyzerReferenceChangedEventArgs>? ProjectAnalyzerReferenceChanged;
public StateManager(DiagnosticAnalyzerInfoCache analyzerInfoCache, IPersistentStorageService persistentStorageService)
public StateManager(HostDiagnosticAnalyzers hostAnalyzers, IPersistentStorageService persistentStorageService)
{
_analyzerInfoCache = analyzerInfoCache;
_hostAnalyzers = hostAnalyzers;
_persistentStorageService = persistentStorageService;
_hostAnalyzerStateMap = ImmutableDictionary<string, HostAnalyzerStateSets>.Empty;
......@@ -150,20 +150,15 @@ public ImmutableArray<StateSet> CreateBuildOnlyProjectStateSet(Project project)
var hostStateSetMap = hostStateSets.ToDictionary(s => s.Analyzer, s => s);
// create project analyzer reference identity map
var referenceIdentities = project.AnalyzerReferences.Select(r => _analyzerInfoCache.GetAnalyzerReferenceIdentity(r)).ToSet();
var projectAnalyzerReferenceIds = project.AnalyzerReferences.Select(r => r.Id).ToSet();
// create build only stateSet array
var stateSets = ImmutableArray.CreateBuilder<StateSet>();
// we always include compiler analyzer in build only state
var compilerAnalyzer = _analyzerInfoCache.GetCompilerDiagnosticAnalyzer(project.Language);
if (compilerAnalyzer == null)
{
// only way to get here is if MEF is corrupted.
FailFast.OnFatalException(new Exception("How can this happen?"));
}
if (hostStateSetMap.TryGetValue(compilerAnalyzer, out var compilerStateSet))
// include compiler analyzer in build only state, if available
StateSet? compilerStateSet = null;
var compilerAnalyzer = _hostAnalyzers.GetCompilerDiagnosticAnalyzer(project.Language);
if (compilerAnalyzer != null && hostStateSetMap.TryGetValue(compilerAnalyzer, out compilerStateSet))
{
stateSets.Add(compilerStateSet);
}
......@@ -172,10 +167,10 @@ public ImmutableArray<StateSet> CreateBuildOnlyProjectStateSet(Project project)
stateSets.AddRange(GetOrUpdateProjectAnalyzerMap(project).Values);
// now add analyzers that exist in both host and project
var analyzerMap = _analyzerInfoCache.GetOrCreateHostDiagnosticAnalyzersPerReference(project.Language);
foreach (var (identity, analyzers) in analyzerMap)
var hostAnalyzersById = _hostAnalyzers.GetOrCreateHostDiagnosticAnalyzersPerReference(project.Language);
foreach (var (identity, analyzers) in hostAnalyzersById)
{
if (!referenceIdentities.Contains(identity))
if (!projectAnalyzerReferenceIds.Contains(identity))
{
// it is from host analyzer package rather than project analyzer reference
// which build doesn't have
......
......@@ -43,11 +43,13 @@ internal partial class DiagnosticIncrementalAnalyzer : IIncrementalAnalyzer
internal IPersistentStorageService PersistentStorageService { get; }
internal AbstractHostDiagnosticUpdateSource HostDiagnosticUpdateSource { get; }
internal DiagnosticAnalyzerInfoCache DiagnosticAnalyzerInfoCache { get; }
internal HostDiagnosticAnalyzers HostAnalyzers { get; }
public DiagnosticIncrementalAnalyzer(
DiagnosticAnalyzerService analyzerService,
int correlationId,
Workspace workspace,
HostDiagnosticAnalyzers hostAnalyzers,
DiagnosticAnalyzerInfoCache analyzerInfoCache,
AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource)
{
......@@ -55,13 +57,14 @@ internal partial class DiagnosticIncrementalAnalyzer : IIncrementalAnalyzer
AnalyzerService = analyzerService;
Workspace = workspace;
HostAnalyzers = hostAnalyzers;
DiagnosticAnalyzerInfoCache = analyzerInfoCache;
HostDiagnosticUpdateSource = hostDiagnosticUpdateSource;
PersistentStorageService = workspace.Services.GetRequiredService<IPersistentStorageService>();
_correlationId = correlationId;
_stateManager = new StateManager(analyzerInfoCache, PersistentStorageService);
_stateManager = new StateManager(hostAnalyzers, PersistentStorageService);
_stateManager.ProjectAnalyzerReferenceChanged += OnProjectAnalyzerReferenceChanged;
_telemetry = new DiagnosticAnalyzerTelemetry();
......
......@@ -48,7 +48,6 @@ private sealed class LatestDiagnosticsForSpanGetter
private readonly IEnumerable<StateSet> _stateSets;
private readonly CompilationWithAnalyzers? _compilation;
private readonly DiagnosticAnalyzer? _compilerAnalyzer;
private readonly TextSpan _range;
private readonly bool _blockForData;
......@@ -99,7 +98,6 @@ private sealed class LatestDiagnosticsForSpanGetter
_stateSets = stateSets;
_diagnosticId = diagnosticId;
_compilation = compilation;
_compilerAnalyzer = _owner.DiagnosticAnalyzerInfoCache.GetCompilerDiagnosticAnalyzer(_document.Project.Language);
_range = range;
_blockForData = blockForData;
......@@ -160,7 +158,7 @@ private async Task<bool> TryGetSyntaxAndSemanticDiagnosticsAsync(StateSet stateS
{
// unfortunately, we need to special case compiler diagnostic analyzer so that
// we can do span based analysis even though we implemented it as semantic model analysis
if (stateSet.Analyzer == _compilerAnalyzer)
if (stateSet.Analyzer.IsCompilerAnalyzer())
{
return await TryGetSyntaxAndSemanticCompilerDiagnosticsAsync(stateSet, list, cancellationToken).ConfigureAwait(false);
}
......@@ -217,7 +215,7 @@ private async Task<IEnumerable<DiagnosticData>> GetCompilerSemanticDiagnosticsAs
private Task<IEnumerable<DiagnosticData>> GetSyntaxDiagnosticsAsync(DiagnosticAnalyzer analyzer, CancellationToken cancellationToken)
{
return AnalyzerHelper.ComputeDiagnosticsAsync(analyzer, _document, AnalysisKind.Syntax, _owner.DiagnosticAnalyzerInfoCache, _compilation, _range, cancellationToken);
return AnalyzerHelper.ComputeDiagnosticsAsync(analyzer, _document, AnalysisKind.Syntax, _compilation, _range, cancellationToken);
}
private Task<IEnumerable<DiagnosticData>> GetSemanticDiagnosticsAsync(DiagnosticAnalyzer analyzer, CancellationToken cancellationToken)
......@@ -225,7 +223,7 @@ private Task<IEnumerable<DiagnosticData>> GetSemanticDiagnosticsAsync(Diagnostic
var supportsSemanticInSpan = analyzer.SupportsSpanBasedSemanticDiagnosticAnalysis();
var analysisSpan = supportsSemanticInSpan ? (TextSpan?)_range : null;
return AnalyzerHelper.ComputeDiagnosticsAsync(analyzer, _document, AnalysisKind.Semantic, _owner.DiagnosticAnalyzerInfoCache, _compilation, analysisSpan, cancellationToken);
return AnalyzerHelper.ComputeDiagnosticsAsync(analyzer, _document, AnalysisKind.Semantic, _compilation, analysisSpan, cancellationToken);
}
private async Task<ImmutableArray<DiagnosticData>> GetProjectDiagnosticsAsync(DiagnosticAnalyzer analyzer, CancellationToken cancellationToken)
......@@ -325,7 +323,7 @@ private static TextSpan AdjustSpan(Document document, SyntaxNode root, TextSpan
List<DiagnosticData> list,
CancellationToken cancellationToken)
{
if (!_owner.DiagnosticAnalyzerInfoCache.SupportAnalysisKind(stateSet.Analyzer, stateSet.Language, kind))
if (!stateSet.Analyzer.SupportAnalysisKind(kind))
{
return true;
}
......
......@@ -304,8 +304,7 @@ private IEnumerable<StateSet> GetStateSetsForFullSolutionAnalysis(IEnumerable<St
private bool IsCandidateForFullSolutionAnalysis(DiagnosticAnalyzer analyzer, Project project)
{
// PERF: Don't query descriptors for compiler analyzer or file content load analyzer, always execute them.
if (DiagnosticAnalyzerInfoCache.IsCompilerDiagnosticAnalyzer(project.Language, analyzer) ||
analyzer == FileContentLoadAnalyzer.Instance)
if (analyzer == FileContentLoadAnalyzer.Instance || analyzer.IsCompilerAnalyzer())
{
return true;
}
......
......@@ -9,24 +9,16 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Runtime.CompilerServices;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics.Log;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Diagnostics
{
/// <summary>
/// Provides and caches information about diagnostic analyzers such as <see cref="AnalyzerReference"/>,
/// <see cref="DiagnosticAnalyzer"/> instance, <see cref="DiagnosticDescriptor"/>s.
/// Thread-safe.
/// </summary>
internal sealed partial class DiagnosticAnalyzerInfoCache
internal sealed class HostDiagnosticAnalyzers
{
/// <summary>
/// Key is analyzer reference identity <see cref="GetAnalyzerReferenceIdentity(AnalyzerReference)"/>.
/// Key is <see cref="AnalyzerReference.Id"/>.
///
/// We use the key to de-duplicate analyzer references if they are referenced from multiple places.
/// </summary>
......@@ -41,7 +33,7 @@ internal sealed partial class DiagnosticAnalyzerInfoCache
private readonly ConcurrentDictionary<string, ImmutableDictionary<object, ImmutableArray<DiagnosticAnalyzer>>> _hostDiagnosticAnalyzersPerLanguageMap;
/// <summary>
/// Key is analyzer reference identity <see cref="GetAnalyzerReferenceIdentity(AnalyzerReference)"/>.
/// Key is <see cref="AnalyzerReference.Id"/>.
///
/// Value is set of <see cref="DiagnosticAnalyzer"/> that belong to the <see cref="AnalyzerReference"/>.
///
......@@ -54,136 +46,31 @@ internal sealed partial class DiagnosticAnalyzerInfoCache
/// </summary>
private ImmutableDictionary<string, DiagnosticAnalyzer> _compilerDiagnosticAnalyzerMap;
/// <summary>
/// Set of diagnostic ids supported by a given compiler diagnostic analyzer.
/// Allows us to quickly determine whether a diagnostic is a compiler diagnostic.
/// </summary>
private ImmutableDictionary<DiagnosticAnalyzer, HashSet<string>> _compilerDiagnosticAnalyzerDescriptorMap;
/// <summary>
/// Supported descriptors of each <see cref="DiagnosticAnalyzer"/>.
/// </summary>
/// <remarks>
/// Holds on <see cref="DiagnosticAnalyzer"/> instances weakly so that we don't keep analyzers coming from package references alive.
/// They need to be released when the project stops referencing the analyzer.
///
/// The purpose of this map is to avoid multiple calls to <see cref="DiagnosticAnalyzer.SupportedDiagnostics"/> that might return different values
/// (they should not but we need a guarantee to function correctly).
/// </remarks>
private readonly ConditionalWeakTable<DiagnosticAnalyzer, DiagnosticDescriptorsInfo> _descriptorsInfo;
private sealed class DiagnosticDescriptorsInfo
{
public readonly ImmutableArray<DiagnosticDescriptor> SupportedDescriptors;
public readonly bool TelemetryAllowed;
public DiagnosticDescriptorsInfo(ImmutableArray<DiagnosticDescriptor> supportedDescriptors, bool telemetryAllowed)
{
SupportedDescriptors = supportedDescriptors;
TelemetryAllowed = telemetryAllowed;
}
}
public DiagnosticAnalyzerInfoCache(Lazy<ImmutableArray<HostDiagnosticAnalyzerPackage>> hostAnalyzerPackages, IAnalyzerAssemblyLoader? hostAnalyzerAssemblyLoader, AbstractHostDiagnosticUpdateSource? hostDiagnosticUpdateSource, PrimaryWorkspace primaryWorkspace)
public HostDiagnosticAnalyzers(Lazy<ImmutableArray<HostDiagnosticAnalyzerPackage>> hostAnalyzerPackages, IAnalyzerAssemblyLoader? hostAnalyzerAssemblyLoader, AbstractHostDiagnosticUpdateSource? hostDiagnosticUpdateSource, PrimaryWorkspace primaryWorkspace)
: this(new Lazy<ImmutableArray<AnalyzerReference>>(() => CreateAnalyzerReferencesFromPackages(hostAnalyzerPackages.Value, new HostAnalyzerReferenceDiagnosticReporter(hostDiagnosticUpdateSource, primaryWorkspace), hostAnalyzerAssemblyLoader), isThreadSafe: true))
{
}
internal DiagnosticAnalyzerInfoCache(ImmutableArray<AnalyzerReference> hostAnalyzerReferences)
internal HostDiagnosticAnalyzers(ImmutableArray<AnalyzerReference> hostAnalyzerReferences)
: this(new Lazy<ImmutableArray<AnalyzerReference>>(() => hostAnalyzerReferences))
{
}
private DiagnosticAnalyzerInfoCache(Lazy<ImmutableArray<AnalyzerReference>> hostAnalyzerReferences)
private HostDiagnosticAnalyzers(Lazy<ImmutableArray<AnalyzerReference>> hostAnalyzerReferences)
{
_hostAnalyzerReferencesMap = new Lazy<ImmutableDictionary<object, AnalyzerReference>>(() => hostAnalyzerReferences.Value.IsDefaultOrEmpty ? ImmutableDictionary<object, AnalyzerReference>.Empty : CreateAnalyzerReferencesMap(hostAnalyzerReferences.Value), isThreadSafe: true);
_hostDiagnosticAnalyzersPerLanguageMap = new ConcurrentDictionary<string, ImmutableDictionary<object, ImmutableArray<DiagnosticAnalyzer>>>(concurrencyLevel: 2, capacity: 2);
_lazyHostDiagnosticAnalyzersPerReferenceMap = new Lazy<ImmutableDictionary<object, ImmutableArray<DiagnosticAnalyzer>>>(() => CreateDiagnosticAnalyzersPerReferenceMap(_hostAnalyzerReferencesMap.Value), isThreadSafe: true);
_compilerDiagnosticAnalyzerMap = ImmutableDictionary<string, DiagnosticAnalyzer>.Empty;
_compilerDiagnosticAnalyzerDescriptorMap = ImmutableDictionary<DiagnosticAnalyzer, HashSet<string>>.Empty;
_descriptorsInfo = new ConditionalWeakTable<DiagnosticAnalyzer, DiagnosticDescriptorsInfo>();
}
/// <summary>
/// It returns a string that can be used as a way to de-duplicate <see cref="AnalyzerReference"/>s.
/// </summary>
public object GetAnalyzerReferenceIdentity(AnalyzerReference reference)
=> reference.Id;
/// <summary>
/// It returns a map with <see cref="AnalyzerReference.Id"/> as key and <see cref="AnalyzerReference"/> as value
/// </summary>
public ImmutableDictionary<object, AnalyzerReference> GetHostAnalyzerReferencesMap()
=> _hostAnalyzerReferencesMap.Value;
/// <summary>
/// Returns <see cref="DiagnosticAnalyzer.SupportedDiagnostics"/> of given <paramref name="analyzer"/>.
/// </summary>
public ImmutableArray<DiagnosticDescriptor> GetDiagnosticDescriptors(DiagnosticAnalyzer analyzer)
=> GetOrCreateDescriptorsInfo(analyzer).SupportedDescriptors;
/// <summary>
/// Determine whether collection of telemetry is allowed for given <paramref name="analyzer"/>.
/// </summary>
public bool IsTelemetryCollectionAllowed(DiagnosticAnalyzer analyzer)
=> GetOrCreateDescriptorsInfo(analyzer).TelemetryAllowed;
private DiagnosticDescriptorsInfo GetOrCreateDescriptorsInfo(DiagnosticAnalyzer analyzer)
=> _descriptorsInfo.GetValue(analyzer, CalculateDescriptorsInfo);
private DiagnosticDescriptorsInfo CalculateDescriptorsInfo(DiagnosticAnalyzer analyzer)
{
ImmutableArray<DiagnosticDescriptor> descriptors;
try
{
// SupportedDiagnostics is user code and can throw an exception.
descriptors = analyzer.SupportedDiagnostics.NullToEmpty();
}
catch
{
// No need to report the exception to the user.
// Eventually, when the analyzer runs the compiler analyzer driver will report a diagnostic.
descriptors = ImmutableArray<DiagnosticDescriptor>.Empty;
}
bool telemetryAllowed = IsTelemetryCollectionAllowed(analyzer, descriptors);
return new DiagnosticDescriptorsInfo(descriptors, telemetryAllowed);
}
private static bool IsTelemetryCollectionAllowed(DiagnosticAnalyzer analyzer, ImmutableArray<DiagnosticDescriptor> descriptors)
=> analyzer.IsCompilerAnalyzer() ||
analyzer is IBuiltInAnalyzer ||
descriptors.Length > 0 && descriptors[0].CustomTags.Any(t => t == WellKnownDiagnosticTags.Telemetry);
/// <summary>
/// Return true if the given <paramref name="analyzer"/> is suppressed for the given project.
/// NOTE: This API is intended to be used only for performance optimization.
/// </summary>
public bool IsAnalyzerSuppressed(DiagnosticAnalyzer analyzer, Project project)
{
var options = project.CompilationOptions;
if (options == null || analyzer == FileContentLoadAnalyzer.Instance || IsCompilerDiagnosticAnalyzer(project.Language, analyzer))
{
return false;
}
// If user has disabled analyzer execution for this project, we only want to execute required analyzers
// that report diagnostics with category "Compiler".
if (!project.State.RunAnalyzers &&
GetDiagnosticDescriptors(analyzer).All(d => d.Category != DiagnosticCategory.Compiler))
{
return true;
}
// NOTE: Previously we used to return "CompilationWithAnalyzers.IsDiagnosticAnalyzerSuppressed(options)"
// on this code path, which returns true if analyzer is suppressed through compilation options.
// However, this check is no longer correct as analyzers can be enabled/disabled for individual
// documents through .editorconfig files. So we pessimistically assume analyzer is not suppressed
// and let the core analyzer driver in the compiler layer handle skipping redundant analysis callbacks.
return false;
}
/// <summary>
/// Get <see cref="AnalyzerReference"/> identity and <see cref="DiagnosticAnalyzer"/>s map for given <paramref name="language"/>
/// </summary>
......@@ -192,16 +79,16 @@ public bool IsAnalyzerSuppressed(DiagnosticAnalyzer analyzer, Project project)
return _hostDiagnosticAnalyzersPerLanguageMap.GetOrAdd(language, CreateHostDiagnosticAnalyzersAndBuildMap);
}
public ImmutableDictionary<string, ImmutableArray<DiagnosticDescriptor>> GetDiagnosticDescriptorsPerReference()
public ImmutableDictionary<string, ImmutableArray<DiagnosticDescriptor>> GetDiagnosticDescriptorsPerReference(DiagnosticAnalyzerInfoCache infoCache)
{
return ConvertReferenceIdentityToName(
CreateDiagnosticDescriptorsPerReference(_lazyHostDiagnosticAnalyzersPerReferenceMap.Value),
CreateDiagnosticDescriptorsPerReference(infoCache, _lazyHostDiagnosticAnalyzersPerReferenceMap.Value),
_hostAnalyzerReferencesMap.Value);
}
public ImmutableDictionary<string, ImmutableArray<DiagnosticDescriptor>> GetDiagnosticDescriptorsPerReference(Project project)
public ImmutableDictionary<string, ImmutableArray<DiagnosticDescriptor>> GetDiagnosticDescriptorsPerReference(DiagnosticAnalyzerInfoCache infoCache, Project project)
{
var descriptorPerReference = CreateDiagnosticDescriptorsPerReference(CreateDiagnosticAnalyzersPerReference(project));
var descriptorPerReference = CreateDiagnosticDescriptorsPerReference(infoCache, CreateDiagnosticAnalyzersPerReference(project));
var map = _hostAnalyzerReferencesMap.Value.AddRange(CreateProjectAnalyzerReferencesMap(project));
return ConvertReferenceIdentityToName(descriptorPerReference, map);
}
......@@ -255,22 +142,6 @@ public bool IsAnalyzerSuppressed(DiagnosticAnalyzer analyzer, Project project)
return CreateDiagnosticAnalyzersPerReferenceMap(CreateProjectAnalyzerReferencesMap(project), project.Language);
}
/// <summary>
/// Check whether given <see cref="DiagnosticData"/> belong to compiler diagnostic analyzer
/// </summary>
public bool IsCompilerDiagnostic(string language, DiagnosticData diagnostic)
{
_ = GetOrCreateHostDiagnosticAnalyzersPerReference(language);
if (_compilerDiagnosticAnalyzerMap.TryGetValue(language, out var compilerAnalyzer) &&
_compilerDiagnosticAnalyzerDescriptorMap.TryGetValue(compilerAnalyzer, out var idMap) &&
idMap.Contains(diagnostic.Id))
{
return true;
}
return false;
}
/// <summary>
/// Return compiler <see cref="DiagnosticAnalyzer"/> for the given language.
/// </summary>
......@@ -285,37 +156,13 @@ public bool IsCompilerDiagnostic(string language, DiagnosticData diagnostic)
return null;
}
/// <summary>
/// Check whether given <see cref="DiagnosticAnalyzer"/> is compiler analyzer for the language or not.
/// </summary>
public bool IsCompilerDiagnosticAnalyzer(string language, DiagnosticAnalyzer analyzer)
{
_ = GetOrCreateHostDiagnosticAnalyzersPerReference(language);
return _compilerDiagnosticAnalyzerMap.TryGetValue(language, out var compilerAnalyzer) && compilerAnalyzer == analyzer;
}
public bool SupportAnalysisKind(DiagnosticAnalyzer analyzer, string language, AnalysisKind kind)
{
// compiler diagnostic analyzer always supports all kinds:
if (IsCompilerDiagnosticAnalyzer(language, analyzer))
{
return true;
}
return kind switch
{
AnalysisKind.Syntax => analyzer.SupportsSyntaxDiagnosticAnalysis(),
AnalysisKind.Semantic => analyzer.SupportsSemanticDiagnosticAnalysis(),
_ => throw ExceptionUtilities.UnexpectedValue(kind)
};
}
private ImmutableDictionary<object, AnalyzerReference> CreateProjectAnalyzerReferencesMap(Project project)
{
return CreateAnalyzerReferencesMap(project.AnalyzerReferences.Where(reference => !_hostAnalyzerReferencesMap.Value.ContainsKey(reference.Id)));
}
private ImmutableDictionary<object, ImmutableArray<DiagnosticDescriptor>> CreateDiagnosticDescriptorsPerReference(
DiagnosticAnalyzerInfoCache infoCache,
ImmutableDictionary<object, ImmutableArray<DiagnosticAnalyzer>> analyzersMap)
{
var builder = ImmutableDictionary.CreateBuilder<object, ImmutableArray<DiagnosticDescriptor>>();
......@@ -325,7 +172,7 @@ public bool SupportAnalysisKind(DiagnosticAnalyzer analyzer, string language, An
foreach (var analyzer in analyzers)
{
// given map should be in good shape. no duplication. no null and etc
descriptors.AddRange(GetDiagnosticDescriptors(analyzer));
descriptors.AddRange(infoCache.GetDiagnosticDescriptors(analyzer));
}
// there can't be duplication since _hostAnalyzerReferenceMap is already de-duplicated.
......@@ -368,7 +215,6 @@ private void UpdateCompilerAnalyzerMapIfNeeded(string language, ImmutableArray<D
{
if (analyzer.IsCompilerAnalyzer())
{
ImmutableInterlocked.GetOrAdd(ref _compilerDiagnosticAnalyzerDescriptorMap, analyzer, a => new HashSet<string>(GetDiagnosticDescriptors(a).Select(d => d.Id)));
ImmutableInterlocked.GetOrAdd(ref _compilerDiagnosticAnalyzerMap, language, analyzer);
return;
}
......
......@@ -20,6 +20,11 @@ internal interface IDiagnosticAnalyzerService
/// </summary>
DiagnosticAnalyzerInfoCache AnalyzerInfoCache { get; }
/// <summary>
/// Host analyzer collection.
/// </summary>
HostDiagnosticAnalyzers HostAnalyzers { get; }
/// <summary>
/// Re-analyze given projects and documents
/// </summary>
......
......@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using Microsoft.CodeAnalysis.Diagnostics;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Shared.Extensions
{
......@@ -20,6 +21,22 @@ public static DiagnosticAnalyzerCategory GetDiagnosticAnalyzerCategory(this Diag
_ => DiagnosticAnalyzerCategory.SyntaxTreeWithoutSemanticsAnalysis | DiagnosticAnalyzerCategory.SemanticDocumentAnalysis | DiagnosticAnalyzerCategory.ProjectAnalysis
};
public static bool SupportAnalysisKind(this DiagnosticAnalyzer analyzer, AnalysisKind kind)
{
// compiler diagnostic analyzer always supports all kinds:
if (analyzer.IsCompilerAnalyzer())
{
return true;
}
return kind switch
{
AnalysisKind.Syntax => analyzer.SupportsSyntaxDiagnosticAnalysis(),
AnalysisKind.Semantic => analyzer.SupportsSemanticDiagnosticAnalysis(),
_ => throw ExceptionUtilities.UnexpectedValue(kind)
};
}
public static bool SupportsSyntaxDiagnosticAnalysis(this DiagnosticAnalyzer analyzer)
{
var category = analyzer.GetDiagnosticAnalyzerCategory();
......
......@@ -91,9 +91,12 @@ public void Initialize(IServiceProvider serviceProvider)
public IReadOnlyDictionary<string, IEnumerable<DiagnosticDescriptor>> GetAllDiagnosticDescriptors(IVsHierarchy? hierarchy)
{
var infoCache = _diagnosticService.AnalyzerInfoCache;
var hostAnalyzers = _diagnosticService.HostAnalyzers;
if (hierarchy == null)
{
return Transform(_diagnosticService.AnalyzerInfoCache.GetDiagnosticDescriptorsPerReference());
return Transform(hostAnalyzers.GetDiagnosticDescriptorsPerReference(infoCache));
}
// Analyzers are only supported for C# and VB currently.
......@@ -106,11 +109,11 @@ public void Initialize(IServiceProvider serviceProvider)
var project = projectsWithHierarchy.FirstOrDefault();
if (project == null)
{
return Transform(_diagnosticService.AnalyzerInfoCache.GetDiagnosticDescriptorsPerReference());
return Transform(hostAnalyzers.GetDiagnosticDescriptorsPerReference(infoCache));
}
else
{
return Transform(_diagnosticService.AnalyzerInfoCache.GetDiagnosticDescriptorsPerReference(project));
return Transform(hostAnalyzers.GetDiagnosticDescriptorsPerReference(infoCache, project));
}
}
else
......@@ -120,7 +123,7 @@ public void Initialize(IServiceProvider serviceProvider)
var descriptorsMap = ImmutableDictionary.CreateBuilder<string, IEnumerable<DiagnosticDescriptor>>();
foreach (var project in projectsWithHierarchy)
{
var descriptorsPerReference = _diagnosticService.AnalyzerInfoCache.GetDiagnosticDescriptorsPerReference(project);
var descriptorsPerReference = hostAnalyzers.GetDiagnosticDescriptorsPerReference(infoCache, project);
foreach (var (displayName, descriptors) in descriptorsPerReference)
{
if (descriptorsMap.TryGetValue(displayName, out var existingDescriptors))
......
......@@ -32,7 +32,7 @@ public sealed class RemoteHostClientService : ForegroundThreadAffinitizedObject,
private readonly IAsynchronousOperationListener _listener;
private readonly Workspace _workspace;
private readonly DiagnosticAnalyzerInfoCache _analyzerInfoCache;
private readonly HostDiagnosticAnalyzers _hostAnalyzers;
private readonly object _gate;
......@@ -44,14 +44,14 @@ public sealed class RemoteHostClientService : ForegroundThreadAffinitizedObject,
IThreadingContext threadingContext,
IAsynchronousOperationListener listener,
Workspace workspace,
DiagnosticAnalyzerInfoCache analyzerInfoCache)
HostDiagnosticAnalyzers hostAnalyzers)
: base(threadingContext)
{
_gate = new object();
_listener = listener;
_workspace = workspace;
_analyzerInfoCache = analyzerInfoCache;
_hostAnalyzers = hostAnalyzers;
}
public Workspace Workspace => _workspace;
......@@ -239,7 +239,7 @@ private Checksum[] AddGlobalAssets(CancellationToken cancellationToken)
var snapshotService = _workspace.Services.GetRequiredService<IRemotableDataService>();
var assetBuilder = new CustomAssetBuilder(_workspace);
foreach (var (_, reference) in _analyzerInfoCache.GetHostAnalyzerReferencesMap())
foreach (var (_, reference) in _hostAnalyzers.GetHostAnalyzerReferencesMap())
{
var asset = assetBuilder.Build(reference, cancellationToken);
......@@ -257,7 +257,7 @@ private void RemoveGlobalAssets()
{
var snapshotService = _workspace.Services.GetRequiredService<IRemotableDataService>();
foreach (var (_, reference) in _analyzerInfoCache.GetHostAnalyzerReferencesMap())
foreach (var (_, reference) in _hostAnalyzers.GetHostAnalyzerReferencesMap())
{
snapshotService.RemoveGlobalAsset(reference, CancellationToken.None);
}
......
......@@ -32,7 +32,7 @@ internal partial class RemoteHostClientServiceFactory : IWorkspaceServiceFactory
public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
{
return new RemoteHostClientService(_threadingContext, _listener, workspaceServices.Workspace, _analyzerService.AnalyzerInfoCache);
return new RemoteHostClientService(_threadingContext, _listener, workspaceServices.Workspace, _analyzerService.HostAnalyzers);
}
}
}
......@@ -425,7 +425,7 @@ public bool IsSupportedDiagnosticId(ProjectId projectId, string id)
// set ids set
var builder = ImmutableHashSet.CreateBuilder<string>();
var descriptorMap = _owner._diagnosticService.AnalyzerInfoCache.GetDiagnosticDescriptorsPerReference(project);
var descriptorMap = _owner._diagnosticService.HostAnalyzers.GetDiagnosticDescriptorsPerReference(_owner._diagnosticService.AnalyzerInfoCache, project);
builder.UnionWith(descriptorMap.Values.SelectMany(v => v.Select(d => d.Id)));
var set = builder.ToImmutable();
......@@ -513,8 +513,8 @@ private bool IsLive(Project project, Compilation compilation, DiagnosticData dia
// REVIEW: current design is that we special case compiler analyzer case and we accept only document level
// diagnostic as live. otherwise, we let them be build errors. we changed compiler analyzer accordingly as well
// so that it doesn't report project level diagnostic as live errors.
if (_owner._diagnosticService.AnalyzerInfoCache.IsCompilerDiagnostic(project.Language, diagnosticData) &&
!IsDocumentLevelDiagnostic(diagnosticData))
if (!IsDocumentLevelDiagnostic(diagnosticData) &&
diagnosticData.CustomTags.Contains(WellKnownDiagnosticTags.Compiler))
{
// compiler error but project level error
return false;
......@@ -580,7 +580,7 @@ private bool SupportedLiveDiagnosticId(Project project, Compilation compilation,
var diagnosticService = _owner._diagnosticService;
var infoCache = diagnosticService.AnalyzerInfoCache;
foreach (var analyzersPerReference in infoCache.CreateDiagnosticAnalyzersPerReference(project))
foreach (var analyzersPerReference in diagnosticService.HostAnalyzers.CreateDiagnosticAnalyzersPerReference(project))
{
foreach (var analyzer in analyzersPerReference.Value)
{
......
......@@ -265,8 +265,10 @@ void Method()
private static DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner CreateAnalyzerRunner(Workspace workspace)
{
var infoCache = new DiagnosticAnalyzerInfoCache(ImmutableArray<AnalyzerReference>.Empty);
return new DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner(AsynchronousOperationListenerProvider.NullListener, infoCache, hostDiagnosticUpdateSource: new MyUpdateSource(workspace));
return new DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner(
AsynchronousOperationListenerProvider.NullListener,
new DiagnosticAnalyzerInfoCache(),
hostDiagnosticUpdateSource: new MyUpdateSource(workspace));
}
private static async Task<DiagnosticAnalysisResult> AnalyzeAsync(TestWorkspace workspace, ProjectId projectId, Type analyzerType, CancellationToken cancellationToken = default)
......
......@@ -816,10 +816,11 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics
analyzersMap As ImmutableDictionary(Of String, ImmutableArray(Of DiagnosticAnalyzer)),
registrationService As IDiagnosticUpdateSourceRegistrationService,
listener As IAsynchronousOperationListener)
MyBase.New(New DiagnosticAnalyzerInfoCache(ImmutableArray.Create(Of AnalyzerReference)(New TestAnalyzerReferenceByLanguage(analyzersMap))),
hostDiagnosticUpdateSource:=Nothing,
registrationService:=registrationService,
listener:=listener)
MyBase.New(New DiagnosticAnalyzerInfoCache(),
New HostDiagnosticAnalyzers(ImmutableArray.Create(Of AnalyzerReference)(New TestAnalyzerReferenceByLanguage(analyzersMap))),
hostDiagnosticUpdateSource:=Nothing,
registrationService:=registrationService,
listener:=listener)
End Sub
End Class
......
......@@ -435,11 +435,13 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics
Private ReadOnly _data As ImmutableArray(Of DiagnosticData)
Private ReadOnly _analyzerInfoCache As DiagnosticAnalyzerInfoCache
Private ReadOnly _hostAnalyzers As HostDiagnosticAnalyzers
Public Sub New(Optional data As ImmutableArray(Of DiagnosticData) = Nothing,
Optional analyzers As ImmutableArray(Of AnalyzerReference) = Nothing)
_data = data.NullToEmpty
_analyzerInfoCache = New DiagnosticAnalyzerInfoCache(analyzers.NullToEmpty)
_analyzerInfoCache = New DiagnosticAnalyzerInfoCache()
_hostAnalyzers = New HostDiagnosticAnalyzers(analyzers.NullToEmpty)
End Sub
Public ReadOnly Property SupportGetDiagnostics As Boolean Implements IDiagnosticUpdateSource.SupportGetDiagnostics
......@@ -454,6 +456,12 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics
End Get
End Property
Public ReadOnly Property HostAnalyzers As HostDiagnosticAnalyzers Implements IDiagnosticAnalyzerService.HostAnalyzers
Get
Return _hostAnalyzers
End Get
End Property
Public Event DiagnosticsUpdated As EventHandler(Of DiagnosticsUpdatedArgs) Implements IDiagnosticUpdateSource.DiagnosticsUpdated
Public Event DiagnosticsCleared As EventHandler Implements IDiagnosticUpdateSource.DiagnosticsCleared
......
......@@ -54,11 +54,11 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics
Dim analyzerPackage = New HostDiagnosticAnalyzerPackage("MyPackage", ImmutableArray.Create(analyzerFile.Path))
Dim analyzerPackages = ImmutableArray.Create(analyzerPackage)
Dim analyzerLoader = VisualStudioDiagnosticAnalyzerProvider.GetLoader()
Dim hostAnalyzerManager = New DiagnosticAnalyzerInfoCache(New Lazy(Of ImmutableArray(Of HostDiagnosticAnalyzerPackage))(
Function() analyzerPackages), analyzerLoader,
hostDiagnosticUpdateSource:=Nothing,
primaryWorkspace:=Nothing)
Dim analyzerReferenceMap = hostAnalyzerManager.GetOrCreateHostDiagnosticAnalyzersPerReference(LanguageNames.CSharp)
Dim hostAnalyzers = New HostDiagnosticAnalyzers(New Lazy(Of ImmutableArray(Of HostDiagnosticAnalyzerPackage))(Function() analyzerPackages),
analyzerLoader,
hostDiagnosticUpdateSource:=Nothing,
primaryWorkspace:=Nothing)
Dim analyzerReferenceMap = hostAnalyzers.GetOrCreateHostDiagnosticAnalyzersPerReference(LanguageNames.CSharp)
Assert.Single(analyzerReferenceMap)
Dim analyzers = analyzerReferenceMap.Single().Value
Assert.Single(analyzers)
......
......@@ -17,9 +17,7 @@ internal partial class CodeAnalysisService : ServiceBase
public CodeAnalysisService(Stream stream, IServiceProvider serviceProvider)
: base(serviceProvider, stream)
{
// TODO: currently we only use the cache for information that doesn't involve references or packages.
// Once we move all analysis OOP we will create the full cache.
_analyzerInfoCache = new DiagnosticAnalyzerInfoCache(ImmutableArray<AnalyzerReference>.Empty);
_analyzerInfoCache = new DiagnosticAnalyzerInfoCache();
StartService();
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册