提交 3d0c9cd1 编写于 作者: M Manish Vasani

Address feedback

上级 dd1d514c
......@@ -147,7 +147,7 @@ private async Task AnalyzeForKindAsync(TextDocument document, AnalysisKind kind,
///
/// The intended audience for this API is for ones that pefer simplicity over performance such as document that belong to misc project.
/// this doesn't cache nor use cache for anything. it will re-caculate new diagnostics every time for the given document.
/// it will not persist any data on disk nor use OOP to calcuate the data.
/// it will not persist any data on disk nor use OOP to calculate the data.
///
/// This should never be used when performance is a big concern. for such context, use much complex API from IDiagnosticAnalyzerService
/// that provide all kinds of knobs/cache/persistency/OOP to get better perf over simplicity.
......
......@@ -121,7 +121,7 @@ public async Task<IEnumerable<DiagnosticData>> ComputeDiagnosticsAsync(Diagnosti
}
}
if (document == null && !(textDocument is AdditionalDocument))
if (document == null && textDocument is not AdditionalDocument)
{
// We currently support document analysis only for source documents and additional documents.
return SpecializedCollections.EmptyEnumerable<DiagnosticData>();
......@@ -182,7 +182,7 @@ private async Task<ImmutableArray<DiagnosticData>> GetSyntaxDiagnosticsAsync(Dia
if (isCompilerAnalyzer)
{
if (!(AnalysisScope.TextDocument is Document))
if (AnalysisScope.TextDocument is not Document)
{
return ImmutableArray<DiagnosticData>.Empty;
}
......
......@@ -74,19 +74,15 @@ internal class InProcOrRemoteHostAnalyzerRunner
{
Contract.ThrowIfFalse(!compilationWithAnalyzers.Analyzers.IsEmpty);
var workspace = project.Solution.Workspace;
if (workspace.Services.GetService<IRemoteHostClientProvider>() is { } service &&
await service.TryGetRemoteHostClientAsync(cancellationToken).ConfigureAwait(false) is { } remoteHostClient)
var remoteHostClient = await RemoteHostClient.TryGetClientAsync(project, cancellationToken).ConfigureAwait(false);
if (remoteHostClient != null)
{
return await AnalyzeOutOfProcAsync(documentAnalysisScope, project, compilationWithAnalyzers, remoteHostClient,
forceExecuteAllAnalyzers, logPerformanceInfo, getTelemetryInfo, cancellationToken).ConfigureAwait(false);
}
// We need to execute InProc due to one of the following reasons:
// 1. The host doesn't support RemoteHostService (such as under unit test) OR
// 2. Remote host is not running (this can happen if remote host is disabled).
return await AnalyzeInProcAsync(documentAnalysisScope, project, compilationWithAnalyzers,
client: null, logPerformanceInfo, getTelemetryInfo, cancellationToken).ConfigureAwait(false);
client: null, logPerformanceInfo, getTelemetryInfo, cancellationToken).ConfigureAwait(false);
}
}
......@@ -115,9 +111,10 @@ await service.TryGetRemoteHostClientAsync(cancellationToken).ConfigureAwait(fals
var skippedAnalyzersInfo = project.GetSkippedAnalyzersInfo(AnalyzerInfoCache);
// get compiler result builder map
var builderMap = analysisResult.ToResultBuilderMap(
var builderMap = await analysisResult.ToResultBuilderMapAsync(
additionalPragmaSuppressionDiagnostics, documentAnalysisScope, project, version,
compilationWithAnalyzers.Compilation, analyzers, skippedAnalyzersInfo, compilationWithAnalyzers.AnalysisOptions.ReportSuppressedDiagnostics, cancellationToken);
compilationWithAnalyzers.Compilation, analyzers, skippedAnalyzersInfo,
compilationWithAnalyzers.AnalysisOptions.ReportSuppressedDiagnostics, cancellationToken).ConfigureAwait(false);
var result = builderMap.ToImmutableDictionary(kv => kv.Key, kv => DiagnosticAnalysisResult.CreateFromBuilder(kv.Value));
var telemetry = getTelemetryInfo
......@@ -140,16 +137,14 @@ await service.TryGetRemoteHostClientAsync(cancellationToken).ConfigureAwait(fals
try
{
// +1 for project itself
var count = documentAnalysisScope != null ? 1 : project.DocumentIds.Count + 1;
await client.RunRemoteAsync(
WellKnownServiceHubService.CodeAnalysis,
nameof(IRemoteDiagnosticAnalyzerService.ReportAnalyzerPerformance),
solution: null,
new object[]
{
analysisResult.AnalyzerTelemetryInfo.ToAnalyzerPerformanceInfo(AnalyzerInfoCache),
// +1 for project itself
documentAnalysisScope != null ? 1 : project.DocumentIds.Count + 1
},
new object[] { analysisResult.AnalyzerTelemetryInfo.ToAnalyzerPerformanceInfo(AnalyzerInfoCache), count },
callbackTarget: null,
cancellationToken).ConfigureAwait(false);
}
......
......@@ -40,7 +40,6 @@ internal partial class DiagnosticIncrementalAnalyzer : IIncrementalAnalyzer2
internal DiagnosticAnalyzerService AnalyzerService { get; }
internal Workspace Workspace { get; }
internal IPersistentStorageService PersistentStorageService { get; }
internal DiagnosticAnalyzerInfoCache DiagnosticAnalyzerInfoCache => _diagnosticAnalyzerRunner.AnalyzerInfoCache;
public DiagnosticIncrementalAnalyzer(
DiagnosticAnalyzerService analyzerService,
......@@ -64,6 +63,8 @@ internal partial class DiagnosticIncrementalAnalyzer : IIncrementalAnalyzer2
_projectCompilationsWithAnalyzers = new ConditionalWeakTable<Project, CompilationWithAnalyzers?>();
}
internal DiagnosticAnalyzerInfoCache DiagnosticAnalyzerInfoCache => _diagnosticAnalyzerRunner.AnalyzerInfoCache;
public bool IsCompilationEndAnalyzer(DiagnosticAnalyzer diagnosticAnalyzer, Project project, Compilation compilation)
=> DiagnosticAnalyzerInfoCache.IsCompilationEndAnalyzer(diagnosticAnalyzer, project, compilation) == true;
......
......@@ -132,7 +132,7 @@ private static string GetAssemblyQualifiedName(Type type)
return type.AssemblyQualifiedName ?? throw ExceptionUtilities.UnexpectedValue(type);
}
public static ImmutableDictionary<DiagnosticAnalyzer, DiagnosticAnalysisResultBuilder> ToResultBuilderMap(
public static async Task<ImmutableDictionary<DiagnosticAnalyzer, DiagnosticAnalysisResultBuilder>> ToResultBuilderMapAsync(
this AnalysisResult analysisResult,
ImmutableArray<Diagnostic> additionalPragmaSuppressionDiagnostics,
DocumentAnalysisScope? documentAnalysisScope,
......@@ -144,17 +144,17 @@ private static string GetAssemblyQualifiedName(Type type)
bool includeSuppressedDiagnostics,
CancellationToken cancellationToken)
{
SyntaxTree? filterTree = null;
AdditionalText? filterAdditionalFile = null;
SyntaxTree? treeToAnalyze = null;
AdditionalText? additionalFileToAnalyze = null;
if (documentAnalysisScope != null)
{
if (documentAnalysisScope.TextDocument is Document document)
{
filterTree = document.GetSyntaxTreeSynchronously(cancellationToken);
treeToAnalyze = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
}
else
{
filterAdditionalFile = documentAnalysisScope.AdditionalFile;
additionalFileToAnalyze = documentAnalysisScope.AdditionalFile;
}
}
......@@ -175,35 +175,35 @@ private static string GetAssemblyQualifiedName(Type type)
if (documentAnalysisScope != null)
{
RoslynDebug.Assert(filterTree != null || filterAdditionalFile != null);
var filterSpan = documentAnalysisScope.Span;
RoslynDebug.Assert(treeToAnalyze != null || additionalFileToAnalyze != null);
var spanToAnalyze = documentAnalysisScope.Span;
var kind = documentAnalysisScope.Kind;
ImmutableDictionary<DiagnosticAnalyzer, ImmutableArray<Diagnostic>>? diagnosticsByAnalyzerMap;
switch (kind)
{
case AnalysisKind.Syntax:
if (filterTree != null)
if (treeToAnalyze != null)
{
if (analysisResult.SyntaxDiagnostics.TryGetValue(filterTree, out diagnosticsByAnalyzerMap))
if (analysisResult.SyntaxDiagnostics.TryGetValue(treeToAnalyze, out diagnosticsByAnalyzerMap))
{
AddAnalyzerDiagnosticsToResult(analyzer, diagnosticsByAnalyzerMap, ref result, compilation,
filterTree, additionalDocumentId: null, filterSpan, AnalysisKind.Syntax, diagnosticIdsToFilter, includeSuppressedDiagnostics);
treeToAnalyze, additionalDocumentId: null, spanToAnalyze, AnalysisKind.Syntax, diagnosticIdsToFilter, includeSuppressedDiagnostics);
}
}
else if (analysisResult.AdditionalFileDiagnostics.TryGetValue(filterAdditionalFile!, out diagnosticsByAnalyzerMap))
else if (analysisResult.AdditionalFileDiagnostics.TryGetValue(additionalFileToAnalyze!, out diagnosticsByAnalyzerMap))
{
AddAnalyzerDiagnosticsToResult(analyzer, diagnosticsByAnalyzerMap, ref result, compilation,
tree: null, documentAnalysisScope.TextDocument.Id, filterSpan, AnalysisKind.Syntax, diagnosticIdsToFilter, includeSuppressedDiagnostics);
tree: null, documentAnalysisScope.TextDocument.Id, spanToAnalyze, AnalysisKind.Syntax, diagnosticIdsToFilter, includeSuppressedDiagnostics);
}
break;
case AnalysisKind.Semantic:
if (analysisResult.SemanticDiagnostics.TryGetValue(filterTree!, out diagnosticsByAnalyzerMap))
if (analysisResult.SemanticDiagnostics.TryGetValue(treeToAnalyze!, out diagnosticsByAnalyzerMap))
{
AddAnalyzerDiagnosticsToResult(analyzer, diagnosticsByAnalyzerMap, ref result, compilation,
filterTree, additionalDocumentId: null, filterSpan, AnalysisKind.Semantic, diagnosticIdsToFilter, includeSuppressedDiagnostics);
treeToAnalyze, additionalDocumentId: null, spanToAnalyze, AnalysisKind.Semantic, diagnosticIdsToFilter, includeSuppressedDiagnostics);
}
break;
......@@ -244,10 +244,10 @@ private static string GetAssemblyQualifiedName(Type type)
{
if (documentAnalysisScope != null)
{
if (filterTree != null)
if (treeToAnalyze != null)
{
var diagnostics = additionalPragmaSuppressionDiagnostics.WhereAsArray(d => d.Location.SourceTree == filterTree);
AddDiagnosticsToResult(diagnostics, ref result, compilation, filterTree, additionalDocumentId: null,
var diagnostics = additionalPragmaSuppressionDiagnostics.WhereAsArray(d => d.Location.SourceTree == treeToAnalyze);
AddDiagnosticsToResult(diagnostics, ref result, compilation, treeToAnalyze, additionalDocumentId: null,
documentAnalysisScope!.Span, AnalysisKind.Semantic, diagnosticIdsToFilter, includeSuppressedDiagnostics);
}
}
......
......@@ -24,6 +24,19 @@ namespace Microsoft.CodeAnalysis.Remote.Diagnostics
{
internal class DiagnosticComputer
{
/// <summary>
/// Cache of <see cref="CompilationWithAnalyzers"/> and a map from analyzer IDs to <see cref="DiagnosticAnalyzer"/>s
/// for all analyzers for each project.
/// The <see cref="CompilationWithAnalyzers"/> instance is shared between all the following document analyses modes for the project:
/// 1. Span-based analysis for active document (lightbulb)
/// 2. Background analysis for active and open documents.
///
/// NOTE: We do not re-use this cache for project analysis as it leads to significant memory increase in the OOP process,
/// and CWT does not seem to drop entries until ForceGC happens.
/// </summary>
private static readonly ConditionalWeakTable<Project, CompilationWithAnalyzersCacheEntry> s_compilationWithAnalyzersCache
= new ConditionalWeakTable<Project, CompilationWithAnalyzersCacheEntry>();
private readonly TextDocument? _document;
private readonly Project _project;
private readonly TextSpan? _span;
......@@ -55,10 +68,10 @@ internal class DiagnosticComputer
bool getTelemetryInfo,
CancellationToken cancellationToken)
{
var compilationWithAnalyzersData = await CompilationWithAnalyzersData.GetOrCreateAsync(
var (compilationWithAnalyzers, analyzerToIdMap) = await GetOrCreateCompilationWithAnalyzersAsync(
_project, isDocumentAnalysis: _document != null, cancellationToken).ConfigureAwait(false);
var analyzers = GetAnalyzers(compilationWithAnalyzersData.AnalyzerToIdMap, analyzerIds);
var analyzers = GetAnalyzers(analyzerToIdMap, analyzerIds);
if (analyzers.IsEmpty)
{
return DiagnosticAnalysisResultMap<string, DiagnosticAnalysisResultBuilder>.Empty;
......@@ -67,12 +80,13 @@ internal class DiagnosticComputer
var cacheService = _project.Solution.Workspace.Services.GetRequiredService<IProjectCacheService>();
using var cache = cacheService.EnableCaching(_project.Id);
var skippedAnalyzersInfo = _project.GetSkippedAnalyzersInfo(_analyzerInfoCache);
return await AnalyzeAsync(compilationWithAnalyzersData, analyzers, skippedAnalyzersInfo,
return await AnalyzeAsync(compilationWithAnalyzers, analyzerToIdMap, analyzers, skippedAnalyzersInfo,
reportSuppressedDiagnostics, logPerformanceInfo, getTelemetryInfo, cancellationToken).ConfigureAwait(false);
}
private async Task<DiagnosticAnalysisResultMap<string, DiagnosticAnalysisResultBuilder>> AnalyzeAsync(
CompilationWithAnalyzersData compilationWithAnalyzersData,
CompilationWithAnalyzers compilationWithAnalyzers,
BidirectionalMap<string, DiagnosticAnalyzer> analyzerToIdMap,
ImmutableArray<DiagnosticAnalyzer> analyzers,
SkippedHostAnalyzersInfo skippedAnalyzersInfo,
bool reportSuppressedDiagnostics,
......@@ -80,7 +94,6 @@ internal class DiagnosticComputer
bool getTelemetryInfo,
CancellationToken cancellationToken)
{
var compilationWithAnalyzers = compilationWithAnalyzersData.CompilationWithAnalyzers;
var documentAnalysisScope = _document != null
? new DocumentAnalysisScope(_document, _span, analyzers, _analysisKind!.Value)
: null;
......@@ -96,12 +109,11 @@ internal class DiagnosticComputer
_performanceTracker.AddSnapshot(analysisResult.AnalyzerTelemetryInfo.ToAnalyzerPerformanceInfo(_analyzerInfoCache), unitCount);
}
var builderMap = analysisResult.ToResultBuilderMap(
var builderMap = await analysisResult.ToResultBuilderMapAsync(
additionalPragmaSuppressionDiagnostics, documentAnalysisScope,
_project, VersionStamp.Default, compilationWithAnalyzers.Compilation,
analyzers, skippedAnalyzersInfo, reportSuppressedDiagnostics, cancellationToken);
analyzers, skippedAnalyzersInfo, reportSuppressedDiagnostics, cancellationToken).ConfigureAwait(false);
var analyzerToIdMap = compilationWithAnalyzersData.AnalyzerToIdMap;
var result = builderMap.ToImmutableDictionary(kv => GetAnalyzerId(analyzerToIdMap, kv.Key), kv => kv.Value);
var telemetry = getTelemetryInfo
? GetTelemetryInfo(analysisResult, analyzers, analyzerToIdMap)
......@@ -165,108 +177,101 @@ private static ImmutableArray<DiagnosticAnalyzer> GetAnalyzers(BidirectionalMap<
return builder.ToImmutable();
}
private sealed class CompilationWithAnalyzersData
private static async Task<(CompilationWithAnalyzers compilationWithAnalyzers, BidirectionalMap<string, DiagnosticAnalyzer> analyzerToIdMap)> GetOrCreateCompilationWithAnalyzersAsync(
Project project,
bool isDocumentAnalysis,
CancellationToken cancellationToken)
{
/// <summary>
/// Cache of <see cref="CompilationWithAnalyzers"/> and a map from analyzer IDs to <see cref="DiagnosticAnalyzer"/>s
/// for all analyzers for each project.
/// The <see cref="CompilationWithAnalyzers"/> instance is shared between all the following document analyses modes for the project:
/// 1. Span-based analysis for active document (lightbulb)
/// 2. Background analysis for active and open documents.
///
/// NOTE: We do not re-use this cache for project analysis as it leads to significant memory increase in the OOP process,
/// and CWT does not seem to drop entries until ForceGC happens.
/// </summary>
private static readonly ConditionalWeakTable<Project, CompilationWithAnalyzersData> s_cache
= new ConditionalWeakTable<Project, CompilationWithAnalyzersData>();
private CompilationWithAnalyzersData(CompilationWithAnalyzers compilationWithAnalyzers, BidirectionalMap<string, DiagnosticAnalyzer> analyzerToIdMap)
{
CompilationWithAnalyzers = compilationWithAnalyzers;
AnalyzerToIdMap = analyzerToIdMap;
}
public BidirectionalMap<string, DiagnosticAnalyzer> AnalyzerToIdMap { get; }
public CompilationWithAnalyzers CompilationWithAnalyzers { get; }
var cacheEntry = await GetOrCreateCacheEntryAsync().ConfigureAwait(false);
return (cacheEntry.CompilationWithAnalyzers, cacheEntry.AnalyzerToIdMap);
public static async Task<CompilationWithAnalyzersData> GetOrCreateAsync(
Project project,
bool isDocumentAnalysis,
CancellationToken cancellationToken)
async Task<CompilationWithAnalyzersCacheEntry> GetOrCreateCacheEntryAsync()
{
if (!isDocumentAnalysis)
{
// Only use cache for document analysis.
return await CreateAsync(project, cancellationToken).ConfigureAwait(false);
return await CreateCompilationWithAnalyzersCacheEntryAsync(project, cancellationToken).ConfigureAwait(false);
}
if (s_cache.TryGetValue(project, out var data))
if (s_compilationWithAnalyzersCache.TryGetValue(project, out var data))
{
return data;
}
data = await CreateAsync(project, cancellationToken).ConfigureAwait(false);
return s_cache.GetValue(project, _ => data);
data = await CreateCompilationWithAnalyzersCacheEntryAsync(project, cancellationToken).ConfigureAwait(false);
return s_compilationWithAnalyzersCache.GetValue(project, _ => data);
}
}
private static async Task<CompilationWithAnalyzersData> CreateAsync(Project project, CancellationToken cancellationToken)
private static async Task<CompilationWithAnalyzersCacheEntry> CreateCompilationWithAnalyzersCacheEntryAsync(Project project, CancellationToken cancellationToken)
{
// We could consider creating a service so that we don't do this repeatedly if this shows up as perf cost
using var pooledObject = SharedPools.Default<HashSet<object>>().GetPooledObject();
using var pooledMap = SharedPools.Default<Dictionary<string, DiagnosticAnalyzer>>().GetPooledObject();
var referenceSet = pooledObject.Object;
var analyzerMapBuilder = pooledMap.Object;
// This follows what we do in DiagnosticAnalyzerInfoCache.CheckAnalyzerReferenceIdentity
using var _ = ArrayBuilder<DiagnosticAnalyzer>.GetInstance(out var analyzerBuilder);
foreach (var reference in project.Solution.AnalyzerReferences.Concat(project.AnalyzerReferences))
{
// We could consider creating a service so that we don't do this repeatedly if this shows up as perf cost
using var pooledObject = SharedPools.Default<HashSet<object>>().GetPooledObject();
using var pooledMap = SharedPools.Default<Dictionary<string, DiagnosticAnalyzer>>().GetPooledObject();
var referenceSet = pooledObject.Object;
var analyzerMapBuilder = pooledMap.Object;
// This follows what we do in DiagnosticAnalyzerInfoCache.CheckAnalyzerReferenceIdentity
using var _ = ArrayBuilder<DiagnosticAnalyzer>.GetInstance(out var analyzerBuilder);
foreach (var reference in project.Solution.AnalyzerReferences.Concat(project.AnalyzerReferences))
if (!referenceSet.Add(reference.Id))
{
if (!referenceSet.Add(reference.Id))
{
continue;
}
var analyzers = reference.GetAnalyzers(project.Language);
analyzerBuilder.AddRange(analyzers);
analyzerMapBuilder.AppendAnalyzerMap(analyzers);
continue;
}
var compilationWithAnalyzers = await CreateCompilationWithAnalyzerAsync(
project, analyzerBuilder.ToImmutable(), cancellationToken).ConfigureAwait(false);
var analyzerToIdMap = new BidirectionalMap<string, DiagnosticAnalyzer>(analyzerMapBuilder);
var analyzers = reference.GetAnalyzers(project.Language);
analyzerBuilder.AddRange(analyzers);
analyzerMapBuilder.AppendAnalyzerMap(analyzers);
}
return new CompilationWithAnalyzersData(compilationWithAnalyzers, analyzerToIdMap);
var compilationWithAnalyzers = await CreateCompilationWithAnalyzerAsync(
project, analyzerBuilder.ToImmutable(), cancellationToken).ConfigureAwait(false);
var analyzerToIdMap = new BidirectionalMap<string, DiagnosticAnalyzer>(analyzerMapBuilder);
static async Task<CompilationWithAnalyzers> CreateCompilationWithAnalyzerAsync(
Project project,
ImmutableArray<DiagnosticAnalyzer> analyzers,
CancellationToken cancellationToken)
{
// Always run analyzers concurrently in OOP
const bool concurrentAnalysis = true;
// Get original compilation
var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false);
// Fork compilation with concurrent build. this is okay since WithAnalyzers will fork compilation
// anyway to attach event queue. This should make compiling compilation concurrent and make things
// faster
compilation = compilation.WithOptions(compilation.Options.WithConcurrentBuild(concurrentAnalysis));
// Run analyzers concurrently, with performance logging and reporting suppressed diagnostics.
// This allows all client requests with or without performance data and/or suppressed diagnostics to be satisfied.
// TODO: can we support analyzerExceptionFilter in remote host?
// right now, host doesn't support watson, we might try to use new NonFatal watson API?
var analyzerOptions = new CompilationWithAnalyzersOptions(
options: new WorkspaceAnalyzerOptions(project.AnalyzerOptions, project.Solution),
onAnalyzerException: null,
analyzerExceptionFilter: null,
concurrentAnalysis: concurrentAnalysis,
logAnalyzerExecutionTime: true,
reportSuppressedDiagnostics: true);
return compilation.WithAnalyzers(analyzers, analyzerOptions);
}
return new CompilationWithAnalyzersCacheEntry(compilationWithAnalyzers, analyzerToIdMap);
static async Task<CompilationWithAnalyzers> CreateCompilationWithAnalyzerAsync(
Project project,
ImmutableArray<DiagnosticAnalyzer> analyzers,
CancellationToken cancellationToken)
{
// Always run analyzers concurrently in OOP
const bool concurrentAnalysis = true;
// Get original compilation
var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false);
// Fork compilation with concurrent build. this is okay since WithAnalyzers will fork compilation
// anyway to attach event queue. This should make compiling compilation concurrent and make things
// faster
compilation = compilation.WithOptions(compilation.Options.WithConcurrentBuild(concurrentAnalysis));
// Run analyzers concurrently, with performance logging and reporting suppressed diagnostics.
// This allows all client requests with or without performance data and/or suppressed diagnostics to be satisfied.
// TODO: can we support analyzerExceptionFilter in remote host?
// right now, host doesn't support watson, we might try to use new NonFatal watson API?
var analyzerOptions = new CompilationWithAnalyzersOptions(
options: new WorkspaceAnalyzerOptions(project.AnalyzerOptions, project.Solution),
onAnalyzerException: null,
analyzerExceptionFilter: null,
concurrentAnalysis: concurrentAnalysis,
logAnalyzerExecutionTime: true,
reportSuppressedDiagnostics: true);
return compilation.WithAnalyzers(analyzers, analyzerOptions);
}
}
private sealed class CompilationWithAnalyzersCacheEntry
{
public CompilationWithAnalyzers CompilationWithAnalyzers { get; }
public BidirectionalMap<string, DiagnosticAnalyzer> AnalyzerToIdMap { get; }
public CompilationWithAnalyzersCacheEntry(CompilationWithAnalyzers compilationWithAnalyzers, BidirectionalMap<string, DiagnosticAnalyzer> analyzerToIdMap)
{
CompilationWithAnalyzers = compilationWithAnalyzers;
AnalyzerToIdMap = analyzerToIdMap;
}
}
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册