diff --git a/src/Features/Core/Diagnostics/AnalyzerManager.cs b/src/Features/Core/Diagnostics/AnalyzerManager.cs index 17f636fe5e99c..564963b38695b 100644 --- a/src/Features/Core/Diagnostics/AnalyzerManager.cs +++ b/src/Features/Core/Diagnostics/AnalyzerManager.cs @@ -102,60 +102,66 @@ public ImmutableArray GetDiagnosticDescriptors(DiagnosticA } /// - /// Get identity and s map + /// Get identity and s map for given + /// + public ImmutableDictionary> GetHostDiagnosticAnalyzersPerReference(string language) + { + return _hostDiagnosticAnalyzersPerLanguageMap.GetOrAdd(language, CreateHostDiagnosticAnalyzers); + } + + /// + /// Create identity and s map /// public ImmutableDictionary> GetHostDiagnosticDescriptorsPerReference() { - return GetDiagnosticDescriptorsPerReference(_lazyHostDiagnosticAnalyzersPerReferenceMap.Value); + return CreateDiagnosticDescriptorsPerReference(_lazyHostDiagnosticAnalyzersPerReferenceMap.Value); } /// - /// Get identity and s map for given + /// Create identity and s map for given /// - public ImmutableDictionary> GetDiagnosticDescriptorsPerReference(Project project) + public ImmutableDictionary> CreateDiagnosticDescriptorsPerReference(Project project) + { + return CreateDiagnosticDescriptorsPerReference(CreateDiagnosticAnalyzersPerReference(project)); + } + + /// + /// Create identity and s map for given + /// + public ImmutableDictionary> CreateDiagnosticAnalyzersPerReference(Project project) { var hostAnalyzerReferences = GetHostDiagnosticAnalyzersPerReference(project.Language); - var projectAnalyzerReferences = CreateDiagnosticAnalyzersPerReferenceMap(CreateAnalyzerReferencesMap(project.AnalyzerReferences), project.Language); + var projectAnalyzerReferences = CreateDiagnosticAnalyzersPerReferenceMap(CreateAnalyzerReferencesMap(project.AnalyzerReferences.Where(CheckAnalyzerReferenceIdentity)), project.Language); - return GetDiagnosticDescriptorsPerReference(hostAnalyzerReferences.Concat(projectAnalyzerReferences)); + return MergeDiagnosticAnalyzerMap(hostAnalyzerReferences, projectAnalyzerReferences); } /// - /// Get identity and s map for given + /// Create s collection for given /// - public ImmutableDictionary> GetHostDiagnosticAnalyzersPerReference(string language) + public ImmutableArray CreateDiagnosticAnalyzers(Project project) { - return _hostDiagnosticAnalyzersPerLanguageMap.GetOrAdd(language, CreateHostDiagnosticAnalyzers); + var analyzersPerReferences = CreateDiagnosticAnalyzersPerReference(project); + return analyzersPerReferences.SelectMany(kv => kv.Value).ToImmutableArray(); } - private ImmutableDictionary> GetDiagnosticDescriptorsPerReference( - IEnumerable>> analyzersMap) + private ImmutableDictionary> CreateDiagnosticDescriptorsPerReference( + ImmutableDictionary> analyzersMap) { - var seen = new HashSet(); var builder = ImmutableDictionary.CreateBuilder>(); foreach (var kv in analyzersMap) { var referenceId = kv.Key; var analyzers = kv.Value; - // this can happen if same analyzer exist in both host and projects. - if (builder.ContainsKey(referenceId)) - { - continue; - } - var descriptors = ImmutableArray.CreateBuilder(); foreach (var analyzer in analyzers) { - // don't put duplicated analyzers - if (analyzer == null || !seen.Add(analyzer)) - { - continue; - } - + // given map should be in good shape. no duplication. no null and etc descriptors.AddRange(GetDiagnosticDescriptors(analyzer)); } + // there can't be duplication since _hostAnalyzerReferenceMap is already de-duplicated. builder.Add(referenceId, descriptors.ToImmutable()); } @@ -190,6 +196,16 @@ private static string GetAnalyzerReferenceId(AnalyzerReference reference) return reference.Display ?? FeaturesResources.Unknown; } + private bool CheckAnalyzerReferenceIdentity(AnalyzerReference reference) + { + if (reference == null) + { + return false; + } + + return !_hostAnalyzerReferencesMap.ContainsKey(GetAnalyzerReferenceId(reference)); + } + private static ImmutableDictionary> CreateDiagnosticAnalyzersPerReferenceMap( IDictionary analyzerReferencesMap, string languageOpt = null) { @@ -204,7 +220,7 @@ private static ImmutableDictionary> C } // input "analyzerReferencesMap" is a dictionary, so there will be no duplication here. - builder.Add(reference.Key, analyzers); + builder.Add(reference.Key, analyzers.WhereNotNull().ToImmutableArray()); } return builder.ToImmutable(); @@ -248,5 +264,27 @@ private static ImmutableArray CreateAnalyzerReferencesFromAss return builder.ToImmutable(); } + + private static ImmutableDictionary> MergeDiagnosticAnalyzerMap( + ImmutableDictionary> map1, ImmutableDictionary> map2) + { + var current = map1; + var seen = new HashSet(map1.Values.SelectMany(v => v)); + + foreach (var kv in map2) + { + var referenceIdentity = kv.Key; + var analyzers = kv.Value; + + if (map1.ContainsKey(referenceIdentity)) + { + continue; + } + + current = current.Add(referenceIdentity, analyzers.Where(a => seen.Add(a)).ToImmutableArray()); + } + + return current; + } } } diff --git a/src/Features/Core/Diagnostics/DiagnosticAnalyzerService.cs b/src/Features/Core/Diagnostics/DiagnosticAnalyzerService.cs index dca90e8d361ce..5433f3841ce9e 100644 --- a/src/Features/Core/Diagnostics/DiagnosticAnalyzerService.cs +++ b/src/Features/Core/Diagnostics/DiagnosticAnalyzerService.cs @@ -43,7 +43,7 @@ public ImmutableDictionary> GetDiag return _analyzerManager.GetHostDiagnosticDescriptorsPerReference(); } - return _analyzerManager.GetDiagnosticDescriptorsPerReference(projectOpt); + return _analyzerManager.CreateDiagnosticDescriptorsPerReference(projectOpt); } public ImmutableArray GetDiagnosticDescriptors(DiagnosticAnalyzer analyzer) diff --git a/src/Features/Core/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs b/src/Features/Core/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs index 6f825124d1e58..b00c9d701af1c 100644 --- a/src/Features/Core/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs +++ b/src/Features/Core/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Text; @@ -30,9 +31,11 @@ public override Task AnalyzeDocumentAsync(Document document, SyntaxNode bodyOpt, return SpecializedTasks.EmptyTask; } - public override Task AnalyzeProjectAsync(Project project, bool semanticsChanged, CancellationToken cancellationToken) + public override async Task AnalyzeProjectAsync(Project project, bool semanticsChanged, CancellationToken cancellationToken) { - return SpecializedTasks.EmptyTask; + var diagnostics = await GetDiagnosticsAsync(project.Solution, project.Id, null, cancellationToken).ConfigureAwait(false); + + RaiseEvents(project, diagnostics); } public override Task AnalyzeSyntaxAsync(Document document, CancellationToken cancellationToken) @@ -57,51 +60,150 @@ public override Task NewSolutionSnapshotAsync(Solution solution, CancellationTok public override void RemoveDocument(DocumentId documentId) { + _owner.RaiseDiagnosticsUpdated( + this, new DiagnosticsUpdatedArgs(ValueTuple.Create(this, documentId), _workspace, null, null, null, ImmutableArray.Empty)); } public override void RemoveProject(ProjectId projectId) { + _owner.RaiseDiagnosticsUpdated( + this, new DiagnosticsUpdatedArgs(ValueTuple.Create(this, projectId), _workspace, null, null, null, ImmutableArray.Empty)); } #endregion public override Task> GetCachedDiagnosticsAsync(Solution solution, ProjectId projectId = null, DocumentId documentId = null, CancellationToken cancellationToken = default(CancellationToken)) { - return SpecializedTasks.EmptyImmutableArray(); + return GetDiagnosticsAsync(solution, projectId, documentId, cancellationToken); } public override Task> GetSpecificCachedDiagnosticsAsync(Solution solution, object id, CancellationToken cancellationToken) { - return SpecializedTasks.EmptyImmutableArray(); + return GetSpecificDiagnosticsAsync(solution, id, cancellationToken); } - public override Task> GetDiagnosticsAsync(Solution solution, ProjectId projectId = null, DocumentId documentId = null, CancellationToken cancellationToken = default(CancellationToken)) + public override async Task> GetDiagnosticsAsync(Solution solution, ProjectId projectId = null, DocumentId documentId = null, CancellationToken cancellationToken = default(CancellationToken)) { - return SpecializedTasks.EmptyImmutableArray(); + if (documentId != null) + { + var diagnostics = await GetProjectDiagnosticsAsync(solution.GetProject(projectId), cancellationToken).ConfigureAwait(false); + return diagnostics.Where(d => d.DocumentId == documentId).ToImmutableArrayOrEmpty(); + } + + if (projectId != null) + { + return await GetProjectDiagnosticsAsync(solution.GetProject(projectId), cancellationToken).ConfigureAwait(false); + } + + var builder = ImmutableArray.CreateBuilder(); + foreach (var project in solution.Projects) + { + builder.AddRange(await GetProjectDiagnosticsAsync(project, cancellationToken).ConfigureAwait(false)); + } + + return builder.ToImmutable(); } - public override Task> GetSpecificDiagnosticsAsync(Solution solution, object id, CancellationToken cancellationToken) + public override async Task> GetSpecificDiagnosticsAsync(Solution solution, object id, CancellationToken cancellationToken) { - return SpecializedTasks.EmptyImmutableArray(); + if (id is ValueTuple) + { + var key = (ValueTuple)id; + return await GetDiagnosticsAsync(solution, key.Item2.ProjectId, key.Item2, cancellationToken).ConfigureAwait(false); + } + + if (id is ValueTuple) + { + var key = (ValueTuple)id; + var diagnostics = await GetDiagnosticsAsync(solution, key.Item2, null, cancellationToken).ConfigureAwait(false); + return diagnostics.Where(d => d.DocumentId == null).ToImmutableArray(); + } + + return ImmutableArray.Empty; } - public override Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId projectId = null, DocumentId documentId = null, ImmutableHashSet diagnosticIds = null, CancellationToken cancellationToken = default(CancellationToken)) + public override async Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId projectId = null, DocumentId documentId = null, ImmutableHashSet diagnosticIds = null, CancellationToken cancellationToken = default(CancellationToken)) { - return SpecializedTasks.EmptyImmutableArray(); + var diagnostics = await GetDiagnosticsAsync(solution, projectId, documentId, cancellationToken).ConfigureAwait(false); + return diagnostics.Where(d => diagnosticIds.Contains(d.Id)).ToImmutableArrayOrEmpty(); } - public override Task> GetProjectDiagnosticsForIdsAsync(Solution solution, ProjectId projectId = null, ImmutableHashSet diagnosticIds = null, CancellationToken cancellationToken = default(CancellationToken)) + public override async Task> GetProjectDiagnosticsForIdsAsync(Solution solution, ProjectId projectId = null, ImmutableHashSet diagnosticIds = null, CancellationToken cancellationToken = default(CancellationToken)) { - return SpecializedTasks.EmptyImmutableArray(); + var diagnostics = await GetDiagnosticsForIdsAsync(solution, projectId, null, diagnosticIds, cancellationToken).ConfigureAwait(false); + return diagnostics.Where(d => d.DocumentId == null).ToImmutableArray(); } - public override Task TryAppendDiagnosticsForSpanAsync(Document document, TextSpan range, List diagnostics, CancellationToken cancellationToken) + public override async Task TryAppendDiagnosticsForSpanAsync(Document document, TextSpan range, List result, CancellationToken cancellationToken) { - return SpecializedTasks.False; + result.AddRange(await GetDiagnosticsForSpanAsync(document, range, cancellationToken).ConfigureAwait(false)); + return true; } - public override Task> GetDiagnosticsForSpanAsync(Document document, TextSpan range, CancellationToken cancellationToken) + public override async Task> GetDiagnosticsForSpanAsync(Document document, TextSpan range, CancellationToken cancellationToken) { - return SpecializedTasks.EmptyEnumerable(); + var diagnostics = await GetDiagnosticsAsync(document.Project.Solution, document.Project.Id, document.Id, cancellationToken).ConfigureAwait(false); + return diagnostics.Where(d => range.IntersectsWith(d.TextSpan)); + } + + private async Task> GetProjectDiagnosticsAsync(Project project, CancellationToken cancellationToken) + { + if (project == null) + { + return ImmutableArray.Empty; + } + + var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + + var analyzers = _analyzerManager.CreateDiagnosticAnalyzers(project); + + var compilationWithAnalyzer = compilation.WithAnalyzers(analyzers, project.AnalyzerOptions, cancellationToken); + + // REVIEW: this API is a bit strange. + // if getting diagnostic is cancelled, it has to create new compilation and do everything from scretch again? + return GetDiagnosticData(project, await compilationWithAnalyzer.GetAnalyzerDiagnosticsAsync().ConfigureAwait(false)).ToImmutableArrayOrEmpty(); + } + + private IEnumerable GetDiagnosticData(Project project, ImmutableArray diagnostics) + { + foreach (var diagnostic in diagnostics) + { + if (diagnostic.Location == Location.None) + { + yield return DiagnosticData.Create(project, diagnostic); + continue; + } + + var document = project.GetDocument(diagnostic.Location.SourceTree); + if (document == null) + { + continue; + } + + yield return DiagnosticData.Create(document, diagnostic); + } + } + + private void RaiseEvents(Project project, ImmutableArray diagnostics) + { + var groups = diagnostics.GroupBy(d => d.DocumentId); + + var solution = project.Solution; + var workspace = solution.Workspace; + + foreach (var kv in groups) + { + if (kv.Key == null) + { + _owner.RaiseDiagnosticsUpdated( + this, new DiagnosticsUpdatedArgs( + ValueTuple.Create(this, project.Id), workspace, solution, project.Id, null, kv.ToImmutableArrayOrEmpty())); + continue; + } + + _owner.RaiseDiagnosticsUpdated( + this, new DiagnosticsUpdatedArgs( + ValueTuple.Create(this, kv.Key), workspace, solution, project.Id, kv.Key, kv.ToImmutableArrayOrEmpty())); + } } } }