From aa6d5faf667af5eeadcfc48b429dda7c929bc846 Mon Sep 17 00:00:00 2001 From: Heejae Chang Date: Thu, 3 Mar 2016 14:56:41 -0800 Subject: [PATCH 01/24] split things in multiple files. --- .../EngineV2/CompilerDiagnosticExecutor.cs | 153 ++++++++++++++++++ .../EngineV2/DiagnosticIncrementalAnalyzer.cs | 152 +---------------- ...ncrementalAnalyzer_BuildSynchronization.cs | 27 ++++ ...osticIncrementalAnalyzer_GetDiagnostics.cs | 75 +++++++++ ...crementalAnalyzer_GetDiagnosticsForSpan.cs | 26 +++ ...IncrementalAnalyzer_IncrementalAnalyzer.cs | 60 +++++++ src/Features/Core/Portable/Features.csproj | 5 + 7 files changed, 353 insertions(+), 145 deletions(-) create mode 100644 src/Features/Core/Portable/Diagnostics/EngineV2/CompilerDiagnosticExecutor.cs create mode 100644 src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_BuildSynchronization.cs create mode 100644 src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs create mode 100644 src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs create mode 100644 src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/CompilerDiagnosticExecutor.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/CompilerDiagnosticExecutor.cs new file mode 100644 index 0000000000000..04acae4363269 --- /dev/null +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/CompilerDiagnosticExecutor.cs @@ -0,0 +1,153 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 +{ + /// + /// Diagnostic Executor that only relies on compiler layer. this might be replaced by new CompilationWithAnalyzer API. + /// + internal static class CompilerDiagnosticExecutor + { + public static async Task AnalyzeAsync( + this Compilation compilation, ImmutableArray analyzers, CompilationWithAnalyzersOptions options, CancellationToken cancellationToken) + { + // Create driver that holds onto compilation and associated analyzers + var analyzerDriver = compilation.WithAnalyzers(analyzers, options); + + // Run all analyzers at once. + // REVIEW: why there are 2 different cancellation token? one that I can give to constructor and one I can give in to each method? + // REVIEW: we drop all those allocations for the diagnostics returned. can we avoid this? + await analyzerDriver.GetAnalyzerDiagnosticsAsync(cancellationToken).ConfigureAwait(false); + + // this is wierd, but now we iterate through each analyzer for each tree to get cached result. + // REVIEW: no better way to do this? + var noSpanFilter = default(TextSpan?); + + var resultMap = new AnalysisResult.ResultMap(); + foreach (var analyzer in analyzers) + { + // REVIEW: more unnecessary allocations just to get diagnostics per analyzer + var oneAnalyzers = ImmutableArray.Create(analyzer); + + foreach (var tree in compilation.SyntaxTrees) + { + var model = compilation.GetSemanticModel(tree); + + resultMap.AddSyntaxDiagnostics(analyzer, tree, await analyzerDriver.GetAnalyzerSyntaxDiagnosticsAsync(tree, oneAnalyzers, cancellationToken).ConfigureAwait(false)); + resultMap.AddSemanticDiagnostics(analyzer, tree, await analyzerDriver.GetAnalyzerSemanticDiagnosticsAsync(model, noSpanFilter, oneAnalyzers, cancellationToken).ConfigureAwait(false)); + } + + resultMap.AddCompilationDiagnostics(analyzer, await analyzerDriver.GetAnalyzerCompilationDiagnosticsAsync(oneAnalyzers, cancellationToken).ConfigureAwait(false)); + } + + return new AnalysisResult(resultMap); + } + } + + // REVIEW: this will probably go away once we have new API. + // now things run sequencially, so no thread-safety. + internal struct AnalysisResult + { + private readonly ResultMap resultMap; + + public AnalysisResult(ResultMap resultMap) + { + this.resultMap = resultMap; + } + + internal struct ResultMap + { + private Dictionary> _lazySyntaxLocals; + private Dictionary> _lazySemanticLocals; + + private Dictionary> _lazyNonLocals; + private List _lazyOthers; + + public void AddSyntaxDiagnostics(DiagnosticAnalyzer analyzer, SyntaxTree tree, ImmutableArray diagnostics) + { + AddDiagnostics(ref _lazySyntaxLocals, tree, diagnostics); + } + + public void AddSemanticDiagnostics(DiagnosticAnalyzer analyzer, SyntaxTree tree, ImmutableArray diagnostics) + { + AddDiagnostics(ref _lazySemanticLocals, tree, diagnostics); + } + + public void AddCompilationDiagnostics(DiagnosticAnalyzer analyzer, ImmutableArray diagnostics) + { + Dictionary> dummy = null; + AddDiagnostics(ref dummy, tree: null, diagnostics: diagnostics); + + // dummy should be always null + Contract.Requires(dummy == null); + } + + private void AddDiagnostics( + ref Dictionary> _lazyLocals, SyntaxTree tree, ImmutableArray diagnostics) + { + if (diagnostics.Length == 0) + { + return; + } + + for (var i = 0; i < diagnostics.Length; i++) + { + var diagnostic = diagnostics[i]; + + // REVIEW: what is our plan for additional locations? + switch (diagnostic.Location.Kind) + { + case LocationKind.None: + case LocationKind.ExternalFile: + { + // no location or reported to external files + _lazyOthers = _lazyOthers ?? new List(); + _lazyOthers.Add(diagnostic); + break; + } + case LocationKind.SourceFile: + { + if (tree != null && diagnostic.Location.SourceTree == tree) + { + // local diagnostics to a file + _lazyLocals = _lazyLocals ?? new Dictionary>(); + _lazyLocals.GetOrAdd(diagnostic.Location.SourceTree, _ => new List()).Add(diagnostic); + } + else if (diagnostic.Location.SourceTree != null) + { + // non local diagnostics to a file + _lazyNonLocals = _lazyNonLocals ?? new Dictionary>(); + _lazyNonLocals.GetOrAdd(diagnostic.Location.SourceTree, _ => new List()).Add(diagnostic); + } + else + { + // non local diagnostics without location + _lazyOthers = _lazyOthers ?? new List(); + _lazyOthers.Add(diagnostic); + } + + break; + } + case LocationKind.MetadataFile: + case LocationKind.XmlFile: + { + // something we don't care + continue; + } + default: + { + Contract.Fail("should not reach"); + break; + } + } + } + } + } + } +} diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs index d0fd340f1634e..2b5b3847d4df3 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs @@ -1,151 +1,29 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 { - internal class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncrementalAnalyzer + internal partial class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncrementalAnalyzer { private readonly int _correlationId; - public DiagnosticIncrementalAnalyzer(DiagnosticAnalyzerService owner, int correlationId, Workspace workspace, HostAnalyzerManager hostAnalyzerManager, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource) + public DiagnosticIncrementalAnalyzer( + DiagnosticAnalyzerService owner, + int correlationId, + Workspace workspace, + HostAnalyzerManager hostAnalyzerManager, + AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource) : base(owner, workspace, hostAnalyzerManager, hostDiagnosticUpdateSource) { _correlationId = correlationId; } - #region IIncrementalAnalyzer - public override Task AnalyzeDocumentAsync(Document document, SyntaxNode bodyOpt, CancellationToken cancellationToken) - { - return SpecializedTasks.EmptyTask; - } - - public override async Task AnalyzeProjectAsync(Project project, bool semanticsChanged, CancellationToken cancellationToken) - { - var diagnostics = await GetDiagnosticsAsync(project.Solution, project.Id, null, includeSuppressedDiagnostics: true, cancellationToken: cancellationToken).ConfigureAwait(false); - - RaiseEvents(project, diagnostics); - } - - public override Task AnalyzeSyntaxAsync(Document document, CancellationToken cancellationToken) - { - return SpecializedTasks.EmptyTask; - } - - public override Task DocumentOpenAsync(Document document, CancellationToken cancellationToken) - { - return SpecializedTasks.EmptyTask; - } - - public override Task DocumentCloseAsync(Document document, CancellationToken cancellationToken) - { - return SpecializedTasks.EmptyTask; - } - - public override Task DocumentResetAsync(Document document, CancellationToken cancellationToken) - { - return SpecializedTasks.EmptyTask; - } - - public override Task NewSolutionSnapshotAsync(Solution solution, CancellationToken cancellationToken) - { - return SpecializedTasks.EmptyTask; - } - - public override void RemoveDocument(DocumentId documentId) - { - Owner.RaiseDiagnosticsUpdated(this, DiagnosticsUpdatedArgs.DiagnosticsRemoved( - ValueTuple.Create(this, documentId), Workspace, null, null, null)); - } - - public override void RemoveProject(ProjectId projectId) - { - Owner.RaiseDiagnosticsUpdated(this, DiagnosticsUpdatedArgs.DiagnosticsRemoved( - ValueTuple.Create(this, projectId), Workspace, null, null, null)); - } - #endregion - - public override Task> GetCachedDiagnosticsAsync(Solution solution, ProjectId projectId = null, DocumentId documentId = null, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken)) - { - return GetDiagnosticsAsync(solution, projectId, documentId, includeSuppressedDiagnostics, cancellationToken); - } - - public override Task> GetSpecificCachedDiagnosticsAsync(Solution solution, object id, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken)) - { - return GetSpecificDiagnosticsAsync(solution, id, includeSuppressedDiagnostics, cancellationToken); - } - - public override async Task> GetDiagnosticsAsync(Solution solution, ProjectId projectId = null, DocumentId documentId = null, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken)) - { - if (documentId != null) - { - var diagnostics = await GetProjectDiagnosticsAsync(solution.GetProject(projectId), includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); - return diagnostics.Where(d => d.DocumentId == documentId).ToImmutableArrayOrEmpty(); - } - - if (projectId != null) - { - return await GetProjectDiagnosticsAsync(solution.GetProject(projectId), includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); - } - - var builder = ImmutableArray.CreateBuilder(); - foreach (var project in solution.Projects) - { - builder.AddRange(await GetProjectDiagnosticsAsync(project, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false)); - } - - return builder.ToImmutable(); - } - - public override async Task> GetSpecificDiagnosticsAsync(Solution solution, object id, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken)) - { - if (id is ValueTuple) - { - var key = (ValueTuple)id; - return await GetDiagnosticsAsync(solution, key.Item2.ProjectId, key.Item2, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); - } - - if (id is ValueTuple) - { - var key = (ValueTuple)id; - var diagnostics = await GetDiagnosticsAsync(solution, key.Item2, null, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); - return diagnostics.Where(d => d.DocumentId == null).ToImmutableArray(); - } - - return ImmutableArray.Empty; - } - - public override async Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId projectId = null, DocumentId documentId = null, ImmutableHashSet diagnosticIds = null, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken)) - { - var diagnostics = await GetDiagnosticsAsync(solution, projectId, documentId, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); - return diagnostics.Where(d => diagnosticIds.Contains(d.Id)).ToImmutableArrayOrEmpty(); - } - - public override async Task> GetProjectDiagnosticsForIdsAsync(Solution solution, ProjectId projectId = null, ImmutableHashSet diagnosticIds = null, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken)) - { - var diagnostics = await GetDiagnosticsForIdsAsync(solution, projectId, null, diagnosticIds, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); - return diagnostics.Where(d => d.DocumentId == null).ToImmutableArray(); - } - - public override async Task TryAppendDiagnosticsForSpanAsync(Document document, TextSpan range, List result, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken)) - { - result.AddRange(await GetDiagnosticsForSpanAsync(document, range, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false)); - return true; - } - - public override async Task> GetDiagnosticsForSpanAsync(Document document, TextSpan range, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken)) - { - var diagnostics = await GetDiagnosticsAsync(document.Project.Solution, document.Project.Id, document.Id, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); - return diagnostics.Where(d => range.IntersectsWith(d.TextSpan)); - } - private async Task> GetProjectDiagnosticsAsync(Project project, bool includeSuppressedDiagnostics, CancellationToken cancellationToken) { if (project == null) @@ -186,22 +64,6 @@ private IEnumerable GetDiagnosticData(Project project, Immutable } } - public override Task SynchronizeWithBuildAsync(DiagnosticAnalyzerService.BatchUpdateToken token, Project project, ImmutableArray diagnostics) - { - // V2 engine doesn't do anything. - // it means live error always win over build errors. build errors that can't be reported by live analyzer - // are already taken cared by engine - return SpecializedTasks.EmptyTask; - } - - public override Task SynchronizeWithBuildAsync(DiagnosticAnalyzerService.BatchUpdateToken token, Document document, ImmutableArray diagnostics) - { - // V2 engine doesn't do anything. - // it means live error always win over build errors. build errors that can't be reported by live analyzer - // are already taken cared by engine - return SpecializedTasks.EmptyTask; - } - private void RaiseEvents(Project project, ImmutableArray diagnostics) { var groups = diagnostics.GroupBy(d => d.DocumentId); diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_BuildSynchronization.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_BuildSynchronization.cs new file mode 100644 index 0000000000000..98d65755b5284 --- /dev/null +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_BuildSynchronization.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Threading.Tasks; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 +{ + internal partial class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncrementalAnalyzer + { + public override Task SynchronizeWithBuildAsync(DiagnosticAnalyzerService.BatchUpdateToken token, Project project, ImmutableArray diagnostics) + { + // V2 engine doesn't do anything. + // it means live error always win over build errors. build errors that can't be reported by live analyzer + // are already taken cared by engine + return SpecializedTasks.EmptyTask; + } + + public override Task SynchronizeWithBuildAsync(DiagnosticAnalyzerService.BatchUpdateToken token, Document document, ImmutableArray diagnostics) + { + // V2 engine doesn't do anything. + // it means live error always win over build errors. build errors that can't be reported by live analyzer + // are already taken cared by engine + return SpecializedTasks.EmptyTask; + } + } +} diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs new file mode 100644 index 0000000000000..59705409433c0 --- /dev/null +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 +{ + internal partial class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncrementalAnalyzer + { + public override Task> GetSpecificCachedDiagnosticsAsync(Solution solution, object id, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken)) + { + return GetSpecificDiagnosticsAsync(solution, id, includeSuppressedDiagnostics, cancellationToken); + } + + public override Task> GetCachedDiagnosticsAsync(Solution solution, ProjectId projectId = null, DocumentId documentId = null, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken)) + { + return GetDiagnosticsAsync(solution, projectId, documentId, includeSuppressedDiagnostics, cancellationToken); + } + + public override async Task> GetSpecificDiagnosticsAsync(Solution solution, object id, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken)) + { + if (id is ValueTuple) + { + var key = (ValueTuple)id; + return await GetDiagnosticsAsync(solution, key.Item2.ProjectId, key.Item2, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); + } + + if (id is ValueTuple) + { + var key = (ValueTuple)id; + var diagnostics = await GetDiagnosticsAsync(solution, key.Item2, null, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); + return diagnostics.Where(d => d.DocumentId == null).ToImmutableArray(); + } + + return ImmutableArray.Empty; + } + + public override async Task> GetDiagnosticsAsync(Solution solution, ProjectId projectId = null, DocumentId documentId = null, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken)) + { + if (documentId != null) + { + var diagnostics = await GetProjectDiagnosticsAsync(solution.GetProject(projectId), includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); + return diagnostics.Where(d => d.DocumentId == documentId).ToImmutableArrayOrEmpty(); + } + + if (projectId != null) + { + return await GetProjectDiagnosticsAsync(solution.GetProject(projectId), includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); + } + + var builder = ImmutableArray.CreateBuilder(); + foreach (var project in solution.Projects) + { + builder.AddRange(await GetProjectDiagnosticsAsync(project, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false)); + } + + return builder.ToImmutable(); + } + + public override async Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId projectId = null, DocumentId documentId = null, ImmutableHashSet diagnosticIds = null, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken)) + { + var diagnostics = await GetDiagnosticsAsync(solution, projectId, documentId, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); + return diagnostics.Where(d => diagnosticIds.Contains(d.Id)).ToImmutableArrayOrEmpty(); + } + + public override async Task> GetProjectDiagnosticsForIdsAsync(Solution solution, ProjectId projectId = null, ImmutableHashSet diagnosticIds = null, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken)) + { + var diagnostics = await GetDiagnosticsForIdsAsync(solution, projectId, null, diagnosticIds, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); + return diagnostics.Where(d => d.DocumentId == null).ToImmutableArray(); + } + } +} \ No newline at end of file diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs new file mode 100644 index 0000000000000..668e24bebba31 --- /dev/null +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 +{ + internal partial class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncrementalAnalyzer + { + public override async Task TryAppendDiagnosticsForSpanAsync(Document document, TextSpan range, List result, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken)) + { + result.AddRange(await GetDiagnosticsForSpanAsync(document, range, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false)); + return true; + } + + public override async Task> GetDiagnosticsForSpanAsync(Document document, TextSpan range, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken)) + { + var diagnostics = await GetDiagnosticsAsync(document.Project.Solution, document.Project.Id, document.Id, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); + return diagnostics.Where(d => range.IntersectsWith(d.TextSpan)); + } + } +} diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs new file mode 100644 index 0000000000000..2170096df1599 --- /dev/null +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading; +using System.Threading.Tasks; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 +{ + internal partial class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncrementalAnalyzer + { + public override Task AnalyzeSyntaxAsync(Document document, CancellationToken cancellationToken) + { + return SpecializedTasks.EmptyTask; + } + + public override Task AnalyzeDocumentAsync(Document document, SyntaxNode bodyOpt, CancellationToken cancellationToken) + { + return SpecializedTasks.EmptyTask; + } + + public override async Task AnalyzeProjectAsync(Project project, bool semanticsChanged, CancellationToken cancellationToken) + { + var diagnostics = await GetDiagnosticsAsync(project.Solution, project.Id, null, includeSuppressedDiagnostics: true, cancellationToken: cancellationToken).ConfigureAwait(false); + + RaiseEvents(project, diagnostics); + } + + public override Task DocumentOpenAsync(Document document, CancellationToken cancellationToken) + { + return SpecializedTasks.EmptyTask; + } + + public override Task DocumentCloseAsync(Document document, CancellationToken cancellationToken) + { + return SpecializedTasks.EmptyTask; + } + + public override Task DocumentResetAsync(Document document, CancellationToken cancellationToken) + { + return SpecializedTasks.EmptyTask; + } + + public override Task NewSolutionSnapshotAsync(Solution solution, CancellationToken cancellationToken) + { + return SpecializedTasks.EmptyTask; + } + + public override void RemoveDocument(DocumentId documentId) + { + Owner.RaiseDiagnosticsUpdated(this, DiagnosticsUpdatedArgs.DiagnosticsRemoved( + ValueTuple.Create(this, documentId), Workspace, null, null, null)); + } + + public override void RemoveProject(ProjectId projectId) + { + Owner.RaiseDiagnosticsUpdated(this, DiagnosticsUpdatedArgs.DiagnosticsRemoved( + ValueTuple.Create(this, projectId), Workspace, null, null, null)); + } + } +} diff --git a/src/Features/Core/Portable/Features.csproj b/src/Features/Core/Portable/Features.csproj index 92265236f82bd..09a94e74426cd 100644 --- a/src/Features/Core/Portable/Features.csproj +++ b/src/Features/Core/Portable/Features.csproj @@ -179,6 +179,11 @@ + + + + + From f8491ab539250fbe5af8c9c7df98ab793982ee8f Mon Sep 17 00:00:00 2001 From: Heejae Chang Date: Thu, 3 Mar 2016 17:36:12 -0800 Subject: [PATCH 02/24] doing more basic arrangement for oop --- ...sticIncrementalAnalyzer.ActiveFileState.cs | 23 ++++ ...gnosticIncrementalAnalyzer.ProjectState.cs | 26 ++++ .../EngineV2/DiagnosticIncrementalAnalyzer.cs | 16 +++ ...ncrementalAnalyzer_BuildSynchronization.cs | 10 +- ...osticIncrementalAnalyzer_GetDiagnostics.cs | 1 + ...crementalAnalyzer_GetDiagnosticsForSpan.cs | 1 + ...IncrementalAnalyzer_IncrementalAnalyzer.cs | 128 +++++++++++++++--- src/Features/Core/Portable/Features.csproj | 2 + 8 files changed, 183 insertions(+), 24 deletions(-) create mode 100644 src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ActiveFileState.cs create mode 100644 src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ActiveFileState.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ActiveFileState.cs new file mode 100644 index 0000000000000..c8ef7646ab638 --- /dev/null +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ActiveFileState.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 +{ + internal partial class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncrementalAnalyzer + { + // TODO: implement active file state + // this should hold onto local syntax/semantic diagnostics for active file in memory. + // this should also hold onto CompilationWithAnalyzer last time used. + // this should use syntax/semantic version for its version + private class ActiveFileState + { + + } + } +} diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs new file mode 100644 index 0000000000000..f98078b30d726 --- /dev/null +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 +{ + internal partial class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncrementalAnalyzer + { + // TODO: implement project state + // this should hold onto information similar to CompilerDiagnsoticExecutor.AnalysisResult + // this should use dependant project version as its version + // this should only cache opened file diagnostics in memory, and all diagnostics in other place. + // we might just use temporary storage rather than peristant storage. but will see. + // now we don't update individual document incrementally. + // but some data might comes from active file state. + private class ProjectState + { + + } + } +} diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs index 2b5b3847d4df3..d49c3e1e1ffea 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs @@ -5,10 +5,13 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Options; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 { + // TODO: implement correct events and etc. internal partial class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncrementalAnalyzer { private readonly int _correlationId; @@ -24,6 +27,19 @@ public DiagnosticIncrementalAnalyzer( _correlationId = correlationId; } + private static bool AnalysisEnabled(Document document) + { + // change it to check active file (or visible files), not open files if active file tracking is enabled. + // otherwise, use open file. + return document.IsOpen(); + } + + private bool FullAnalysisEnabled(Workspace workspace, string language) + { + return workspace.Options.GetOption(ServiceFeatureOnOffOptions.ClosedFileDiagnostic, language) && + workspace.Options.GetOption(RuntimeOptions.FullSolutionAnalysis); + } + private async Task> GetProjectDiagnosticsAsync(Project project, bool includeSuppressedDiagnostics, CancellationToken cancellationToken) { if (project == null) diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_BuildSynchronization.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_BuildSynchronization.cs index 98d65755b5284..aa378e8c3aab4 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_BuildSynchronization.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_BuildSynchronization.cs @@ -8,19 +8,17 @@ namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 { internal partial class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncrementalAnalyzer { + // TODO: this API will be changed to 1 API that gives all diagnostics under a project + // and that will simply replace project state public override Task SynchronizeWithBuildAsync(DiagnosticAnalyzerService.BatchUpdateToken token, Project project, ImmutableArray diagnostics) { - // V2 engine doesn't do anything. - // it means live error always win over build errors. build errors that can't be reported by live analyzer - // are already taken cared by engine + // TODO: for now, we dont do anything. return SpecializedTasks.EmptyTask; } public override Task SynchronizeWithBuildAsync(DiagnosticAnalyzerService.BatchUpdateToken token, Document document, ImmutableArray diagnostics) { - // V2 engine doesn't do anything. - // it means live error always win over build errors. build errors that can't be reported by live analyzer - // are already taken cared by engine + // TODO: for now, we dont do anything. return SpecializedTasks.EmptyTask; } } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs index 59705409433c0..093213a776ae3 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs @@ -8,6 +8,7 @@ namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 { + // TODO: need to mimic what V1 does. use cache if possible, otherwise, calculate at the spot. internal partial class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncrementalAnalyzer { public override Task> GetSpecificCachedDiagnosticsAsync(Solution solution, object id, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken)) diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs index 668e24bebba31..e18b7d8c9651f 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs @@ -9,6 +9,7 @@ namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 { + // TODO: we need to do what V1 does for LB for span. internal partial class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncrementalAnalyzer { public override async Task TryAppendDiagnosticsForSpanAsync(Document document, TextSpan range, List result, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken)) diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs index 2170096df1599..b1556d8084030 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs @@ -1,60 +1,152 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Collections.Immutable; +using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 { + // TODO: make it to use cache internal partial class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncrementalAnalyzer { - public override Task AnalyzeSyntaxAsync(Document document, CancellationToken cancellationToken) + public async override Task AnalyzeSyntaxAsync(Document document, CancellationToken cancellationToken) { - return SpecializedTasks.EmptyTask; - } + if (!AnalysisEnabled(document)) + { + return; + } - public override Task AnalyzeDocumentAsync(Document document, SyntaxNode bodyOpt, CancellationToken cancellationToken) - { - return SpecializedTasks.EmptyTask; - } + // TODO: make active file state to cache compilationWithAnalyzer + // REVIEW: this is really wierd that we need compilation for syntax diagnostics which basically defeat any reason + // we have syntax diagnostics. + var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - public override async Task AnalyzeProjectAsync(Project project, bool semanticsChanged, CancellationToken cancellationToken) - { - var diagnostics = await GetDiagnosticsAsync(project.Solution, project.Id, null, includeSuppressedDiagnostics: true, cancellationToken: cancellationToken).ConfigureAwait(false); + // TODO: make it to use state manager + var analyzers = HostAnalyzerManager.CreateDiagnosticAnalyzers(document.Project); - RaiseEvents(project, diagnostics); + // Create driver that holds onto compilation and associated analyzers + // TODO: use CompilationWithAnalyzerOption instead of AnalyzerOption so that we can have exception filter and etc + var analyzerDriver = compilation.WithAnalyzers(analyzers, document.Project.AnalyzerOptions, cancellationToken); + + foreach (var analyzer in analyzers) + { + // TODO: implement perf optimization not to run analyzers that are not needed. + // REVIEW: more unnecessary allocations just to get diagnostics per analyzer + var oneAnalyzers = ImmutableArray.Create(analyzer); + + // TODO: use cache for perf optimization + var diagnostics = await analyzerDriver.GetAnalyzerSyntaxDiagnosticsAsync(tree, oneAnalyzers, cancellationToken).ConfigureAwait(false); + + // we only care about local diagnostics + var diagnosticData = GetDiagnosticData(document.Project, diagnostics).Where(d => d.DocumentId == document.Id); + + // TODO: update using right arguments + Owner.RaiseDiagnosticsUpdated( + this, DiagnosticsUpdatedArgs.DiagnosticsCreated( + ValueTuple.Create(this, "Syntax", document.Id), document.Project.Solution.Workspace, document.Project.Solution, document.Project.Id, document.Id, diagnosticData.ToImmutableArrayOrEmpty())); + } } - public override Task DocumentOpenAsync(Document document, CancellationToken cancellationToken) + public override async Task AnalyzeDocumentAsync(Document document, SyntaxNode bodyOpt, CancellationToken cancellationToken) { - return SpecializedTasks.EmptyTask; + if (!AnalysisEnabled(document)) + { + return; + } + + // TODO: make active file state to cache compilationWithAnalyzer + var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + // TODO: make it to use state manager + var analyzers = HostAnalyzerManager.CreateDiagnosticAnalyzers(document.Project); + + // Create driver that holds onto compilation and associated analyzers + // TODO: use CompilationWithAnalyzerOption instead of AnalyzerOption so that we can have exception filter and etc + var analyzerDriver = compilation.WithAnalyzers(analyzers, document.Project.AnalyzerOptions, cancellationToken); + + var noSpanFilter = default(TextSpan?); + foreach (var analyzer in analyzers) + { + // REVIEW: more unnecessary allocations just to get diagnostics per analyzer + var oneAnalyzers = ImmutableArray.Create(analyzer); + + // TODO: use cache for perf optimization + // REVIEW: I think we don't even need member tracking optimization + var diagnostics = await analyzerDriver.GetAnalyzerSemanticDiagnosticsAsync(model, noSpanFilter, oneAnalyzers, cancellationToken).ConfigureAwait(false); + + var diagnosticData = GetDiagnosticData(document.Project, diagnostics).Where(d => d.DocumentId == document.Id); + + // TODO: update using right arguments + Owner.RaiseDiagnosticsUpdated( + this, DiagnosticsUpdatedArgs.DiagnosticsCreated( + ValueTuple.Create(this, "Semantic", document.Id), document.Project.Solution.Workspace, document.Project.Solution, document.Project.Id, document.Id, diagnosticData.ToImmutableArrayOrEmpty())); + } } public override Task DocumentCloseAsync(Document document, CancellationToken cancellationToken) { + // TODO: at this event, if file being closed is active file (the one in ActiveFileState), we should put that data into + // ProjectState return SpecializedTasks.EmptyTask; } public override Task DocumentResetAsync(Document document, CancellationToken cancellationToken) { - return SpecializedTasks.EmptyTask; - } - - public override Task NewSolutionSnapshotAsync(Solution solution, CancellationToken cancellationToken) - { + // REVIEW: this should reset both active file and project state the document belong to. return SpecializedTasks.EmptyTask; } public override void RemoveDocument(DocumentId documentId) { + // TODO: do proper eventing + Owner.RaiseDiagnosticsUpdated(this, DiagnosticsUpdatedArgs.DiagnosticsRemoved( + ValueTuple.Create(this, "Syntax", documentId), Workspace, null, null, null)); + + Owner.RaiseDiagnosticsUpdated(this, DiagnosticsUpdatedArgs.DiagnosticsRemoved( + ValueTuple.Create(this, "Semantic", documentId), Workspace, null, null, null)); + Owner.RaiseDiagnosticsUpdated(this, DiagnosticsUpdatedArgs.DiagnosticsRemoved( ValueTuple.Create(this, documentId), Workspace, null, null, null)); } + public override async Task AnalyzeProjectAsync(Project project, bool semanticsChanged, CancellationToken cancellationToken) + { + if (!FullAnalysisEnabled(project.Solution.Workspace, project.Language)) + { + // TODO: check whether there is existing state, if so, raise events to remove them all. + return; + } + + // TODO: make this to use cache. + // TODO: use CompilerDiagnosticExecutor + var diagnostics = await GetDiagnosticsAsync(project.Solution, project.Id, null, includeSuppressedDiagnostics: true, cancellationToken: cancellationToken).ConfigureAwait(false); + + // TODO: do proper event + RaiseEvents(project, diagnostics); + } + public override void RemoveProject(ProjectId projectId) { + // TODO: do proper event Owner.RaiseDiagnosticsUpdated(this, DiagnosticsUpdatedArgs.DiagnosticsRemoved( ValueTuple.Create(this, projectId), Workspace, null, null, null)); } + + public override Task DocumentOpenAsync(Document document, CancellationToken cancellationToken) + { + // Review: I think we don't need to care about it + return SpecializedTasks.EmptyTask; + } + + public override Task NewSolutionSnapshotAsync(Solution solution, CancellationToken cancellationToken) + { + // we don't use this one. + return SpecializedTasks.EmptyTask; + } } } diff --git a/src/Features/Core/Portable/Features.csproj b/src/Features/Core/Portable/Features.csproj index 09a94e74426cd..4b58f7874136b 100644 --- a/src/Features/Core/Portable/Features.csproj +++ b/src/Features/Core/Portable/Features.csproj @@ -180,6 +180,8 @@ + + From 2e819f9425eb0c56e28d780319c89356550adbea Mon Sep 17 00:00:00 2001 From: Heejae Chang Date: Mon, 21 Mar 2016 15:40:12 -0700 Subject: [PATCH 03/24] made solution to build after merge --- .../EngineV2/DiagnosticIncrementalAnalyzer.cs | 6 ++---- ...osticIncrementalAnalyzer_IncrementalAnalyzer.cs | 14 ++++++-------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs index d49c3e1e1ffea..2a441b3019446 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs @@ -91,14 +91,12 @@ private void RaiseEvents(Project project, ImmutableArray diagnos { if (kv.Key == null) { - Owner.RaiseDiagnosticsUpdated( - this, DiagnosticsUpdatedArgs.DiagnosticsCreated( + Owner.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs.DiagnosticsCreated( ValueTuple.Create(this, project.Id), workspace, solution, project.Id, null, kv.ToImmutableArrayOrEmpty())); continue; } - Owner.RaiseDiagnosticsUpdated( - this, DiagnosticsUpdatedArgs.DiagnosticsCreated( + Owner.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs.DiagnosticsCreated( ValueTuple.Create(this, kv.Key), workspace, solution, project.Id, kv.Key, kv.ToImmutableArrayOrEmpty())); } } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs index b1556d8084030..4ab5d95895d67 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs @@ -45,8 +45,7 @@ public async override Task AnalyzeSyntaxAsync(Document document, CancellationTok var diagnosticData = GetDiagnosticData(document.Project, diagnostics).Where(d => d.DocumentId == document.Id); // TODO: update using right arguments - Owner.RaiseDiagnosticsUpdated( - this, DiagnosticsUpdatedArgs.DiagnosticsCreated( + Owner.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs.DiagnosticsCreated( ValueTuple.Create(this, "Syntax", document.Id), document.Project.Solution.Workspace, document.Project.Solution, document.Project.Id, document.Id, diagnosticData.ToImmutableArrayOrEmpty())); } } @@ -82,8 +81,7 @@ public override async Task AnalyzeDocumentAsync(Document document, SyntaxNode bo var diagnosticData = GetDiagnosticData(document.Project, diagnostics).Where(d => d.DocumentId == document.Id); // TODO: update using right arguments - Owner.RaiseDiagnosticsUpdated( - this, DiagnosticsUpdatedArgs.DiagnosticsCreated( + Owner.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs.DiagnosticsCreated( ValueTuple.Create(this, "Semantic", document.Id), document.Project.Solution.Workspace, document.Project.Solution, document.Project.Id, document.Id, diagnosticData.ToImmutableArrayOrEmpty())); } } @@ -104,13 +102,13 @@ public override Task DocumentResetAsync(Document document, CancellationToken can public override void RemoveDocument(DocumentId documentId) { // TODO: do proper eventing - Owner.RaiseDiagnosticsUpdated(this, DiagnosticsUpdatedArgs.DiagnosticsRemoved( + Owner.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs.DiagnosticsRemoved( ValueTuple.Create(this, "Syntax", documentId), Workspace, null, null, null)); - Owner.RaiseDiagnosticsUpdated(this, DiagnosticsUpdatedArgs.DiagnosticsRemoved( + Owner.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs.DiagnosticsRemoved( ValueTuple.Create(this, "Semantic", documentId), Workspace, null, null, null)); - Owner.RaiseDiagnosticsUpdated(this, DiagnosticsUpdatedArgs.DiagnosticsRemoved( + Owner.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs.DiagnosticsRemoved( ValueTuple.Create(this, documentId), Workspace, null, null, null)); } @@ -133,7 +131,7 @@ public override async Task AnalyzeProjectAsync(Project project, bool semanticsCh public override void RemoveProject(ProjectId projectId) { // TODO: do proper event - Owner.RaiseDiagnosticsUpdated(this, DiagnosticsUpdatedArgs.DiagnosticsRemoved( + Owner.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs.DiagnosticsRemoved( ValueTuple.Create(this, projectId), Workspace, null, null, null)); } From c632be8ea39b1b512b827775fdbbf8075ccbd1ae Mon Sep 17 00:00:00 2001 From: Heejae Chang Date: Wed, 23 Mar 2016 13:42:25 -0700 Subject: [PATCH 04/24] made solution to compile without v1 engine. --- .../DiagnosticAnalyzerCategory.cs | 0 ...agnosticIncrementalAnalyzer.NestedTypes.cs | 25 ++------------ .../EngineV2/DiagnosticIncrementalAnalyzer.cs | 2 ++ .../Diagnostics/LiveDiagnosticUpdateArgsId.cs | 34 +++++++++++++++++++ src/Features/Core/Portable/Features.csproj | 34 ++++++++++--------- ...DiagnosticListTable.LiveTableDataSource.cs | 6 ++-- .../VisualStudioBaseDiagnosticListTable.cs | 10 +++--- 7 files changed, 65 insertions(+), 46 deletions(-) rename src/Features/Core/Portable/Diagnostics/{EngineV1 => }/DiagnosticAnalyzerCategory.cs (100%) create mode 100644 src/Features/Core/Portable/Diagnostics/LiveDiagnosticUpdateArgsId.cs diff --git a/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticAnalyzerCategory.cs b/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerCategory.cs similarity index 100% rename from src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticAnalyzerCategory.cs rename to src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerCategory.cs diff --git a/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.NestedTypes.cs b/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.NestedTypes.cs index dbec975cdf9ee..4e7508b38ad0b 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.NestedTypes.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.NestedTypes.cs @@ -102,32 +102,13 @@ public override string BuildTool } } - public class ArgumentKey : AnalyzerUpdateArgsId + public class ArgumentKey : LiveDiagnosticUpdateArgsId { - public readonly StateType StateType; - public readonly object Key; - - public ArgumentKey(DiagnosticAnalyzer analyzer, StateType stateType, object key) : base(analyzer) + public ArgumentKey(DiagnosticAnalyzer analyzer, StateType stateType, object key) : base(analyzer, key, (int)stateType) { - StateType = stateType; - Key = key; } - public override bool Equals(object obj) - { - var other = obj as ArgumentKey; - if (other == null) - { - return false; - } - - return StateType == other.StateType && Equals(Key, other.Key) && base.Equals(obj); - } - - public override int GetHashCode() - { - return Hash.Combine(Key, Hash.Combine((int)StateType, base.GetHashCode())); - } + public StateType StateType => (StateType)Kind; } } } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs index 2a441b3019446..50bbf3ab086d5 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -7,6 +8,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Options; +using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 diff --git a/src/Features/Core/Portable/Diagnostics/LiveDiagnosticUpdateArgsId.cs b/src/Features/Core/Portable/Diagnostics/LiveDiagnosticUpdateArgsId.cs new file mode 100644 index 0000000000000..bfb512a7f3441 --- /dev/null +++ b/src/Features/Core/Portable/Diagnostics/LiveDiagnosticUpdateArgsId.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Diagnostics +{ + internal class LiveDiagnosticUpdateArgsId : AnalyzerUpdateArgsId + { + public readonly object Key; + public readonly int Kind; + + public LiveDiagnosticUpdateArgsId(DiagnosticAnalyzer analyzer, object key, int kind) : base(analyzer) + { + Key = key; + Kind = kind; + } + + public override bool Equals(object obj) + { + var other = obj as LiveDiagnosticUpdateArgsId; + if (other == null) + { + return false; + } + + return Kind == other.Kind && Equals(Key, other.Key) && base.Equals(obj); + } + + public override int GetHashCode() + { + return Hash.Combine(Key, Hash.Combine(Kind, base.GetHashCode())); + } + } +} diff --git a/src/Features/Core/Portable/Features.csproj b/src/Features/Core/Portable/Features.csproj index 5ef6049b915ba..4329b0d062201 100644 --- a/src/Features/Core/Portable/Features.csproj +++ b/src/Features/Core/Portable/Features.csproj @@ -182,9 +182,26 @@ + + + + + + + + + + + + + + + + + @@ -197,13 +214,6 @@ - - - - - - - @@ -218,10 +228,6 @@ - - - - @@ -283,11 +289,6 @@ - - - - - @@ -577,6 +578,7 @@ + diff --git a/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioBaseDiagnosticListTable.LiveTableDataSource.cs b/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioBaseDiagnosticListTable.LiveTableDataSource.cs index e6d4be18053f4..3f2310c091a50 100644 --- a/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioBaseDiagnosticListTable.LiveTableDataSource.cs +++ b/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioBaseDiagnosticListTable.LiveTableDataSource.cs @@ -123,14 +123,14 @@ private object CreateAggregationKey(object data) return GetItemKey(data); } - var argumentKey = args.Id as DiagnosticIncrementalAnalyzer.ArgumentKey; - if (argumentKey == null) + var liveArgsId = args.Id as LiveDiagnosticUpdateArgsId; + if (liveArgsId == null) { return GetItemKey(data); } var documents = args.Solution.GetRelatedDocumentIds(args.DocumentId); - return new AggregatedKey(documents, argumentKey.Analyzer, argumentKey.StateType); + return new AggregatedKey(documents, liveArgsId.Analyzer, liveArgsId.Kind); } private void PopulateInitialData(Workspace workspace, IDiagnosticService diagnosticService) diff --git a/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioBaseDiagnosticListTable.cs b/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioBaseDiagnosticListTable.cs index 13f534a75e5c5..6fbe30e97d62f 100644 --- a/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioBaseDiagnosticListTable.cs +++ b/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioBaseDiagnosticListTable.cs @@ -104,13 +104,13 @@ protected class AggregatedKey { public readonly ImmutableArray DocumentIds; public readonly DiagnosticAnalyzer Analyzer; - public readonly DiagnosticIncrementalAnalyzer.StateType StateType; + public readonly int Kind; - public AggregatedKey(ImmutableArray documentIds, DiagnosticAnalyzer analyzer, DiagnosticIncrementalAnalyzer.StateType stateType) + public AggregatedKey(ImmutableArray documentIds, DiagnosticAnalyzer analyzer, int kind) { DocumentIds = documentIds; Analyzer = analyzer; - StateType = stateType; + Kind = kind; } public override bool Equals(object obj) @@ -121,12 +121,12 @@ public override bool Equals(object obj) return false; } - return this.DocumentIds == other.DocumentIds && this.Analyzer == other.Analyzer && this.StateType == other.StateType; + return this.DocumentIds == other.DocumentIds && this.Analyzer == other.Analyzer && this.Kind == other.Kind; } public override int GetHashCode() { - return Hash.Combine(Analyzer.GetHashCode(), Hash.Combine(DocumentIds.GetHashCode(), (int)StateType)); + return Hash.Combine(Analyzer.GetHashCode(), Hash.Combine(DocumentIds.GetHashCode(), Kind)); } } } From 3cd50f0c3c7695f67f84638e1bf8e717879a6871 Mon Sep 17 00:00:00 2001 From: Heejae Chang Date: Wed, 23 Mar 2016 14:01:30 -0700 Subject: [PATCH 05/24] made argsId to be shared between engine v1 and v2 --- ...agnosticIncrementalAnalyzer.NestedTypes.cs | 31 ------------------- .../EngineV1/DiagnosticIncrementalAnalyzer.cs | 11 ++----- ...osticIncrementalAnalyzer_GetDiagnostics.cs | 18 +++++------ .../Diagnostics/LiveDiagnosticUpdateArgsId.cs | 14 ++++++++- 4 files changed, 24 insertions(+), 50 deletions(-) diff --git a/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.NestedTypes.cs b/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.NestedTypes.cs index 4e7508b38ad0b..45fc27292bc78 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.NestedTypes.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.NestedTypes.cs @@ -1,9 +1,6 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using System.Collections.Immutable; -using System.Linq; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics.EngineV1 { @@ -82,33 +79,5 @@ public VersionArgument(VersionStamp textVersion, VersionStamp dataVersion, Versi this.ProjectVersion = projectVersion; } } - - public class HostAnalyzerKey : ArgumentKey - { - private readonly string _analyzerPackageName; - - public HostAnalyzerKey(DiagnosticAnalyzer analyzer, StateType stateType, object key, string analyzerPackageName) : - base(analyzer, stateType, key) - { - _analyzerPackageName = analyzerPackageName; - } - - public override string BuildTool - { - get - { - return _analyzerPackageName; - } - } - } - - public class ArgumentKey : LiveDiagnosticUpdateArgsId - { - public ArgumentKey(DiagnosticAnalyzer analyzer, StateType stateType, object key) : base(analyzer, key, (int)stateType) - { - } - - public StateType StateType => (StateType)Kind; - } } } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.cs index 6eec24da3f914..f0c3a6c07fed8 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.cs @@ -823,7 +823,7 @@ private void RaiseDiagnosticsCreated( StateType type, object key, StateSet stateSet, SolutionArgument solution, ImmutableArray diagnostics, Action raiseEvents) { // get right arg id for the given analyzer - var id = CreateArgumentKey(type, key, stateSet); + var id = new LiveDiagnosticUpdateArgsId(stateSet.Analyzer, key, (int)type, stateSet.ErrorSourceName); raiseEvents(DiagnosticsUpdatedArgs.DiagnosticsCreated(id, Workspace, solution.Solution, solution.ProjectId, solution.DocumentId, diagnostics)); } @@ -837,7 +837,7 @@ private void RaiseDiagnosticsRemoved( StateType type, object key, StateSet stateSet, SolutionArgument solution, Action raiseEvents) { // get right arg id for the given analyzer - var id = CreateArgumentKey(type, key, stateSet); + var id = new LiveDiagnosticUpdateArgsId(stateSet.Analyzer, key, (int)type, stateSet.ErrorSourceName); raiseEvents(DiagnosticsUpdatedArgs.DiagnosticsRemoved(id, Workspace, solution.Solution, solution.ProjectId, solution.DocumentId)); } @@ -867,13 +867,6 @@ private void RaiseProjectDiagnosticsRemoved(Project project, IEnumerable UpdateDocumentDiagnostics( AnalysisData existingData, ImmutableArray range, ImmutableArray memberDiagnostics, SyntaxTree tree, SyntaxNode member, int memberId) diff --git a/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs b/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs index dd1054d5a5819..dcf6738dab878 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs @@ -295,7 +295,7 @@ public async Task> GetSpecificDiagnosticsAsync(Ca return ImmutableArray.Empty; } - var key = Id as ArgumentKey; + var key = Id as LiveDiagnosticUpdateArgsId; if (key == null) { return ImmutableArray.Empty; @@ -316,7 +316,7 @@ public async Task> GetSpecificDiagnosticsAsync(Ca return ImmutableArray.Empty; } - var state = stateSet.GetState(key.StateType); + var state = stateSet.GetState((StateType)key.Kind); if (state == null) { return ImmutableArray.Empty; @@ -584,7 +584,7 @@ public async Task> GetSpecificDiagnosticsAsync(Ca return ImmutableArray.Empty; } - var key = Id as ArgumentKey; + var key = Id as LiveDiagnosticUpdateArgsId; if (key == null) { return ImmutableArray.Empty; @@ -597,7 +597,7 @@ public async Task> GetSpecificDiagnosticsAsync(Ca return ImmutableArray.Empty; } - if (key.StateType != StateType.Project) + if (key.Kind != (int)StateType.Project) { return await GetSpecificDiagnosticsAsync(documentOrProject, key, cancellationToken).ConfigureAwait(false); } @@ -605,9 +605,9 @@ public async Task> GetSpecificDiagnosticsAsync(Ca return await GetSpecificDiagnosticsAsync(GetProject(documentOrProject), key, cancellationToken).ConfigureAwait(false); } - private async Task> GetSpecificDiagnosticsAsync(object documentOrProject, ArgumentKey key, CancellationToken cancellationToken) + private async Task> GetSpecificDiagnosticsAsync(object documentOrProject, LiveDiagnosticUpdateArgsId key, CancellationToken cancellationToken) { - var versions = await GetVersionsAsync(documentOrProject, key.StateType, cancellationToken).ConfigureAwait(false); + var versions = await GetVersionsAsync(documentOrProject, (StateType)key.Kind, cancellationToken).ConfigureAwait(false); var project = GetProject(documentOrProject); var stateSet = this.StateManager.GetOrCreateStateSet(project, key.Analyzer); @@ -617,10 +617,10 @@ private async Task> GetSpecificDiagnosticsAsync(o } var analyzers = Owner._stateManager.GetOrCreateAnalyzers(project); - var driver = await GetDiagnosticAnalyzerDriverAsync(documentOrProject, analyzers, key.StateType, cancellationToken).ConfigureAwait(false); + var driver = await GetDiagnosticAnalyzerDriverAsync(documentOrProject, analyzers, (StateType)key.Kind, cancellationToken).ConfigureAwait(false); - var analysisData = await GetDiagnosticAnalysisDataAsync(driver, stateSet, key.StateType, versions).ConfigureAwait(false); - if (key.StateType != StateType.Project) + var analysisData = await GetDiagnosticAnalysisDataAsync(driver, stateSet, (StateType)key.Kind, versions).ConfigureAwait(false); + if (key.Kind != (int)StateType.Project) { return analysisData.Items; } diff --git a/src/Features/Core/Portable/Diagnostics/LiveDiagnosticUpdateArgsId.cs b/src/Features/Core/Portable/Diagnostics/LiveDiagnosticUpdateArgsId.cs index bfb512a7f3441..150a54ddf9e6c 100644 --- a/src/Features/Core/Portable/Diagnostics/LiveDiagnosticUpdateArgsId.cs +++ b/src/Features/Core/Portable/Diagnostics/LiveDiagnosticUpdateArgsId.cs @@ -6,15 +6,27 @@ namespace Microsoft.CodeAnalysis.Diagnostics { internal class LiveDiagnosticUpdateArgsId : AnalyzerUpdateArgsId { + private readonly string _analyzerPackageName; + public readonly object Key; public readonly int Kind; - public LiveDiagnosticUpdateArgsId(DiagnosticAnalyzer analyzer, object key, int kind) : base(analyzer) + public LiveDiagnosticUpdateArgsId(DiagnosticAnalyzer analyzer, object key, int kind) : + this(analyzer, key, kind, analyzerPackageName: null) + { + } + + public LiveDiagnosticUpdateArgsId(DiagnosticAnalyzer analyzer, object key, int kind, string analyzerPackageName) : + base(analyzer) { Key = key; Kind = kind; + + _analyzerPackageName = analyzerPackageName; } + public override string BuildTool => _analyzerPackageName ?? base.BuildTool; + public override bool Equals(object obj) { var other = obj as LiveDiagnosticUpdateArgsId; From f1616137e8d69c71b32f6d03b4ffb7d05e5f1ea4 Mon Sep 17 00:00:00 2001 From: Heejae Chang Date: Mon, 28 Mar 2016 23:57:32 -0700 Subject: [PATCH 06/24] removed unused usings --- .../EngineV1/DiagnosticIncrementalAnalyzer.StateManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.cs b/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.cs index 0bbe0a1e3d3b9..3756d3202ce04 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; From 82c9de92110a90088e3e03d5c86b072a795ff4e8 Mon Sep 17 00:00:00 2001 From: Heejae Chang Date: Mon, 11 Apr 2016 04:05:13 -0700 Subject: [PATCH 07/24] full implementation of engine v2 --- .../Analyzers/IDocumentDiagnosticAnalyzer.cs | 7 +- .../Analyzers/IProjectDiagnosticAnalyzer.cs | 6 +- .../BaseDiagnosticIncrementalAnalyzer.cs | 16 +- ...ticAnalyzerService_BuildSynchronization.cs | 69 +-- ...sticAnalyzerService_IncrementalAnalyzer.cs | 9 +- .../DiagnosticAnalyzerService_UpdateSource.cs | 17 + .../EngineV1/DiagnosticAnalyzerDriver.cs | 33 +- ...gnosticIncrementalAnalyzer.StateManager.cs | 16 +- ...ncrementalAnalyzer_BuildSynchronization.cs | 51 +- ...osticIncrementalAnalyzer_GetDiagnostics.cs | 2 +- .../Diagnostics/EngineV2/AnalysisResult.cs | 100 ++++ .../EngineV2/CompilerDiagnosticExecutor.cs | 152 ++++-- .../EngineV2/DiagnosticDataSerializer.cs | 392 +++++++++++++++ ...sticIncrementalAnalyzer.ActiveFileState.cs | 62 ++- ...gnosticIncrementalAnalyzer.AnalysisData.cs | 117 +++++ ...gnosticIncrementalAnalyzer.AnalysisKind.cs | 17 + ...cIncrementalAnalyzer.CompilationManager.cs | 208 ++++++++ .../DiagnosticIncrementalAnalyzer.Executor.cs | 295 +++++++++++ ...sticIncrementalAnalyzer.InMemoryStorage.cs | 89 ++++ ...rojectAnalyzerReferenceChangedEventArgs.cs | 30 ++ ...gnosticIncrementalAnalyzer.ProjectState.cs | 283 ++++++++++- ...ementalAnalyzer.StateManager.HostStates.cs | 139 ++++++ ...ntalAnalyzer.StateManager.ProjectStates.cs | 258 ++++++++++ ...gnosticIncrementalAnalyzer.StateManager.cs | 265 ++++++++++ .../DiagnosticIncrementalAnalyzer.StateSet.cs | 203 ++++++++ .../EngineV2/DiagnosticIncrementalAnalyzer.cs | 183 +++++-- ...ncrementalAnalyzer_BuildSynchronization.cs | 185 ++++++- ...osticIncrementalAnalyzer_GetDiagnostics.cs | 462 ++++++++++++++++-- ...crementalAnalyzer_GetDiagnosticsForSpan.cs | 409 +++++++++++++++- ...IncrementalAnalyzer_IncrementalAnalyzer.cs | 429 +++++++++++++--- .../Diagnostics/LiveDiagnosticUpdateArgsId.cs | 7 +- src/Features/Core/Portable/Features.csproj | 12 + .../ExternalErrorDiagnosticUpdateSource.cs | 151 ++---- 33 files changed, 4174 insertions(+), 500 deletions(-) create mode 100644 src/Features/Core/Portable/Diagnostics/EngineV2/AnalysisResult.cs create mode 100644 src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticDataSerializer.cs create mode 100644 src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.AnalysisData.cs create mode 100644 src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.AnalysisKind.cs create mode 100644 src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs create mode 100644 src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs create mode 100644 src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InMemoryStorage.cs create mode 100644 src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectAnalyzerReferenceChangedEventArgs.cs create mode 100644 src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.HostStates.cs create mode 100644 src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs create mode 100644 src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs create mode 100644 src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateSet.cs diff --git a/src/Features/Core/Portable/Diagnostics/Analyzers/IDocumentDiagnosticAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/Analyzers/IDocumentDiagnosticAnalyzer.cs index a8bce6846ab56..b1e24468ded7a 100644 --- a/src/Features/Core/Portable/Diagnostics/Analyzers/IDocumentDiagnosticAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/Analyzers/IDocumentDiagnosticAnalyzer.cs @@ -3,7 +3,6 @@ using System; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.LanguageServices; namespace Microsoft.CodeAnalysis.Diagnostics { @@ -12,10 +11,14 @@ namespace Microsoft.CodeAnalysis.Diagnostics /// internal abstract class DocumentDiagnosticAnalyzer : DiagnosticAnalyzer { + // REVIEW: why DocumentDiagnosticAnalyzer doesn't have span based analysis? public abstract Task AnalyzeSyntaxAsync(Document document, Action addDiagnostic, CancellationToken cancellationToken); public abstract Task AnalyzeSemanticsAsync(Document document, Action addDiagnostic, CancellationToken cancellationToken); - public override void Initialize(AnalysisContext context) + /// + /// it is not allowed one to implement both DocumentDiagnosticAnalzyer and DiagnosticAnalyzer + /// + public sealed override void Initialize(AnalysisContext context) { } } diff --git a/src/Features/Core/Portable/Diagnostics/Analyzers/IProjectDiagnosticAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/Analyzers/IProjectDiagnosticAnalyzer.cs index 13aea173a0651..be0be072e99b5 100644 --- a/src/Features/Core/Portable/Diagnostics/Analyzers/IProjectDiagnosticAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/Analyzers/IProjectDiagnosticAnalyzer.cs @@ -3,7 +3,6 @@ using System; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.LanguageServices; namespace Microsoft.CodeAnalysis.Diagnostics { @@ -14,7 +13,10 @@ internal abstract class ProjectDiagnosticAnalyzer : DiagnosticAnalyzer { public abstract Task AnalyzeProjectAsync(Project project, Action addDiagnostic, CancellationToken cancellationToken); - public override void Initialize(AnalysisContext context) + /// + /// it is not allowed one to implement both ProjectDiagnosticAnalzyer and DiagnosticAnalyzer + /// + public sealed override void Initialize(AnalysisContext context) { } } diff --git a/src/Features/Core/Portable/Diagnostics/BaseDiagnosticIncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/BaseDiagnosticIncrementalAnalyzer.cs index c757cada1569f..d05b08dbb1112 100644 --- a/src/Features/Core/Portable/Diagnostics/BaseDiagnosticIncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/BaseDiagnosticIncrementalAnalyzer.cs @@ -199,22 +199,8 @@ protected BaseDiagnosticIncrementalAnalyzer(DiagnosticAnalyzerService owner, Wor /// It is up to each incremental analyzer how they will merge this information with live diagnostic info. /// /// this API doesn't have cancellationToken since it can't be cancelled. - /// - /// given diagnostics are project wide diagnostics that doesn't contain a source location. - /// - public abstract Task SynchronizeWithBuildAsync(DiagnosticAnalyzerService.BatchUpdateToken token, Project project, ImmutableArray diagnostics); - - /// - /// Callback from build listener. - /// - /// Given diagnostics are errors host got from explicit build. - /// It is up to each incremental analyzer how they will merge this information with live diagnostic info - /// - /// this API doesn't have cancellationToken since it can't be cancelled. - /// - /// given diagnostics are ones that has a source location. /// - public abstract Task SynchronizeWithBuildAsync(DiagnosticAnalyzerService.BatchUpdateToken token, Document document, ImmutableArray diagnostics); + public abstract Task SynchronizeWithBuildAsync(Workspace workspace, ImmutableDictionary> diagnostics); #endregion internal DiagnosticAnalyzerService Owner { get; } diff --git a/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerService_BuildSynchronization.cs b/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerService_BuildSynchronization.cs index 8b17416f83d52..429a6fee8a024 100644 --- a/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerService_BuildSynchronization.cs +++ b/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerService_BuildSynchronization.cs @@ -1,7 +1,5 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.Collections.Concurrent; using System.Collections.Immutable; using System.Threading.Tasks; using Roslyn.Utilities; @@ -10,81 +8,20 @@ namespace Microsoft.CodeAnalysis.Diagnostics { internal partial class DiagnosticAnalyzerService { - /// - /// Start new Batch build diagnostics update token. - /// - public IDisposable BeginBatchBuildDiagnosticsUpdate(Solution solution) - { - return new BatchUpdateToken(solution); - } - /// /// Synchronize build errors with live error. /// /// no cancellationToken since this can't be cancelled /// - public Task SynchronizeWithBuildAsync(IDisposable batchUpdateToken, Project project, ImmutableArray diagnostics) + public Task SynchronizeWithBuildAsync(Workspace workspace, ImmutableDictionary> diagnostics) { - var token = (BatchUpdateToken)batchUpdateToken; - token.CheckProjectInSnapshot(project); - - BaseDiagnosticIncrementalAnalyzer analyzer; - if (_map.TryGetValue(project.Solution.Workspace, out analyzer)) - { - return analyzer.SynchronizeWithBuildAsync(token, project, diagnostics); - } - - return SpecializedTasks.EmptyTask; - } - - /// - /// Synchronize build errors with live error - /// - /// no cancellationToken since this can't be cancelled - /// - public Task SynchronizeWithBuildAsync(IDisposable batchUpdateToken, Document document, ImmutableArray diagnostics) - { - var token = (BatchUpdateToken)batchUpdateToken; - token.CheckDocumentInSnapshot(document); - BaseDiagnosticIncrementalAnalyzer analyzer; - if (_map.TryGetValue(document.Project.Solution.Workspace, out analyzer)) + if (_map.TryGetValue(workspace, out analyzer)) { - return analyzer.SynchronizeWithBuildAsync(token, document, diagnostics); + return analyzer.SynchronizeWithBuildAsync(workspace, diagnostics); } return SpecializedTasks.EmptyTask; } - - public class BatchUpdateToken : IDisposable - { - public readonly ConcurrentDictionary _cache = new ConcurrentDictionary(concurrencyLevel: 2, capacity: 1); - private readonly Solution _solution; - - public BatchUpdateToken(Solution solution) - { - _solution = solution; - } - - public object GetCache(object key, Func cacheCreator) - { - return _cache.GetOrAdd(key, cacheCreator); - } - - public void CheckDocumentInSnapshot(Document document) - { - Contract.ThrowIfFalse(_solution.GetDocument(document.Id) == document); - } - - public void CheckProjectInSnapshot(Project project) - { - Contract.ThrowIfFalse(_solution.GetProject(project.Id) == project); - } - - public void Dispose() - { - _cache.Clear(); - } - } } } diff --git a/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerService_IncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerService_IncrementalAnalyzer.cs index cdd6f7767446c..eceed50ab7284 100644 --- a/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerService_IncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerService_IncrementalAnalyzer.cs @@ -180,14 +180,9 @@ public override bool ContainsDiagnostics(Workspace workspace, ProjectId projectI #endregion #region build synchronization - public override Task SynchronizeWithBuildAsync(DiagnosticAnalyzerService.BatchUpdateToken token, Project project, ImmutableArray diagnostics) + public override Task SynchronizeWithBuildAsync(Workspace workspace, ImmutableDictionary> diagnostics) { - return Analyzer.SynchronizeWithBuildAsync(token, project, diagnostics); - } - - public override Task SynchronizeWithBuildAsync(DiagnosticAnalyzerService.BatchUpdateToken token, Document document, ImmutableArray diagnostics) - { - return Analyzer.SynchronizeWithBuildAsync(token, document, diagnostics); + return Analyzer.SynchronizeWithBuildAsync(workspace, diagnostics); } #endregion diff --git a/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerService_UpdateSource.cs b/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerService_UpdateSource.cs index fefc437a36b95..7c468582f4700 100644 --- a/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerService_UpdateSource.cs +++ b/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerService_UpdateSource.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Immutable; using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.Shared.TestHooks; using Roslyn.Utilities; @@ -69,6 +70,22 @@ internal void RaiseBulkDiagnosticsUpdated(Action> } } + internal void RaiseBulkDiagnosticsUpdated(Func, Task> eventActionAsync) + { + // all diagnostics events are serialized. + var ev = _eventMap.GetEventHandlers>(DiagnosticsUpdatedEventName); + if (ev.HasHandlers) + { + // we do this bulk update to reduce number of tasks (with captured data) enqueued. + // we saw some "out of memory" due to us having long list of pending tasks in memory. + // this is to reduce for such case to happen. + Action raiseEvents = args => ev.RaiseEvent(handler => handler(this, args)); + + var asyncToken = Listener.BeginAsyncOperation(nameof(RaiseDiagnosticsUpdated)); + _eventQueue.ScheduleTask(() => eventActionAsync(raiseEvents)).CompletesAsyncOperation(asyncToken); + } + } + bool IDiagnosticUpdateSource.SupportGetDiagnostics { get { return true; } } ImmutableArray IDiagnosticUpdateSource.GetDiagnostics(Workspace workspace, ProjectId projectId, DocumentId documentId, object id, bool includeSuppressedDiagnostics, CancellationToken cancellationToken) diff --git a/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticAnalyzerDriver.cs b/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticAnalyzerDriver.cs index 050859ae774ec..785c08a8129ab 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticAnalyzerDriver.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticAnalyzerDriver.cs @@ -291,12 +291,15 @@ public async Task> GetProjectDiagnosticsAsync(Diagnos using (var diagnostics = SharedPools.Default>().GetPooledObject()) { - if (_project.SupportsCompilation) + var projectAnalyzer = analyzer as ProjectDiagnosticAnalyzer; + if (projectAnalyzer != null) { - await this.GetCompilationDiagnosticsAsync(analyzer, diagnostics.Object).ConfigureAwait(false); + await this.GetProjectDiagnosticsWorkerAsync(projectAnalyzer, diagnostics.Object).ConfigureAwait(false); + return diagnostics.Object.ToImmutableArray(); } - await this.GetProjectDiagnosticsWorkerAsync(analyzer, diagnostics.Object).ConfigureAwait(false); + Contract.ThrowIfFalse(_project.SupportsCompilation); + await this.GetCompilationDiagnosticsAsync(analyzer, diagnostics.Object).ConfigureAwait(false); return diagnostics.Object.ToImmutableArray(); } @@ -307,29 +310,17 @@ public async Task> GetProjectDiagnosticsAsync(Diagnos } } - private async Task GetProjectDiagnosticsWorkerAsync(DiagnosticAnalyzer analyzer, List diagnostics) + private async Task GetProjectDiagnosticsWorkerAsync(ProjectDiagnosticAnalyzer analyzer, List diagnostics) { + try { - var projectAnalyzer = analyzer as ProjectDiagnosticAnalyzer; - if (projectAnalyzer == null) - { - return; - } - - try - { - await projectAnalyzer.AnalyzeProjectAsync(_project, diagnostics.Add, _cancellationToken).ConfigureAwait(false); - } - catch (Exception e) when (!IsCanceled(e, _cancellationToken)) - { - var compilation = await _project.GetCompilationAsync(_cancellationToken).ConfigureAwait(false); - OnAnalyzerException(e, analyzer, compilation); - } + await analyzer.AnalyzeProjectAsync(_project, diagnostics.Add, _cancellationToken).ConfigureAwait(false); } - catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) + catch (Exception e) when (!IsCanceled(e, _cancellationToken)) { - throw ExceptionUtilities.Unreachable; + var compilation = await _project.GetCompilationAsync(_cancellationToken).ConfigureAwait(false); + OnAnalyzerException(e, analyzer, compilation); } } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.cs b/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.cs index 3756d3202ce04..96ac1e9f691d3 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.cs @@ -68,16 +68,6 @@ public IEnumerable GetStateSets(Project project) return GetStateSets(project.Id).Where(s => s.Language == project.Language); } - /// - /// Return s that are added as the given 's AnalyzerReferences. - /// This will never create new but will return ones already created. - /// - public ImmutableArray GetBuildOnlyStateSets(object cache, Project project) - { - var stateSetCache = (IDictionary>)cache; - return stateSetCache.GetOrAdd(project, CreateBuildOnlyProjectStateSet); - } - /// /// Return s for the given . /// This will either return already created s for the specific snapshot of or @@ -126,7 +116,11 @@ public void RemoveStateSet(ProjectId projectId) _projectStates.RemoveStateSet(projectId); } - private ImmutableArray CreateBuildOnlyProjectStateSet(Project project) + /// + /// Return s that are added as the given 's AnalyzerReferences. + /// This will never create new but will return ones already created. + /// + public ImmutableArray CreateBuildOnlyProjectStateSet(Project project) { var referenceIdentities = project.AnalyzerReferences.Select(r => _analyzerManager.GetAnalyzerReferenceIdentity(r)).ToSet(); var stateSetMap = GetStateSets(project).ToDictionary(s => s.Analyzer, s => s); diff --git a/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer_BuildSynchronization.cs b/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer_BuildSynchronization.cs index 2bba644812bae..eeb049a7703be 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer_BuildSynchronization.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer_BuildSynchronization.cs @@ -1,7 +1,5 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Globalization; @@ -14,21 +12,42 @@ namespace Microsoft.CodeAnalysis.Diagnostics.EngineV1 { internal partial class DiagnosticIncrementalAnalyzer { - private readonly static Func s_cacheCreator = _ => new ConcurrentDictionary>(concurrencyLevel: 2, capacity: 10); - - public override async Task SynchronizeWithBuildAsync(DiagnosticAnalyzerService.BatchUpdateToken token, Project project, ImmutableArray diagnostics) + public override async Task SynchronizeWithBuildAsync(Workspace workspace, ImmutableDictionary> map) { - if (!PreferBuildErrors(project.Solution.Workspace)) + if (!PreferBuildErrors(workspace)) { // prefer live errors over build errors return; } + var solution = workspace.CurrentSolution; + foreach (var projectEntry in map) + { + var project = solution.GetProject(projectEntry.Key); + if (project == null) + { + continue; + } + + var stateSets = _stateManager.CreateBuildOnlyProjectStateSet(project); + var lookup = projectEntry.Value.ToLookup(d => d.DocumentId); + + // do project one first + await SynchronizeWithBuildAsync(project, stateSets, lookup[null]).ConfigureAwait(false); + + foreach (var document in project.Documents) + { + await SynchronizeWithBuildAsync(document, stateSets, lookup[document.Id]).ConfigureAwait(false); + } + } + } + + private async Task SynchronizeWithBuildAsync(Project project, IEnumerable stateSets, IEnumerable diagnostics) + { using (var poolObject = SharedPools.Default>().GetPooledObject()) { var lookup = CreateDiagnosticIdLookup(diagnostics); - - foreach (var stateSet in _stateManager.GetBuildOnlyStateSets(token.GetCache(_stateManager, s_cacheCreator), project)) + foreach (var stateSet in stateSets) { var descriptors = HostAnalyzerManager.GetDiagnosticDescriptors(stateSet.Analyzer); var liveDiagnostics = ConvertToLiveDiagnostics(lookup, descriptors, poolObject.Object); @@ -47,14 +66,9 @@ public override async Task SynchronizeWithBuildAsync(DiagnosticAnalyzerService.B } } - public override async Task SynchronizeWithBuildAsync(DiagnosticAnalyzerService.BatchUpdateToken token, Document document, ImmutableArray diagnostics) + private async Task SynchronizeWithBuildAsync(Document document, IEnumerable stateSets, IEnumerable diagnostics) { var workspace = document.Project.Solution.Workspace; - if (!PreferBuildErrors(workspace)) - { - // prefer live errors over build errors - return; - } // check whether, for opened documents, we want to prefer live diagnostics if (PreferLiveErrorsOnOpenedFiles(workspace) && workspace.IsDocumentOpen(document.Id)) @@ -68,7 +82,7 @@ public override async Task SynchronizeWithBuildAsync(DiagnosticAnalyzerService.B { var lookup = CreateDiagnosticIdLookup(diagnostics); - foreach (var stateSet in _stateManager.GetBuildOnlyStateSets(token.GetCache(_stateManager, s_cacheCreator), document.Project)) + foreach (var stateSet in stateSets) { // we are using Default so that things like LB can't use cached information var textVersion = VersionStamp.Default; @@ -141,13 +155,8 @@ private ImmutableArray MergeDiagnostics(ImmutableArray.Empty : builder.ToImmutable(); } - private static ILookup CreateDiagnosticIdLookup(ImmutableArray diagnostics) + private static ILookup CreateDiagnosticIdLookup(IEnumerable diagnostics) { - if (diagnostics.Length == 0) - { - return null; - } - return diagnostics.ToLookup(d => d.Id); } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs b/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs index dcf6738dab878..80f795ae0a668 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs @@ -236,7 +236,7 @@ protected void AppendDiagnostics(IEnumerable items) } } - protected virtual ImmutableArray GetDiagnosticData() + protected ImmutableArray GetDiagnosticData() { return _builder != null ? _builder.ToImmutableArray() : ImmutableArray.Empty; } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/AnalysisResult.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/AnalysisResult.cs new file mode 100644 index 0000000000000..f6cd1c6e2b6db --- /dev/null +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/AnalysisResult.cs @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 +{ + /// + /// This holds onto diagnostics for a specific version of project snapshot + /// in a way each kind of diagnostics can be queried fast. + /// + internal struct AnalysisResult + { + public readonly ProjectId ProjectId; + public readonly VersionStamp Version; + + // set of documents that has any kind of diagnostics on it + public readonly ImmutableHashSet DocumentIds; + public readonly bool IsEmpty; + + // map for each kind of diagnostics + // syntax locals and semantic locals are self explanatory. + // non locals means diagnostics that belong to a tree that are produced by analyzing other files. + // others means diagnostics that doesnt have locations. + private readonly ImmutableDictionary> _syntaxLocals; + private readonly ImmutableDictionary> _semanticLocals; + private readonly ImmutableDictionary> _nonLocals; + private readonly ImmutableArray _others; + + public AnalysisResult( + ProjectId projectId, VersionStamp version, ImmutableHashSet documentIds, bool isEmpty) + { + ProjectId = projectId; + Version = version; + DocumentIds = documentIds; + IsEmpty = isEmpty; + + _syntaxLocals = null; + _semanticLocals = null; + _nonLocals = null; + _others = default(ImmutableArray); + } + + public AnalysisResult( + ProjectId projectId, VersionStamp version, + ImmutableHashSet documentIds, + ImmutableDictionary> syntaxLocals, + ImmutableDictionary> semanticLocals, + ImmutableDictionary> nonLocals, + ImmutableArray others) + { + ProjectId = projectId; + Version = version; + DocumentIds = documentIds; + + _syntaxLocals = syntaxLocals; + _semanticLocals = semanticLocals; + _nonLocals = nonLocals; + _others = others; + + IsEmpty = DocumentIds.IsEmpty && _others.IsEmpty; + } + + // aggregated form means it has aggregated information but no actual data. + public bool IsAggregatedForm => _syntaxLocals == null; + + // this shouldn't be called for aggregated form. + public ImmutableDictionary> SyntaxLocals => ReturnIfNotDefalut(_syntaxLocals); + public ImmutableDictionary> SemanticLocals => ReturnIfNotDefalut(_semanticLocals); + public ImmutableDictionary> NonLocals => ReturnIfNotDefalut(_nonLocals); + public ImmutableArray Others => ReturnIfNotDefalut(_others); + + public ImmutableArray GetResultOrEmpty(ImmutableDictionary> map, DocumentId key) + { + // this is just a helper method. + ImmutableArray diagnostics; + if (map.TryGetValue(key, out diagnostics)) + { + return diagnostics; + } + + return ImmutableArray.Empty; + } + + public AnalysisResult ToAggregatedForm() + { + return new AnalysisResult(ProjectId, Version, DocumentIds, IsEmpty); + } + + private T ReturnIfNotDefalut(T value) + { + if (object.Equals(value, default(T))) + { + Contract.Fail("shouldn't be called"); + } + + return value; + } + } +} \ No newline at end of file diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/CompilerDiagnosticExecutor.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/CompilerDiagnosticExecutor.cs index 04acae4363269..e0222a07e02d3 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/CompilerDiagnosticExecutor.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/CompilerDiagnosticExecutor.cs @@ -14,11 +14,9 @@ namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 /// internal static class CompilerDiagnosticExecutor { - public static async Task AnalyzeAsync( - this Compilation compilation, ImmutableArray analyzers, CompilationWithAnalyzersOptions options, CancellationToken cancellationToken) + public static async Task> AnalyzeAsync(this CompilationWithAnalyzers analyzerDriver, Project project, CancellationToken cancellationToken) { - // Create driver that holds onto compilation and associated analyzers - var analyzerDriver = compilation.WithAnalyzers(analyzers, options); + var version = await DiagnosticIncrementalAnalyzer.GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false); // Run all analyzers at once. // REVIEW: why there are 2 different cancellation token? one that I can give to constructor and one I can give in to each method? @@ -28,10 +26,14 @@ public static async Task AnalyzeAsync( // this is wierd, but now we iterate through each analyzer for each tree to get cached result. // REVIEW: no better way to do this? var noSpanFilter = default(TextSpan?); + var analyzers = analyzerDriver.Analyzers; + var compilation = analyzerDriver.Compilation; - var resultMap = new AnalysisResult.ResultMap(); + var builder = ImmutableDictionary.CreateBuilder(); foreach (var analyzer in analyzers) { + var result = new Builder(project, version); + // REVIEW: more unnecessary allocations just to get diagnostics per analyzer var oneAnalyzers = ImmutableArray.Create(analyzer); @@ -39,49 +41,80 @@ public static async Task AnalyzeAsync( { var model = compilation.GetSemanticModel(tree); - resultMap.AddSyntaxDiagnostics(analyzer, tree, await analyzerDriver.GetAnalyzerSyntaxDiagnosticsAsync(tree, oneAnalyzers, cancellationToken).ConfigureAwait(false)); - resultMap.AddSemanticDiagnostics(analyzer, tree, await analyzerDriver.GetAnalyzerSemanticDiagnosticsAsync(model, noSpanFilter, oneAnalyzers, cancellationToken).ConfigureAwait(false)); + var syntax = await analyzerDriver.GetAnalyzerSyntaxDiagnosticsAsync(tree, oneAnalyzers, cancellationToken).ConfigureAwait(false); + result.AddSyntaxDiagnostics(tree, CompilationWithAnalyzers.GetEffectiveDiagnostics(syntax, compilation)); + + var semantic = await analyzerDriver.GetAnalyzerSemanticDiagnosticsAsync(model, noSpanFilter, oneAnalyzers, cancellationToken).ConfigureAwait(false); + result.AddSemanticDiagnostics(tree, CompilationWithAnalyzers.GetEffectiveDiagnostics(semantic, compilation)); } - resultMap.AddCompilationDiagnostics(analyzer, await analyzerDriver.GetAnalyzerCompilationDiagnosticsAsync(oneAnalyzers, cancellationToken).ConfigureAwait(false)); + var rest = await analyzerDriver.GetAnalyzerCompilationDiagnosticsAsync(oneAnalyzers, cancellationToken).ConfigureAwait(false); + result.AddCompilationDiagnostics(CompilationWithAnalyzers.GetEffectiveDiagnostics(rest, compilation)); + + builder.Add(analyzer, result.ToResult()); } - return new AnalysisResult(resultMap); + return builder.ToImmutable(); } - } - // REVIEW: this will probably go away once we have new API. - // now things run sequencially, so no thread-safety. - internal struct AnalysisResult - { - private readonly ResultMap resultMap; - - public AnalysisResult(ResultMap resultMap) + /// + /// We have this builder to avoid creating collections unnecessarily. + /// Expectation is that, most of time, most of analyzers doesn't have any diagnostics. so no need to actually create any objects. + /// + internal struct Builder { - this.resultMap = resultMap; - } + private readonly Project _project; + private readonly VersionStamp _version; - internal struct ResultMap - { - private Dictionary> _lazySyntaxLocals; - private Dictionary> _lazySemanticLocals; + private HashSet _lazySet; + + private Dictionary> _lazySyntaxLocals; + private Dictionary> _lazySemanticLocals; + private Dictionary> _lazyNonLocals; + + private List _lazyOthers; + + public Builder(Project project, VersionStamp version) + { + _project = project; + _version = version; + + _lazySet = null; + _lazySyntaxLocals = null; + _lazySemanticLocals = null; + _lazyNonLocals = null; + _lazyOthers = null; + } + + public AnalysisResult ToResult() + { + var documentIds = _lazySet == null ? ImmutableHashSet.Empty : _lazySet.ToImmutableHashSet(); + var syntaxLocals = Convert(_lazySyntaxLocals); + var semanticLocals = Convert(_lazySemanticLocals); + var nonLocals = Convert(_lazyNonLocals); + var others = _lazyOthers == null ? ImmutableArray.Empty : _lazyOthers.ToImmutableArray(); + + return new AnalysisResult(_project.Id, _version, documentIds, syntaxLocals, semanticLocals, nonLocals, others); + } - private Dictionary> _lazyNonLocals; - private List _lazyOthers; + private ImmutableDictionary> Convert(Dictionary> map) + { + return map == null ? ImmutableDictionary>.Empty : map.ToImmutableDictionary(kv => kv.Key, kv => kv.Value.ToImmutableArray()); + } - public void AddSyntaxDiagnostics(DiagnosticAnalyzer analyzer, SyntaxTree tree, ImmutableArray diagnostics) + public void AddSyntaxDiagnostics(SyntaxTree tree, IEnumerable diagnostics) { AddDiagnostics(ref _lazySyntaxLocals, tree, diagnostics); } - public void AddSemanticDiagnostics(DiagnosticAnalyzer analyzer, SyntaxTree tree, ImmutableArray diagnostics) + public void AddSemanticDiagnostics(SyntaxTree tree, IEnumerable diagnostics) { AddDiagnostics(ref _lazySemanticLocals, tree, diagnostics); } - public void AddCompilationDiagnostics(DiagnosticAnalyzer analyzer, ImmutableArray diagnostics) + public void AddCompilationDiagnostics(IEnumerable diagnostics) { - Dictionary> dummy = null; + Dictionary> dummy = null; AddDiagnostics(ref dummy, tree: null, diagnostics: diagnostics); // dummy should be always null @@ -89,47 +122,55 @@ public void AddCompilationDiagnostics(DiagnosticAnalyzer analyzer, ImmutableArra } private void AddDiagnostics( - ref Dictionary> _lazyLocals, SyntaxTree tree, ImmutableArray diagnostics) + ref Dictionary> lazyLocals, SyntaxTree tree, IEnumerable diagnostics) { - if (diagnostics.Length == 0) - { - return; - } - - for (var i = 0; i < diagnostics.Length; i++) + foreach (var diagnostic in diagnostics) { - var diagnostic = diagnostics[i]; - // REVIEW: what is our plan for additional locations? switch (diagnostic.Location.Kind) { - case LocationKind.None: case LocationKind.ExternalFile: { - // no location or reported to external files - _lazyOthers = _lazyOthers ?? new List(); - _lazyOthers.Add(diagnostic); + // TODO: currently additional file location is not supported. + break; + } + case LocationKind.None: + { + _lazyOthers = _lazyOthers ?? new List(); + _lazyOthers.Add(DiagnosticData.Create(_project, diagnostic)); break; } case LocationKind.SourceFile: { if (tree != null && diagnostic.Location.SourceTree == tree) { - // local diagnostics to a file - _lazyLocals = _lazyLocals ?? new Dictionary>(); - _lazyLocals.GetOrAdd(diagnostic.Location.SourceTree, _ => new List()).Add(diagnostic); + var document = GetDocument(diagnostic); + if (document != null) + { + // local diagnostics to a file + lazyLocals = lazyLocals ?? new Dictionary>(); + lazyLocals.GetOrAdd(document.Id, _ => new List()).Add(DiagnosticData.Create(document, diagnostic)); + + SetDocument(document); + } } else if (diagnostic.Location.SourceTree != null) { - // non local diagnostics to a file - _lazyNonLocals = _lazyNonLocals ?? new Dictionary>(); - _lazyNonLocals.GetOrAdd(diagnostic.Location.SourceTree, _ => new List()).Add(diagnostic); + var document = _project.GetDocument(diagnostic.Location.SourceTree); + if (document != null) + { + // non local diagnostics to a file + _lazyNonLocals = _lazyNonLocals ?? new Dictionary>(); + _lazyNonLocals.GetOrAdd(document.Id, _ => new List()).Add(DiagnosticData.Create(document, diagnostic)); + + SetDocument(document); + } } else { // non local diagnostics without location - _lazyOthers = _lazyOthers ?? new List(); - _lazyOthers.Add(diagnostic); + _lazyOthers = _lazyOthers ?? new List(); + _lazyOthers.Add(DiagnosticData.Create(_project, diagnostic)); } break; @@ -148,6 +189,17 @@ private void AddDiagnostics( } } } + + private void SetDocument(Document document) + { + _lazySet = _lazySet ?? new HashSet(); + _lazySet.Add(document.Id); + } + + private Document GetDocument(Diagnostic diagnostic) + { + return _project.GetDocument(diagnostic.Location.SourceTree); + } } } } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticDataSerializer.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticDataSerializer.cs new file mode 100644 index 0000000000000..f78c1160c9852 --- /dev/null +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticDataSerializer.cs @@ -0,0 +1,392 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 +{ + /// + /// DiagnosticData serializer + /// + internal struct DiagnosticDataSerializer + { + // version of serialized format + private const int FormatVersion = 1; + + // version of analyzer that produced this data + public readonly VersionStamp AnalyzerVersion; + + // version of project this data belong to + public readonly VersionStamp Version; + + public DiagnosticDataSerializer(VersionStamp analyzerVersion, VersionStamp version) + { + AnalyzerVersion = analyzerVersion; + Version = version; + } + + public async Task SerializeAsync(object documentOrProject, string key, ImmutableArray items, CancellationToken cancellationToken) + { + using (var stream = SerializableBytes.CreateWritableStream()) + { + WriteTo(stream, items, cancellationToken); + + var solution = GetSolution(documentOrProject); + var persistService = solution.Workspace.Services.GetService(); + + using (var storage = persistService.GetStorage(solution)) + { + stream.Position = 0; + return await WriteStreamAsync(storage, documentOrProject, key, stream, cancellationToken).ConfigureAwait(false); + } + } + } + + public async Task> DeserializeAsync(object documentOrProject, string key, CancellationToken cancellationToken) + { + // we have persisted data + var solution = GetSolution(documentOrProject); + var persistService = solution.Workspace.Services.GetService(); + + using (var storage = persistService.GetStorage(solution)) + using (var stream = await ReadStreamAsync(storage, key, documentOrProject, cancellationToken).ConfigureAwait(false)) + { + if (stream == null) + { + return default(ImmutableArray); + } + + return ReadFrom(stream, documentOrProject, cancellationToken); + } + } + + private Task WriteStreamAsync(IPersistentStorage storage, object documentOrProject, string key, Stream stream, CancellationToken cancellationToken) + { + var document = documentOrProject as Document; + if (document != null) + { + return storage.WriteStreamAsync(document, key, stream, cancellationToken); + } + + var project = (Project)documentOrProject; + return storage.WriteStreamAsync(project, key, stream, cancellationToken); + } + + private void WriteTo(Stream stream, ImmutableArray items, CancellationToken cancellationToken) + { + using (var writer = new ObjectWriter(stream, cancellationToken: cancellationToken)) + { + writer.WriteInt32(FormatVersion); + + AnalyzerVersion.WriteTo(writer); + Version.WriteTo(writer); + + writer.WriteInt32(items.Length); + + foreach (var item in items) + { + cancellationToken.ThrowIfCancellationRequested(); + + writer.WriteString(item.Id); + writer.WriteString(item.Category); + + writer.WriteString(item.Message); + writer.WriteString(item.ENUMessageForBingSearch); + writer.WriteString(item.Title); + writer.WriteString(item.Description); + writer.WriteString(item.HelpLink); + writer.WriteInt32((int)item.Severity); + writer.WriteInt32((int)item.DefaultSeverity); + writer.WriteBoolean(item.IsEnabledByDefault); + writer.WriteBoolean(item.IsSuppressed); + writer.WriteInt32(item.WarningLevel); + + if (item.HasTextSpan) + { + // document state + writer.WriteInt32(item.TextSpan.Start); + writer.WriteInt32(item.TextSpan.Length); + } + else + { + // project state + writer.WriteInt32(0); + writer.WriteInt32(0); + } + + WriteTo(writer, item.DataLocation, cancellationToken); + WriteTo(writer, item.AdditionalLocations, cancellationToken); + + writer.WriteInt32(item.CustomTags.Count); + foreach (var tag in item.CustomTags) + { + writer.WriteString(tag); + } + + writer.WriteInt32(item.Properties.Count); + foreach (var property in item.Properties) + { + writer.WriteString(property.Key); + writer.WriteString(property.Value); + } + } + } + } + + private static void WriteTo(ObjectWriter writer, IReadOnlyCollection additionalLocations, CancellationToken cancellationToken) + { + writer.WriteInt32(additionalLocations?.Count ?? 0); + if (additionalLocations != null) + { + foreach (var location in additionalLocations) + { + cancellationToken.ThrowIfCancellationRequested(); + WriteTo(writer, location, cancellationToken); + } + } + } + + private static void WriteTo(ObjectWriter writer, DiagnosticDataLocation item, CancellationToken cancellationToken) + { + if (item == null) + { + writer.WriteBoolean(false); + return; + } + else + { + writer.WriteBoolean(true); + } + + if (item.SourceSpan.HasValue) + { + writer.WriteBoolean(true); + writer.WriteInt32(item.SourceSpan.Value.Start); + writer.WriteInt32(item.SourceSpan.Value.Length); + } + else + { + writer.WriteBoolean(false); + } + + writer.WriteString(item.OriginalFilePath); + writer.WriteInt32(item.OriginalStartLine); + writer.WriteInt32(item.OriginalStartColumn); + writer.WriteInt32(item.OriginalEndLine); + writer.WriteInt32(item.OriginalEndColumn); + + writer.WriteString(item.MappedFilePath); + writer.WriteInt32(item.MappedStartLine); + writer.WriteInt32(item.MappedStartColumn); + writer.WriteInt32(item.MappedEndLine); + writer.WriteInt32(item.MappedEndColumn); + } + + private Task ReadStreamAsync(IPersistentStorage storage, string key, object documentOrProject, CancellationToken cancellationToken) + { + var document = documentOrProject as Document; + if (document != null) + { + return storage.ReadStreamAsync(document, key, cancellationToken); + } + + var project = (Project)documentOrProject; + return storage.ReadStreamAsync(project, key, cancellationToken); + } + + private ImmutableArray ReadFrom(Stream stream, object documentOrProject, CancellationToken cancellationToken) + { + var document = documentOrProject as Document; + if (document != null) + { + return ReadFrom(stream, document.Project, document, cancellationToken); + } + + var project = (Project)documentOrProject; + return ReadFrom(stream, project, null, cancellationToken); + } + + private ImmutableArray ReadFrom(Stream stream, Project project, Document document, CancellationToken cancellationToken) + { + try + { + using (var pooledObject = SharedPools.Default>().GetPooledObject()) + using (var reader = new ObjectReader(stream)) + { + var list = pooledObject.Object; + + var format = reader.ReadInt32(); + if (format != FormatVersion) + { + return default(ImmutableArray); + } + + // saved data is for same analyzer of different version of dll + var analyzerVersion = VersionStamp.ReadFrom(reader); + if (analyzerVersion != AnalyzerVersion) + { + return default(ImmutableArray); + } + + var version = VersionStamp.ReadFrom(reader); + if (version != VersionStamp.Default && version != Version) + { + return default(ImmutableArray); + } + + ReadFrom(reader, project, document, list, cancellationToken); + return list.ToImmutableArray(); + } + } + catch (Exception) + { + return default(ImmutableArray); + } + } + + private static void ReadFrom(ObjectReader reader, Project project, Document document, List list, CancellationToken cancellationToken) + { + var count = reader.ReadInt32(); + + for (var i = 0; i < count; i++) + { + cancellationToken.ThrowIfCancellationRequested(); + + var id = reader.ReadString(); + var category = reader.ReadString(); + + var message = reader.ReadString(); + var messageFormat = reader.ReadString(); + var title = reader.ReadString(); + var description = reader.ReadString(); + var helpLink = reader.ReadString(); + var severity = (DiagnosticSeverity)reader.ReadInt32(); + var defaultSeverity = (DiagnosticSeverity)reader.ReadInt32(); + var isEnabledByDefault = reader.ReadBoolean(); + var isSuppressed = reader.ReadBoolean(); + var warningLevel = reader.ReadInt32(); + + var start = reader.ReadInt32(); + var length = reader.ReadInt32(); + var textSpan = new TextSpan(start, length); + + var location = ReadLocation(project, reader, document); + var additionalLocations = ReadAdditionalLocations(project, reader); + + var customTagsCount = reader.ReadInt32(); + var customTags = GetCustomTags(reader, customTagsCount); + + var propertiesCount = reader.ReadInt32(); + var properties = GetProperties(reader, propertiesCount); + + list.Add(new DiagnosticData( + id, category, message, messageFormat, severity, defaultSeverity, isEnabledByDefault, warningLevel, customTags, properties, + project.Solution.Workspace, project.Id, location, additionalLocations, + title: title, + description: description, + helpLink: helpLink, + isSuppressed: isSuppressed)); + } + } + + private static DiagnosticDataLocation ReadLocation(Project project, ObjectReader reader, Document documentOpt) + { + var exists = reader.ReadBoolean(); + if (!exists) + { + return null; + } + + TextSpan? sourceSpan = null; + if (reader.ReadBoolean()) + { + sourceSpan = new TextSpan(reader.ReadInt32(), reader.ReadInt32()); + } + + var originalFile = reader.ReadString(); + var originalStartLine = reader.ReadInt32(); + var originalStartColumn = reader.ReadInt32(); + var originalEndLine = reader.ReadInt32(); + var originalEndColumn = reader.ReadInt32(); + + var mappedFile = reader.ReadString(); + var mappedStartLine = reader.ReadInt32(); + var mappedStartColumn = reader.ReadInt32(); + var mappedEndLine = reader.ReadInt32(); + var mappedEndColumn = reader.ReadInt32(); + + var documentId = documentOpt != null + ? documentOpt.Id + : project.Documents.FirstOrDefault(d => d.FilePath == originalFile)?.Id; + + return new DiagnosticDataLocation(documentId, sourceSpan, + originalFile, originalStartLine, originalStartColumn, originalEndLine, originalEndColumn, + mappedFile, mappedStartLine, mappedStartColumn, mappedEndLine, mappedEndColumn); + } + + private static IReadOnlyCollection ReadAdditionalLocations(Project project, ObjectReader reader) + { + var count = reader.ReadInt32(); + var result = new List(); + for (var i = 0; i < count; i++) + { + result.Add(ReadLocation(project, reader, documentOpt: null)); + } + + return result; + } + + private static ImmutableDictionary GetProperties(ObjectReader reader, int count) + { + if (count > 0) + { + var properties = ImmutableDictionary.CreateBuilder(); + for (var i = 0; i < count; i++) + { + properties.Add(reader.ReadString(), reader.ReadString()); + } + + return properties.ToImmutable(); + } + + return ImmutableDictionary.Empty; + } + + private static IReadOnlyList GetCustomTags(ObjectReader reader, int count) + { + if (count > 0) + { + var tags = new List(count); + for (var i = 0; i < count; i++) + { + tags.Add(reader.ReadString()); + } + + return new ReadOnlyCollection(tags); + } + + return SpecializedCollections.EmptyReadOnlyList(); + } + + private static Solution GetSolution(object documentOrProject) + { + var document = documentOrProject as Document; + if (document != null) + { + return document.Project.Solution; + } + + var project = (Project)documentOrProject; + return project.Solution; + } + } +} diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ActiveFileState.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ActiveFileState.cs index c8ef7646ab638..d20fdceba5324 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ActiveFileState.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ActiveFileState.cs @@ -1,23 +1,65 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 { - internal partial class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncrementalAnalyzer + internal partial class DiagnosticIncrementalAnalyzer { - // TODO: implement active file state - // this should hold onto local syntax/semantic diagnostics for active file in memory. - // this should also hold onto CompilationWithAnalyzer last time used. - // this should use syntax/semantic version for its version + /// + /// state that is responsible to hold onto local diagnostics data regarding active/opened files (depends on host) + /// in memory. + /// private class ActiveFileState { + // file state this is for + public readonly DocumentId DocumentId; + // analysis data for each kind + private DocumentAnalysisData _syntax = DocumentAnalysisData.Empty; + private DocumentAnalysisData _semantic = DocumentAnalysisData.Empty; + + public ActiveFileState(DocumentId documentId) + { + DocumentId = documentId; + } + + public bool IsEmpty => _syntax.Items.IsEmpty && _semantic.Items.IsEmpty; + + public DocumentAnalysisData GetAnalysisData(AnalysisKind kind) + { + switch (kind) + { + case AnalysisKind.Syntax: + return _syntax; + + case AnalysisKind.Semantic: + return _semantic; + + default: + return Contract.FailWithReturn("Shouldn't reach here"); + } + } + + public void Save(AnalysisKind kind, DocumentAnalysisData data) + { + Contract.ThrowIfFalse(data.OldItems.IsDefault); + + switch (kind) + { + case AnalysisKind.Syntax: + _syntax = data; + return; + + case AnalysisKind.Semantic: + _semantic = data; + return; + + default: + Contract.Fail("Shouldn't reach here"); + return; + } + } } } } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.AnalysisData.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.AnalysisData.cs new file mode 100644 index 0000000000000..db144799e240f --- /dev/null +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.AnalysisData.cs @@ -0,0 +1,117 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 +{ + internal partial class DiagnosticIncrementalAnalyzer + { + /// + /// + /// + private struct DocumentAnalysisData + { + public static readonly DocumentAnalysisData Empty = new DocumentAnalysisData(VersionStamp.Default, ImmutableArray.Empty); + + public readonly VersionStamp Version; + public readonly ImmutableArray OldItems; + public readonly ImmutableArray Items; + + public DocumentAnalysisData(VersionStamp version, ImmutableArray items) + { + this.Version = version; + this.Items = items; + } + + public DocumentAnalysisData(VersionStamp version, ImmutableArray oldItems, ImmutableArray newItems) : + this(version, newItems) + { + this.OldItems = oldItems; + } + + public DocumentAnalysisData ToPersistData() + { + return new DocumentAnalysisData(Version, Items); + } + + public bool FromCache + { + get { return this.OldItems.IsDefault; } + } + } + + private struct ProjectAnalysisData + { + public static readonly ProjectAnalysisData Empty = new ProjectAnalysisData( + VersionStamp.Default, ImmutableDictionary.Empty, ImmutableDictionary.Empty); + + public readonly VersionStamp Version; + public readonly ImmutableDictionary OldResult; + public readonly ImmutableDictionary Result; + + + public ProjectAnalysisData(VersionStamp version, ImmutableDictionary result) + { + this.Version = version; + this.Result = result; + + this.OldResult = null; + } + + public ProjectAnalysisData( + VersionStamp version, + ImmutableDictionary oldResult, + ImmutableDictionary newResult) : + this(version, newResult) + { + this.OldResult = oldResult; + } + + public AnalysisResult GetResult(DiagnosticAnalyzer analyzer) + { + return IDictionaryExtensions.GetValueOrDefault(Result, analyzer); + } + + public bool FromCache + { + get { return this.OldResult == null; } + } + + public static async Task CreateAsync(Project project, IEnumerable stateSets, bool avoidLoadingData, CancellationToken cancellationToken) + { + VersionStamp? version = null; + + var builder = ImmutableDictionary.CreateBuilder(); + foreach (var stateSet in stateSets) + { + var state = stateSet.GetProjectState(project.Id); + var result = await state.GetAnalysisDataAsync(project, avoidLoadingData, cancellationToken).ConfigureAwait(false); + + if (!version.HasValue) + { + version = result.Version; + } + else + { + // all version must be same. + Contract.ThrowIfFalse(version == result.Version); + } + + builder.Add(stateSet.Analyzer, result); + } + + if (!version.HasValue) + { + // there is no saved data to return. + return ProjectAnalysisData.Empty; + } + + return new ProjectAnalysisData(version.Value, builder.ToImmutable()); + } + } + } +} \ No newline at end of file diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.AnalysisKind.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.AnalysisKind.cs new file mode 100644 index 0000000000000..1a90fb58d5169 --- /dev/null +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.AnalysisKind.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 +{ + internal partial class DiagnosticIncrementalAnalyzer + { + /// + /// enum for each analysis kind. + /// + private enum AnalysisKind + { + Syntax, + Semantic, + NonLocal + } + } +} diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs new file mode 100644 index 0000000000000..254ed34f2fefd --- /dev/null +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs @@ -0,0 +1,208 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.ErrorReporting; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 +{ + internal partial class DiagnosticIncrementalAnalyzer + { + /// + /// This cache CompilationWithAnalyzer for active/open files. + /// This will aggressively let go cached compilationWithAnalyzers to not hold them into memory too long. + /// + private class CompilationManager + { + private readonly DiagnosticIncrementalAnalyzer _owner; + private ConditionalWeakTable _map; + + public CompilationManager(DiagnosticIncrementalAnalyzer owner) + { + _owner = owner; + _map = new ConditionalWeakTable(); + } + + /// + /// Return CompilationWithAnalyzer for given project with given stateSets + /// + public async Task GetAnalyzerDriverAsync(Project project, IEnumerable stateSets, CancellationToken cancellationToken) + { + Contract.ThrowIfFalse(project.SupportsCompilation); + + CompilationWithAnalyzers analyzerDriver; + if (_map.TryGetValue(project, out analyzerDriver)) + { + // we have cached one, return that. + AssertAnalyzers(analyzerDriver, stateSets); + return analyzerDriver; + } + + // Create driver that holds onto compilation and associated analyzers + var concurrentAnalysis = false; + var includeSuppressedDiagnostics = true; + var newAnalyzerDriver = await CreateAnalyzerDriverAsync(project, stateSets, concurrentAnalysis, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); + + // Add new analyzer driver to the map + analyzerDriver = _map.GetValue(project, _ => newAnalyzerDriver); + + // if somebody has beat us, make sure analyzers are good. + if (analyzerDriver != newAnalyzerDriver) + { + AssertAnalyzers(analyzerDriver, stateSets); + } + + // return driver + return analyzerDriver; + } + + public Task CreateAnalyzerDriverAsync( + Project project, IEnumerable stateSets, bool concurrentAnalysis, bool includeSuppressedDiagnostics, CancellationToken cancellationToken) + { + var analyzers = stateSets.Select(s => s.Analyzer).ToImmutableArrayOrEmpty(); + return CreateAnalyzerDriverAsync(project, analyzers, concurrentAnalysis, includeSuppressedDiagnostics, cancellationToken); + } + + public Task CreateAnalyzerDriverAsync( + Project project, ImmutableArray analyzers, bool includeSuppressedDiagnostics, CancellationToken cancellationToken) + { + var concurrentAnalysis = false; + return CreateAnalyzerDriverAsync(project, analyzers, concurrentAnalysis, includeSuppressedDiagnostics, cancellationToken); + } + + public async Task CreateAnalyzerDriverAsync( + Project project, ImmutableArray analyzers, bool concurrentAnalysis, bool includeSuppressedDiagnostics, CancellationToken cancellationToken) + { + Contract.ThrowIfFalse(project.SupportsCompilation); + + var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + + // Create driver that holds onto compilation and associated analyzers + return CreateAnalyzerDriver( + project, compilation, analyzers, concurrentAnalysis: concurrentAnalysis, logAnalyzerExecutionTime: false, reportSuppressedDiagnostics: includeSuppressedDiagnostics); + } + + public CompilationWithAnalyzers CreateAnalyzerDriver( + Project project, + Compilation compilation, + ImmutableArray analyzers, + bool concurrentAnalysis, + bool logAnalyzerExecutionTime, + bool reportSuppressedDiagnostics) + { + Contract.ThrowIfFalse(project.SupportsCompilation); + AssertCompilation(project, compilation); + + var analysisOptions = GetAnalyzerOptions(project, concurrentAnalysis, logAnalyzerExecutionTime, reportSuppressedDiagnostics); + + // Create driver that holds onto compilation and associated analyzers + return compilation.WithAnalyzers(analyzers, analysisOptions); + } + + private CompilationWithAnalyzersOptions GetAnalyzerOptions( + Project project, + bool concurrentAnalysis, + bool logAnalyzerExecutionTime, + bool reportSuppressedDiagnostics) + { + return new CompilationWithAnalyzersOptions( + options: new WorkspaceAnalyzerOptions(project.AnalyzerOptions, project.Solution.Workspace), + onAnalyzerException: GetOnAnalyzerException(project.Id), + analyzerExceptionFilter: GetAnalyzerExceptionFilter(project), + concurrentAnalysis: concurrentAnalysis, + logAnalyzerExecutionTime: logAnalyzerExecutionTime, + reportSuppressedDiagnostics: reportSuppressedDiagnostics); + } + + private Func GetAnalyzerExceptionFilter(Project project) + { + return ex => + { + if (project.Solution.Workspace.Options.GetOption(InternalDiagnosticsOptions.CrashOnAnalyzerException)) + { + // if option is on, crash the host to get crash dump. + FatalError.ReportUnlessCanceled(ex); + } + + return true; + }; + } + + private Action GetOnAnalyzerException(ProjectId projectId) + { + return _owner.Owner.GetOnAnalyzerException(projectId, _owner.DiagnosticLogAggregator); + } + + private void ResetAnalyzerDriverMap() + { + // we basically eagarly clear the cache on some known changes + // to let CompilationWithAnalyzer go. + + // we create new conditional weak table every time, it turns out + // only way to clear ConditionalWeakTable is re-creating it. + // also, conditional weak table has a leak - https://github.com/dotnet/coreclr/issues/665 + _map = new ConditionalWeakTable(); + } + + [Conditional("DEBUG")] + private void AssertAnalyzers(CompilationWithAnalyzers analyzerDriver, IEnumerable stateSets) + { + // make sure analyzers are same. + Contract.ThrowIfFalse(analyzerDriver.Analyzers.SetEquals(stateSets.Select(s => s.Analyzer))); + } + + [Conditional("DEBUG")] + private void AssertCompilation(Project project, Compilation compilation1) + { + // given compilation must be from given project. + Compilation compilation2; + Contract.ThrowIfFalse(project.TryGetCompilation(out compilation2)); + Contract.ThrowIfFalse(compilation1 == compilation2); + } + + #region state changed + public void OnActiveDocumentChanged() + { + ResetAnalyzerDriverMap(); + } + + public void OnDocumentOpened() + { + ResetAnalyzerDriverMap(); + } + + public void OnDocumentClosed() + { + ResetAnalyzerDriverMap(); + } + + public void OnDocumentReset() + { + ResetAnalyzerDriverMap(); + } + + public void OnDocumentRemoved() + { + ResetAnalyzerDriverMap(); + } + + public void OnProjectRemoved() + { + ResetAnalyzerDriverMap(); + } + + public void OnNewSolution() + { + ResetAnalyzerDriverMap(); + } + #endregion + } + } +} diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs new file mode 100644 index 0000000000000..e67526e4765bc --- /dev/null +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs @@ -0,0 +1,295 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics.Log; +using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.Shared.Options; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 +{ + internal partial class DiagnosticIncrementalAnalyzer + { + /// + /// This is responsible for getting diagnostics for given input. + /// It either return one from cache or calculate new one. + /// + private class Executor + { + private readonly DiagnosticIncrementalAnalyzer _owner; + + public Executor(DiagnosticIncrementalAnalyzer owner) + { + _owner = owner; + } + + public IEnumerable ConvertToLocalDiagnostics(Document targetDocument, IEnumerable diagnostics, TextSpan? span = null) + { + var project = targetDocument.Project; + + foreach (var diagnostic in diagnostics) + { + var document = project.GetDocument(diagnostic.Location.SourceTree); + if (document == null || document != targetDocument) + { + continue; + } + + if (span.HasValue && !span.Value.Contains(diagnostic.Location.SourceSpan)) + { + continue; + } + + yield return DiagnosticData.Create(document, diagnostic); + } + } + + public async Task GetDocumentAnalysisDataAsync( + CompilationWithAnalyzers analyzerDriver, Document document, StateSet stateSet, AnalysisKind kind, CancellationToken cancellationToken) + { + try + { + var version = await GetDiagnosticVersionAsync(document.Project, cancellationToken).ConfigureAwait(false); + var state = stateSet.GetActiveFileState(document.Id); + var existingData = state.GetAnalysisData(kind); + + if (existingData.Version == version) + { + return existingData; + } + + // perf optimization. check whether analyzer is suppressed and avoid getting diagnostics if suppressed. + // REVIEW: IsAnalyzerSuppressed call seems can be quite expensive in certain condition. is there any other way to do this? + if (_owner.Owner.IsAnalyzerSuppressed(stateSet.Analyzer, document.Project)) + { + return new DocumentAnalysisData(version, existingData.Items, ImmutableArray.Empty); + } + + var nullFilterSpan = (TextSpan?)null; + var diagnostics = await ComputeDiagnosticsAsync(analyzerDriver, document, stateSet.Analyzer, kind, nullFilterSpan, cancellationToken).ConfigureAwait(false); + + // we only care about local diagnostics + return new DocumentAnalysisData(version, existingData.Items, diagnostics.ToImmutableArrayOrEmpty()); + } + catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) + { + throw ExceptionUtilities.Unreachable; + } + } + + public async Task GetProjectAnalysisDataAsync(CompilationWithAnalyzers analyzerDriver, Project project, IEnumerable stateSets, CancellationToken cancellationToken) + { + try + { + // PERF: we need to flip this to false when we do actual diffing. + var avoidLoadingData = true; + var version = await GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false); + var existingData = await ProjectAnalysisData.CreateAsync(project, stateSets, avoidLoadingData, cancellationToken).ConfigureAwait(false); + + if (existingData.Version == version) + { + return existingData; + } + + // perf optimization. check whether we want to analyze this project or not. + if (!await FullAnalysisEnabledAsync(project, cancellationToken).ConfigureAwait(false)) + { + return new ProjectAnalysisData(version, existingData.Result, ImmutableDictionary.Empty); + } + + var result = await ComputeDiagnosticsAsync(analyzerDriver, project, stateSets, cancellationToken).ConfigureAwait(false); + + return new ProjectAnalysisData(version, existingData.Result, result); + } + catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) + { + throw ExceptionUtilities.Unreachable; + } + } + + public async Task> ComputeDiagnosticsAsync( + CompilationWithAnalyzers analyzerDriver, Document document, DiagnosticAnalyzer analyzer, AnalysisKind kind, TextSpan? spanOpt, CancellationToken cancellationToken) + { + var documentAnalyzer = analyzer as DocumentDiagnosticAnalyzer; + if (documentAnalyzer != null) + { + var diagnostics = await ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync(document, documentAnalyzer, kind, analyzerDriver.Compilation, cancellationToken).ConfigureAwait(false); + return ConvertToLocalDiagnostics(document, diagnostics); + } + + var documentDiagnostics = await ComputeDiagnosticAnalyzerDiagnosticsAsync(analyzerDriver, document, analyzer, kind, spanOpt, cancellationToken).ConfigureAwait(false); + return ConvertToLocalDiagnostics(document, documentDiagnostics); + } + + public async Task> ComputeDiagnosticsAsync( + CompilationWithAnalyzers analyzerDriver, Project project, IEnumerable stateSets, CancellationToken cancellationToken) + { + // calculate regular diagnostic analyzers diagnostics + var result = await analyzerDriver.AnalyzeAsync(project, cancellationToken).ConfigureAwait(false); + + // record telemetry data + await UpdateAnalyzerTelemetryDataAsync(analyzerDriver, project, cancellationToken).ConfigureAwait(false); + + // check whether there is IDE specific project diagnostic analyzer + return await MergeProjectDiagnosticAnalyzerDiagnosticsAsync(project, stateSets, analyzerDriver.Compilation, result, cancellationToken).ConfigureAwait(false); + } + + private async Task> MergeProjectDiagnosticAnalyzerDiagnosticsAsync( + Project project, IEnumerable stateSets, Compilation compilation, ImmutableDictionary result, CancellationToken cancellationToken) + { + // check whether there is IDE specific project diagnostic analyzer + var projectAnalyzers = stateSets.Select(s => s.Analyzer).OfType().ToImmutableArrayOrEmpty(); + if (projectAnalyzers.Length <= 0) + { + return result; + } + + var version = await GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false); + using (var diagnostics = SharedPools.Default>().GetPooledObject()) + { + foreach (var analyzer in projectAnalyzers) + { + // reset pooled list + diagnostics.Object.Clear(); + + try + { + await analyzer.AnalyzeProjectAsync(project, diagnostics.Object.Add, cancellationToken).ConfigureAwait(false); + } + catch (Exception e) when (!IsCanceled(e, cancellationToken)) + { + OnAnalyzerException(analyzer, project.Id, compilation, e); + continue; + } + + // create result map + var builder = new CompilerDiagnosticExecutor.Builder(project, version); + builder.AddCompilationDiagnostics(diagnostics.Object); + + // merge the result to existing one. + result = result.Add(analyzer, builder.ToResult()); + } + } + + return result; + } + + private async Task> ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync( + Document document, DocumentDiagnosticAnalyzer analyzer, AnalysisKind kind, Compilation compilation, CancellationToken cancellationToken) + { + using (var pooledObject = SharedPools.Default>().GetPooledObject()) + { + var diagnostics = pooledObject.Object; + cancellationToken.ThrowIfCancellationRequested(); + + try + { + switch (kind) + { + case AnalysisKind.Syntax: + await analyzer.AnalyzeSyntaxAsync(document, diagnostics.Add, cancellationToken).ConfigureAwait(false); + return CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, compilation); + case AnalysisKind.Semantic: + await analyzer.AnalyzeSemanticsAsync(document, diagnostics.Add, cancellationToken).ConfigureAwait(false); + return CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, compilation); + default: + return Contract.FailWithReturn>("shouldn't reach here"); + } + } + catch (Exception e) when (!IsCanceled(e, cancellationToken)) + { + OnAnalyzerException(analyzer, document.Project.Id, compilation, e); + + return ImmutableArray.Empty; + } + } + } + + private async Task> ComputeDiagnosticAnalyzerDiagnosticsAsync( + CompilationWithAnalyzers analyzerDriver, Document document, DiagnosticAnalyzer analyzer, AnalysisKind kind, TextSpan? spanOpt, CancellationToken cancellationToken) + { + // quick optimization to reduce allocations. + if (!_owner.SupportAnalysisKind(analyzer, document.Project.Language, kind)) + { + return ImmutableArray.Empty; + } + + // REVIEW: more unnecessary allocations just to get diagnostics per analyzer + var oneAnalyzers = ImmutableArray.Create(analyzer); + + switch (kind) + { + case AnalysisKind.Syntax: + var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + var diagnostics = await analyzerDriver.GetAnalyzerSyntaxDiagnosticsAsync(tree, oneAnalyzers, cancellationToken).ConfigureAwait(false); + return CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, analyzerDriver.Compilation); + case AnalysisKind.Semantic: + var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + diagnostics = await analyzerDriver.GetAnalyzerSemanticDiagnosticsAsync(model, spanOpt, oneAnalyzers, cancellationToken).ConfigureAwait(false); + return CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, analyzerDriver.Compilation); + default: + return Contract.FailWithReturn>("shouldn't reach here"); + } + } + + private async Task UpdateAnalyzerTelemetryDataAsync(CompilationWithAnalyzers analyzerDriver, Project project, CancellationToken cancellationToken) + { + foreach (var analyzer in analyzerDriver.Analyzers) + { + await UpdateAnalyzerTelemetryDataAsync(analyzerDriver, analyzer, project, cancellationToken).ConfigureAwait(false); + } + } + + private async Task UpdateAnalyzerTelemetryDataAsync(CompilationWithAnalyzers analyzerDriver, DiagnosticAnalyzer analyzer, Project project, CancellationToken cancellationToken) + { + try + { + var analyzerTelemetryInfo = await analyzerDriver.GetAnalyzerTelemetryInfoAsync(analyzer, cancellationToken).ConfigureAwait(false); + DiagnosticAnalyzerLogger.UpdateAnalyzerTypeCount(analyzer, analyzerTelemetryInfo, project, _owner.DiagnosticLogAggregator); + } + catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) + { + throw ExceptionUtilities.Unreachable; + } + } + + private static async Task FullAnalysisEnabledAsync(Project project, CancellationToken cancellationToken) + { + var workspace = project.Solution.Workspace; + var language = project.Language; + + if (!workspace.Options.GetOption(ServiceFeatureOnOffOptions.ClosedFileDiagnostic, language) || + !workspace.Options.GetOption(RuntimeOptions.FullSolutionAnalysis)) + { + return false; + } + + return await project.HasSuccessfullyLoadedAsync(cancellationToken).ConfigureAwait(false); + } + + private static bool IsCanceled(Exception ex, CancellationToken cancellationToken) + { + return (ex as OperationCanceledException)?.CancellationToken == cancellationToken; + } + + private void OnAnalyzerException(DiagnosticAnalyzer analyzer, ProjectId projectId, Compilation compilation, Exception ex) + { + var exceptionDiagnostic = AnalyzerHelper.CreateAnalyzerExceptionDiagnostic(analyzer, ex); + + if (compilation != null) + { + exceptionDiagnostic = CompilationWithAnalyzers.GetEffectiveDiagnostics(ImmutableArray.Create(exceptionDiagnostic), compilation).SingleOrDefault(); + } + + var onAnalyzerException = _owner.GetOnAnalyzerException(projectId); + onAnalyzerException(ex, analyzer, exceptionDiagnostic); + } + } + } +} diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InMemoryStorage.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InMemoryStorage.cs new file mode 100644 index 0000000000000..21e3ccd9edddc --- /dev/null +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InMemoryStorage.cs @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Concurrent; +using System.Collections.Immutable; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 +{ + internal partial class DiagnosticIncrementalAnalyzer + { + private static class InMemoryStorage + { + // the reason using nested map rather than having tuple as key is so that I dont have a gigantic map + private readonly static ConcurrentDictionary> s_map = + new ConcurrentDictionary>(concurrencyLevel: 2, capacity: 10); + + public static bool TryGetValue(DiagnosticAnalyzer analyzer, object key, out CacheEntry entry) + { + AssertKey(key); + + entry = default(CacheEntry); + + ConcurrentDictionary analyzerMap; + if (!s_map.TryGetValue(analyzer, out analyzerMap) || + !analyzerMap.TryGetValue(key, out entry)) + { + return false; + } + + return true; + } + + public static void Cache(DiagnosticAnalyzer analyzer, object key, CacheEntry entry) + { + AssertKey(key); + + // add new cache entry + var analyzerMap = s_map.GetOrAdd(analyzer, _ => new ConcurrentDictionary(concurrencyLevel: 2, capacity: 10)); + analyzerMap[key] = entry; + } + + public static void Remove(DiagnosticAnalyzer analyzer, object key) + { + AssertKey(key); + + // remove the entry + ConcurrentDictionary analyzerMap; + if (!s_map.TryGetValue(analyzer, out analyzerMap)) + { + return; + } + + CacheEntry entry; + analyzerMap.TryRemove(key, out entry); + + if (analyzerMap.IsEmpty) + { + s_map.TryRemove(analyzer, out analyzerMap); + } + } + + public static void DropCache(DiagnosticAnalyzer analyzer) + { + // drop any cache related to given analyzer + ConcurrentDictionary analyzerMap; + s_map.TryRemove(analyzer, out analyzerMap); + } + + // make sure key is either documentId or projectId + private static void AssertKey(object key) + { + Contract.ThrowIfFalse(key is DocumentId || key is ProjectId); + } + } + + // in memory cache entry + private struct CacheEntry + { + public readonly VersionStamp Version; + public readonly ImmutableArray Diagnostics; + + public CacheEntry(VersionStamp version, ImmutableArray diagnostics) + { + Version = version; + Diagnostics = diagnostics; + } + } + } +} diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectAnalyzerReferenceChangedEventArgs.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectAnalyzerReferenceChangedEventArgs.cs new file mode 100644 index 0000000000000..f2fb4c375ed14 --- /dev/null +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectAnalyzerReferenceChangedEventArgs.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Immutable; + +namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 +{ + internal partial class DiagnosticIncrementalAnalyzer + { + /// + /// EventArgs for + /// + /// this event args contains information such as the has changed + /// and what has changed. + /// + private class ProjectAnalyzerReferenceChangedEventArgs : EventArgs + { + public readonly Project Project; + public readonly ImmutableArray Added; + public readonly ImmutableArray Removed; + + public ProjectAnalyzerReferenceChangedEventArgs(Project project, ImmutableArray added, ImmutableArray removed) + { + Project = project; + Added = added; + Removed = removed; + } + } + } +} diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs index f98078b30d726..0c958928ccd42 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs @@ -1,26 +1,289 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Collections.Generic; +using System; using System.Collections.Immutable; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 { - internal partial class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncrementalAnalyzer + internal partial class DiagnosticIncrementalAnalyzer { - // TODO: implement project state - // this should hold onto information similar to CompilerDiagnsoticExecutor.AnalysisResult - // this should use dependant project version as its version - // this should only cache opened file diagnostics in memory, and all diagnostics in other place. - // we might just use temporary storage rather than peristant storage. but will see. - // now we don't update individual document incrementally. - // but some data might comes from active file state. + /// + /// State for diagnostics that belong to a project at given time. + /// private class ProjectState { + // project id of this state + private readonly StateSet _owner; + // last aggregated analysis result for this project saved + private AnalysisResult _lastResult; + + public ProjectState(StateSet owner, ProjectId projectId) + { + _owner = owner; + _lastResult = new AnalysisResult(projectId, VersionStamp.Default, ImmutableHashSet.Empty, isEmpty: true); + } + + public ImmutableHashSet GetDocumentsWithDiagnostics() + { + return _lastResult.DocumentIds; + } + + public bool IsEmpty() + { + return _lastResult.IsEmpty; + } + + public bool IsEmpty(DocumentId documentId) + { + return !_lastResult.DocumentIds.Contains(documentId); + } + + public async Task GetAnalysisDataAsync(Project project, bool avoidLoadingData, CancellationToken cancellationToken) + { + // make a copy of last result. + var lastResult = _lastResult; + + var version = await GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false); + var versionToLoad = GetVersionToLoad(lastResult.Version, version); + + // PERF: avoid loading data if version is not right one. + // avoid loading data flag is there as a strickly perf optimization. + if (avoidLoadingData && versionToLoad != version) + { + return lastResult; + } + + // loading data can be cancelled any time. + var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, versionToLoad); + var builder = new Builder(project.Id, versionToLoad, lastResult.DocumentIds); + + foreach (var documentId in lastResult.DocumentIds) + { + var document = project.GetDocument(documentId); + if (document == null) + { + return lastResult; + } + + if (!await TryDeserializeDocumentAsync(serializer, document, builder, cancellationToken).ConfigureAwait(false)) + { + return lastResult; + } + } + + if (!await TryDeserializeAsync(serializer, project, project.Id, _owner.NonLocalStateName, builder.AddOthers, cancellationToken).ConfigureAwait(false)) + { + return lastResult; + } + + return builder.ToResult(); + } + + public async Task GetAnalysisDataAsync(Document document, bool avoidLoadingData, CancellationToken cancellationToken) + { + // make a copy of last result. + var lastResult = _lastResult; + + var version = await GetDiagnosticVersionAsync(document.Project, cancellationToken).ConfigureAwait(false); + var versionToLoad = GetVersionToLoad(lastResult.Version, version); + + if (avoidLoadingData && versionToLoad != version) + { + return lastResult; + } + + // loading data can be cancelled any time. + var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, versionToLoad); + var builder = new Builder(document.Project.Id, versionToLoad, lastResult.DocumentIds); + + if (!await TryDeserializeDocumentAsync(serializer, document, builder, cancellationToken).ConfigureAwait(false)) + { + return lastResult; + } + + return builder.ToResult(); + } + + public async Task GetProjectAnalysisDataAsync(Project project, bool avoidLoadingData, CancellationToken cancellationToken) + { + // make a copy of last result. + var lastResult = _lastResult; + + var version = await GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false); + var versionToLoad = GetVersionToLoad(lastResult.Version, version); + + if (avoidLoadingData && versionToLoad != version) + { + return lastResult; + } + + // loading data can be cancelled any time. + var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, versionToLoad); + var builder = new Builder(project.Id, versionToLoad, lastResult.DocumentIds); + + if (!await TryDeserializeAsync(serializer, project, project.Id, _owner.NonLocalStateName, builder.AddOthers, cancellationToken).ConfigureAwait(false)) + { + return lastResult; + } + + return builder.ToResult(); + } + + public async Task SaveAsync(Project project, AnalysisResult result) + { + // save last aggregated form of analysis result + _lastResult = result.ToAggregatedForm(); + + // serialization can't be cancelled. + var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, result.Version); + foreach (var documentId in result.DocumentIds) + { + var document = project.GetDocument(documentId); + Contract.ThrowIfNull(document); + + await SerializeAsync(serializer, document, document.Id, _owner.SyntaxStateName, GetResult(result, AnalysisKind.Syntax, document.Id)).ConfigureAwait(false); + await SerializeAsync(serializer, document, document.Id, _owner.SemanticStateName, GetResult(result, AnalysisKind.Semantic, document.Id)).ConfigureAwait(false); + await SerializeAsync(serializer, document, document.Id, _owner.NonLocalStateName, GetResult(result, AnalysisKind.NonLocal, document.Id)).ConfigureAwait(false); + } + + await SerializeAsync(serializer, project, result.ProjectId, _owner.NonLocalStateName, result.Others).ConfigureAwait(false); + } + + public bool OnDocumentRemoved(DocumentId id) + { + RemoveInMemoryCacheEntry(id); + + return !IsEmpty(id); + } + + public bool OnProjectRemoved(ProjectId id) + { + RemoveInMemoryCacheEntry(id); + + return !IsEmpty(); + } + + private async Task SerializeAsync(DiagnosticDataSerializer serializer, object documentOrProject, object key, string stateKey, ImmutableArray diagnostics) + { + // try to serialize it + if (await serializer.SerializeAsync(documentOrProject, stateKey, diagnostics, CancellationToken.None).ConfigureAwait(false)) + { + // we succeeded saving it to persistent storage. remove it from in memory cache if it exists + RemoveInMemoryCacheEntry(key); + return; + } + + // if serialization fail, hold it in the memory + InMemoryStorage.Cache(_owner.Analyzer, key, new CacheEntry(serializer.Version, diagnostics)); + } + + private async Task TryDeserializeDocumentAsync(DiagnosticDataSerializer serializer, Document document, Builder builder, CancellationToken cancellationToken) + { + return await TryDeserializeAsync(serializer, document, document.Id, _owner.SyntaxStateName, builder.AddSyntaxLocals, cancellationToken).ConfigureAwait(false) && + await TryDeserializeAsync(serializer, document, document.Id, _owner.SemanticStateName, builder.AddSemanticLocals, cancellationToken).ConfigureAwait(false) && + await TryDeserializeAsync(serializer, document, document.Id, _owner.NonLocalStateName, builder.AddNonLocals, cancellationToken).ConfigureAwait(false); + } + + private async Task TryDeserializeAsync( + DiagnosticDataSerializer serializer, + object documentOrProject, T key, string stateKey, + Action> add, + CancellationToken cancellationToken) where T : class + { + var diagnostics = await DeserializeAsync(serializer, documentOrProject, key, stateKey, cancellationToken).ConfigureAwait(false); + if (diagnostics.IsDefault) + { + return false; + } + + add(key, diagnostics); + return true; + } + + private async Task> DeserializeAsync(DiagnosticDataSerializer serializer, object documentOrProject, object key, string stateKey, CancellationToken cancellationToken) + { + // check cache first + CacheEntry entry; + if (InMemoryStorage.TryGetValue(_owner.Analyzer, key, out entry) && serializer.Version == entry.Version) + { + return entry.Diagnostics; + } + + // try to deserialize it + return await serializer.DeserializeAsync(documentOrProject, stateKey, cancellationToken).ConfigureAwait(false); + } + + private void RemoveInMemoryCacheEntry(object key) + { + // remove in memory cache if entry exist + InMemoryStorage.Remove(_owner.Analyzer, key); + } + + private static VersionStamp GetVersionToLoad(VersionStamp savedVersion, VersionStamp currentVersion) + { + // if we don't have saved version, use currrent version. + // this will let us deal with case where we want to load saved data from last vs session. + return savedVersion == VersionStamp.Default ? currentVersion : savedVersion; + } + + // we have this builder to avoid allocating collections unnecessarily. + private class Builder + { + private readonly ProjectId _projectId; + private readonly VersionStamp _version; + private readonly ImmutableHashSet _documentIds; + + private ImmutableDictionary>.Builder _syntaxLocals; + private ImmutableDictionary>.Builder _semanticLocals; + private ImmutableDictionary>.Builder _nonLocals; + private ImmutableArray _others; + + public Builder(ProjectId projectId, VersionStamp version, ImmutableHashSet documentIds) + { + _projectId = projectId; + _version = version; + _documentIds = documentIds; + } + + public void AddSyntaxLocals(DocumentId documentId, ImmutableArray diagnostics) + { + Add(ref _syntaxLocals, documentId, diagnostics); + } + + public void AddSemanticLocals(DocumentId documentId, ImmutableArray diagnostics) + { + Add(ref _semanticLocals, documentId, diagnostics); + } + + public void AddNonLocals(DocumentId documentId, ImmutableArray diagnostics) + { + Add(ref _nonLocals, documentId, diagnostics); + } + + public void AddOthers(ProjectId unused, ImmutableArray diagnostics) + { + _others = diagnostics; + } + + private void Add(ref ImmutableDictionary>.Builder locals, DocumentId documentId, ImmutableArray diagnostics) + { + locals = locals ?? ImmutableDictionary.CreateBuilder>(); + locals.Add(documentId, diagnostics); + } + + public AnalysisResult ToResult() + { + return new AnalysisResult(_projectId, _version, _documentIds, + _syntaxLocals?.ToImmutable() ?? ImmutableDictionary>.Empty, + _semanticLocals?.ToImmutable() ?? ImmutableDictionary>.Empty, + _nonLocals?.ToImmutable() ?? ImmutableDictionary>.Empty, + _others.IsDefault ? ImmutableArray.Empty : _others); + } + } } } } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.HostStates.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.HostStates.cs new file mode 100644 index 0000000000000..6189620ee1e04 --- /dev/null +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.HostStates.cs @@ -0,0 +1,139 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; + +namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 +{ + internal partial class DiagnosticIncrementalAnalyzer + { + private partial class StateManager + { + /// + /// This class is responsible for anything related to for host level s. + /// + private class HostStates + { + private readonly StateManager _owner; + + private ImmutableDictionary _stateMap; + + public HostStates(StateManager owner) + { + _owner = owner; + _stateMap = ImmutableDictionary.Empty; + } + + public IEnumerable GetStateSets() + { + return _stateMap.Values.SelectMany(v => v.GetStateSets()); + } + + public IEnumerable GetOrCreateStateSets(string language) + { + return GetAnalyzerMap(language).GetStateSets(); + } + + public IEnumerable GetAnalyzers(string language) + { + var map = GetAnalyzerMap(language); + return map.GetAnalyzers(); + } + + public StateSet GetOrCreateStateSet(string language, DiagnosticAnalyzer analyzer) + { + return GetAnalyzerMap(language).GetStateSet(analyzer); + } + + private DiagnosticAnalyzerMap GetAnalyzerMap(string language) + { + return ImmutableInterlocked.GetOrAdd(ref _stateMap, language, CreateLanguageSpecificAnalyzerMap, this); + } + + private DiagnosticAnalyzerMap CreateLanguageSpecificAnalyzerMap(string language, HostStates @this) + { + var analyzersPerReference = _owner.AnalyzerManager.GetHostDiagnosticAnalyzersPerReference(language); + + var analyzerMap = CreateAnalyzerMap(_owner.AnalyzerManager, language, analyzersPerReference.Values); + VerifyDiagnosticStates(analyzerMap.Values); + + return new DiagnosticAnalyzerMap(_owner.AnalyzerManager, language, analyzerMap); + } + + private class DiagnosticAnalyzerMap + { + private readonly DiagnosticAnalyzer _compilerAnalyzer; + private readonly StateSet _compilerStateSet; + + private readonly ImmutableDictionary _map; + + public DiagnosticAnalyzerMap(HostAnalyzerManager analyzerManager, string language, ImmutableDictionary analyzerMap) + { + // hold directly on to compiler analyzer + _compilerAnalyzer = analyzerManager.GetCompilerDiagnosticAnalyzer(language); + + // in test case, we might not have the compiler analyzer. + if (_compilerAnalyzer == null) + { + _map = analyzerMap; + return; + } + + _compilerStateSet = analyzerMap[_compilerAnalyzer]; + + // hold rest of analyzers + _map = analyzerMap.Remove(_compilerAnalyzer); + } + + public IEnumerable GetAnalyzers() + { + // always return compiler one first if it exists. + // it might not exist in test environment. + if (_compilerAnalyzer != null) + { + yield return _compilerAnalyzer; + } + + foreach (var analyzer in _map.Keys) + { + yield return analyzer; + } + } + + public IEnumerable GetStateSets() + { + // always return compiler one first if it exists. + // it might not exist in test environment. + if (_compilerAnalyzer != null) + { + yield return _compilerStateSet; + } + + // TODO: for now, this is static, but in future, we might consider making this a dynamic so that we process cheaper analyzer first. + foreach (var set in _map.Values) + { + yield return set; + } + } + + public StateSet GetStateSet(DiagnosticAnalyzer analyzer) + { + if (_compilerAnalyzer == analyzer) + { + return _compilerStateSet; + } + + StateSet set; + if (_map.TryGetValue(analyzer, out set)) + { + return set; + } + + return null; + } + } + } + } + } +} diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs new file mode 100644 index 0000000000000..652535185a8c3 --- /dev/null +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs @@ -0,0 +1,258 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 +{ + internal partial class DiagnosticIncrementalAnalyzer + { + private partial class StateManager + { + /// + /// This class is responsible for anything related to for project level s. + /// + private class ProjectStates + { + private readonly StateManager _owner; + private readonly ConcurrentDictionary _stateMap; + + public ProjectStates(StateManager owner) + { + _owner = owner; + _stateMap = new ConcurrentDictionary(concurrencyLevel: 2, capacity: 10); + } + + public IEnumerable GetStateSets(ProjectId projectId) + { + var map = GetCachedAnalyzerMap(projectId); + return map.Values; + } + + public IEnumerable GetOrCreateAnalyzers(Project project) + { + var map = GetOrCreateAnalyzerMap(project); + return map.Keys; + } + + public IEnumerable GetOrUpdateStateSets(Project project) + { + var map = GetOrUpdateAnalyzerMap(project); + return map.Values; + } + + public IEnumerable GetOrCreateStateSets(Project project) + { + var map = GetOrCreateAnalyzerMap(project); + return map.Values; + } + + public StateSet GetOrCreateStateSet(Project project, DiagnosticAnalyzer analyzer) + { + var map = GetOrCreateAnalyzerMap(project); + + StateSet set; + if (map.TryGetValue(analyzer, out set)) + { + return set; + } + + return null; + } + + public void RemoveStateSet(ProjectId projectId) + { + if (projectId == null) + { + return; + } + + Entry unused; + _stateMap.TryRemove(projectId, out unused); + } + + private ImmutableDictionary GetOrUpdateAnalyzerMap(Project project) + { + var map = GetAnalyzerMap(project); + if (map != null) + { + return map; + } + + var newAnalyzersPerReference = _owner.AnalyzerManager.CreateProjectDiagnosticAnalyzersPerReference(project); + var newMap = StateManager.CreateAnalyzerMap(_owner.AnalyzerManager, project.Language, newAnalyzersPerReference.Values); + + RaiseProjectAnalyzerReferenceChangedIfNeeded(project, newAnalyzersPerReference, newMap); + + // update cache. + // add and update is same since this method will not be called concurrently. + var entry = _stateMap.AddOrUpdate(project.Id, + _ => new Entry(project.AnalyzerReferences, newAnalyzersPerReference, newMap), (_1, _2) => new Entry(project.AnalyzerReferences, newAnalyzersPerReference, newMap)); + + VerifyDiagnosticStates(entry.AnalyzerMap.Values); + + return entry.AnalyzerMap; + } + + private ImmutableDictionary GetCachedAnalyzerMap(ProjectId projectId) + { + Entry entry; + if (_stateMap.TryGetValue(projectId, out entry)) + { + return entry.AnalyzerMap; + } + + return ImmutableDictionary.Empty; + } + + private ImmutableDictionary GetOrCreateAnalyzerMap(Project project) + { + // if we can't use cached one, we will create a new analyzer map. which is a bit of waste since + // we will create new StateSet for all analyzers. but since this only happens when project analyzer references + // are changed, I believe it is acceptable to have a bit of waste for simplicity. + return GetAnalyzerMap(project) ?? CreateAnalyzerMap(project); + } + + private ImmutableDictionary GetAnalyzerMap(Project project) + { + Entry entry; + if (_stateMap.TryGetValue(project.Id, out entry) && entry.AnalyzerReferences.Equals(project.AnalyzerReferences)) + { + return entry.AnalyzerMap; + } + + return null; + } + + private ImmutableDictionary CreateAnalyzerMap(Project project) + { + if (project.AnalyzerReferences.Count == 0) + { + return ImmutableDictionary.Empty; + } + + var analyzersPerReference = _owner.AnalyzerManager.CreateProjectDiagnosticAnalyzersPerReference(project); + if (analyzersPerReference.Count == 0) + { + return ImmutableDictionary.Empty; + } + + return StateManager.CreateAnalyzerMap(_owner.AnalyzerManager, project.Language, analyzersPerReference.Values); + } + + private void RaiseProjectAnalyzerReferenceChangedIfNeeded( + Project project, + ImmutableDictionary> newMapPerReference, + ImmutableDictionary newMap) + { + Entry entry; + if (!_stateMap.TryGetValue(project.Id, out entry)) + { + // no previous references and we still don't have any references + if (newMap.Count == 0) + { + return; + } + + // new reference added + _owner.RaiseProjectAnalyzerReferenceChanged( + new ProjectAnalyzerReferenceChangedEventArgs(project, newMap.Values.ToImmutableArrayOrEmpty(), ImmutableArray.Empty)); + return; + } + + Contract.Requires(!entry.AnalyzerReferences.Equals(project.AnalyzerReferences)); + + // there has been change. find out what has changed + var addedStates = DiffStateSets(project.AnalyzerReferences.Except(entry.AnalyzerReferences), newMapPerReference, newMap); + var removedStates = DiffStateSets(entry.AnalyzerReferences.Except(project.AnalyzerReferences), entry.MapPerReferences, entry.AnalyzerMap); + + // nothing has changed + if (addedStates.Length == 0 && removedStates.Length == 0) + { + return; + } + + _owner.RaiseProjectAnalyzerReferenceChanged( + new ProjectAnalyzerReferenceChangedEventArgs(project, addedStates, removedStates)); + } + + private ImmutableArray DiffStateSets( + IEnumerable references, + ImmutableDictionary> mapPerReference, + ImmutableDictionary map) + { + if (mapPerReference.Count == 0 || map.Count == 0) + { + // nothing to diff + return ImmutableArray.Empty; + } + + var builder = ImmutableArray.CreateBuilder(); + foreach (var reference in references) + { + var referenceIdentity = _owner.AnalyzerManager.GetAnalyzerReferenceIdentity(reference); + + // check duplication + ImmutableArray analyzers; + if (!mapPerReference.TryGetValue(referenceIdentity, out analyzers)) + { + continue; + } + + // okay, this is real reference. get stateset + foreach (var analyzer in analyzers) + { + StateSet set; + if (!map.TryGetValue(analyzer, out set)) + { + continue; + } + + builder.Add(set); + } + } + + return builder.ToImmutable(); + } + + [Conditional("DEBUG")] + private void VerifyDiagnosticStates(IEnumerable stateSets) + { + // We do not de-duplicate analyzer instances across host and project analyzers. + var projectAnalyzers = stateSets.Select(state => state.Analyzer).ToImmutableHashSet(); + + var hostStates = _owner._hostStates.GetStateSets() + .Where(state => !projectAnalyzers.Contains(state.Analyzer)); + + StateManager.VerifyDiagnosticStates(hostStates.Concat(stateSets)); + } + + private struct Entry + { + public readonly IReadOnlyList AnalyzerReferences; + public readonly ImmutableDictionary> MapPerReferences; + public readonly ImmutableDictionary AnalyzerMap; + + public Entry( + IReadOnlyList analyzerReferences, + ImmutableDictionary> mapPerReferences, + ImmutableDictionary analyzerMap) + { + Contract.ThrowIfNull(analyzerReferences); + Contract.ThrowIfNull(mapPerReferences); + Contract.ThrowIfNull(analyzerMap); + + AnalyzerReferences = analyzerReferences; + MapPerReferences = mapPerReferences; + AnalyzerMap = analyzerMap; + } + } + } + } + } +} diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs new file mode 100644 index 0000000000000..5149a6a0e851e --- /dev/null +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs @@ -0,0 +1,265 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 +{ + internal partial class DiagnosticIncrementalAnalyzer + { + private const string RoslynLanguageServices = "Roslyn Language Services"; + + /// + /// This is in charge of anything related to + /// + private partial class StateManager + { + private readonly HostAnalyzerManager _analyzerManager; + + private readonly HostStates _hostStates; + private readonly ProjectStates _projectStates; + + public StateManager(HostAnalyzerManager analyzerManager) + { + _analyzerManager = analyzerManager; + + _hostStates = new HostStates(this); + _projectStates = new ProjectStates(this); + } + + private HostAnalyzerManager AnalyzerManager { get { return _analyzerManager; } } + + /// + /// This will be raised whenever finds change + /// + public event EventHandler ProjectAnalyzerReferenceChanged; + + /// + /// Return existing or new s for the given . + /// + public IEnumerable GetOrCreateAnalyzers(Project project) + { + return _hostStates.GetAnalyzers(project.Language).Concat(_projectStates.GetOrCreateAnalyzers(project)); + } + + /// + /// Return s for the given . + /// This will never create new but will return ones already created. + /// + public IEnumerable GetStateSets(ProjectId projectId) + { + return _hostStates.GetStateSets().Concat(_projectStates.GetStateSets(projectId)); + } + + /// + /// Return s for the given . + /// This will never create new but will return ones already created. + /// Difference with is that + /// this will only return s that have same language as . + /// + public IEnumerable GetStateSets(Project project) + { + return GetStateSets(project.Id).Where(s => s.Language == project.Language); + } + + /// + /// Return s for the given . + /// This will either return already created s for the specific snapshot of or + /// It will create new s for the and update internal state. + /// + /// since this has a side-effect, this should never be called concurrently. and incremental analyzer (solution crawler) should guarantee that. + /// + public IEnumerable GetOrUpdateStateSets(Project project) + { + return _hostStates.GetOrCreateStateSets(project.Language).Concat(_projectStates.GetOrUpdateStateSets(project)); + } + + /// + /// Return s for the given . + /// This will either return already created s for the specific snapshot of or + /// It will create new s for the . + /// Unlike , this has no side effect. + /// + public IEnumerable GetOrCreateStateSets(Project project) + { + return _hostStates.GetOrCreateStateSets(project.Language).Concat(_projectStates.GetOrCreateStateSets(project)); + } + + /// + /// Return for the given in the context of . + /// This will either return already created for the specific snapshot of or + /// It will create new for the . + /// This will not have any side effect. + /// + public StateSet GetOrCreateStateSet(Project project, DiagnosticAnalyzer analyzer) + { + var stateSet = _hostStates.GetOrCreateStateSet(project.Language, analyzer); + if (stateSet != null) + { + return stateSet; + } + + return _projectStates.GetOrCreateStateSet(project, analyzer); + } + + /// + /// Return s that are added as the given 's AnalyzerReferences. + /// This will never create new but will return ones already created. + /// + public ImmutableArray CreateBuildOnlyProjectStateSet(Project project) + { + var referenceIdentities = project.AnalyzerReferences.Select(r => _analyzerManager.GetAnalyzerReferenceIdentity(r)).ToSet(); + var stateSetMap = GetStateSets(project).ToDictionary(s => s.Analyzer, s => s); + + var stateSets = ImmutableArray.CreateBuilder(); + + // we always include compiler analyzer in build only state + var compilerAnalyzer = _analyzerManager.GetCompilerDiagnosticAnalyzer(project.Language); + StateSet compilerStateSet; + if (stateSetMap.TryGetValue(compilerAnalyzer, out compilerStateSet)) + { + stateSets.Add(compilerStateSet); + } + + var analyzerMap = _analyzerManager.GetHostDiagnosticAnalyzersPerReference(project.Language); + foreach (var kv in analyzerMap) + { + var identity = kv.Key; + if (!referenceIdentities.Contains(identity)) + { + // it is from host analyzer package rather than project analyzer reference + // which build doesn't have + continue; + } + + // if same analyzer exists both in host (vsix) and in analyzer reference, + // we include it in build only analyzer. + foreach (var analyzer in kv.Value) + { + StateSet stateSet; + if (stateSetMap.TryGetValue(analyzer, out stateSet) && stateSet != compilerStateSet) + { + stateSets.Add(stateSet); + } + } + } + + return stateSets.ToImmutable(); + } + + public bool OnDocumentReset(IEnumerable stateSets, DocumentId documentId) + { + var removed = false; + foreach (var stateSet in stateSets) + { + removed |= stateSet.OnDocumentReset(documentId); + } + + return removed; + } + + public bool OnDocumentClosed(IEnumerable stateSets, DocumentId documentId) + { + var removed = false; + foreach (var stateSet in stateSets) + { + removed |= stateSet.OnDocumentClosed(documentId); + } + + return removed; + } + + public bool OnDocumentRemoved(IEnumerable stateSets, DocumentId documentId) + { + var removed = false; + foreach (var stateSet in stateSets) + { + removed |= stateSet.OnDocumentRemoved(documentId); + } + + return removed; + } + + public bool OnProjectRemoved(IEnumerable stateSets, ProjectId projectId) + { + var removed = false; + foreach (var stateSet in stateSets) + { + removed |= stateSet.OnProjectRemoved(projectId); + } + + _projectStates.RemoveStateSet(projectId); + return removed; + } + + private void RaiseProjectAnalyzerReferenceChanged(ProjectAnalyzerReferenceChangedEventArgs args) + { + ProjectAnalyzerReferenceChanged?.Invoke(this, args); + } + + private static ImmutableDictionary CreateAnalyzerMap( + HostAnalyzerManager analyzerManager, string language, IEnumerable> analyzerCollection) + { + var compilerAnalyzer = analyzerManager.GetCompilerDiagnosticAnalyzer(language); + + var builder = ImmutableDictionary.CreateBuilder(); + foreach (var analyzers in analyzerCollection) + { + foreach (var analyzer in analyzers) + { + // TODO: + // #1, all de -duplication should move to HostAnalyzerManager + // #2, not sure whether de-duplication of analyzer itself makes sense. this can only happen + // if user deliberately put same analyzer twice. + if (builder.ContainsKey(analyzer)) + { + continue; + } + + var buildToolName = analyzer == compilerAnalyzer ? + PredefinedBuildTools.Live : GetBuildToolName(analyzerManager, language, analyzer); + + builder.Add(analyzer, new StateSet(language, analyzer, buildToolName)); + } + } + + return builder.ToImmutable(); + } + + private static string GetBuildToolName(HostAnalyzerManager analyzerManager, string language, DiagnosticAnalyzer analyzer) + { + var packageName = analyzerManager.GetDiagnosticAnalyzerPackageName(language, analyzer); + if (packageName == null) + { + return null; + } + + if (packageName == RoslynLanguageServices) + { + return PredefinedBuildTools.Live; + } + + return $"{analyzer.GetAnalyzerAssemblyName()} [{packageName}]"; + } + + [Conditional("DEBUG")] + private static void VerifyDiagnosticStates(IEnumerable stateSets) + { + // Ensure diagnostic state name is indeed unique. + var set = new HashSet>(); + + foreach (var stateSet in stateSets) + { + if (!(set.Add(ValueTuple.Create(stateSet.Language, stateSet.StateName)))) + { + Contract.Fail(); + } + } + } + } + } +} diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateSet.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateSet.cs new file mode 100644 index 0000000000000..4ee5c9c8ec9ca --- /dev/null +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateSet.cs @@ -0,0 +1,203 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 +{ + internal partial class DiagnosticIncrementalAnalyzer + { + /// + /// this contains all states regarding a + /// + private class StateSet + { + private const string UserDiagnosticsPrefixTableName = ""; + + private readonly string _language; + private readonly DiagnosticAnalyzer _analyzer; + private readonly string _errorSourceName; + + // analyzer version this state belong to + private readonly VersionStamp _analyzerVersion; + + // name of each analysis kind persistent storage + private readonly string _stateName; + private readonly string _syntaxStateName; + private readonly string _semanticStateName; + private readonly string _nonLocalStateName; + + private readonly ConcurrentDictionary _activeFileStates; + private readonly ConcurrentDictionary _projectStates; + + public StateSet(string language, DiagnosticAnalyzer analyzer, string errorSourceName) + { + _language = language; + _analyzer = analyzer; + _errorSourceName = errorSourceName; + + var nameAndVersion = GetNameAndVersion(_analyzer); + _analyzerVersion = nameAndVersion.Item2; + + _stateName = nameAndVersion.Item1; + + _syntaxStateName = _stateName + ".Syntax"; + _semanticStateName = _stateName + ".Semantic"; + _nonLocalStateName = _stateName + ".NonLocal"; + + _activeFileStates = new ConcurrentDictionary(concurrencyLevel: 2, capacity: 10); + _projectStates = new ConcurrentDictionary(concurrencyLevel: 2, capacity: 1); + } + + public string StateName => _stateName; + public string SyntaxStateName => _syntaxStateName; + public string SemanticStateName => _semanticStateName; + public string NonLocalStateName => _nonLocalStateName; + + public string Language => _language; + public string ErrorSourceName => _errorSourceName; + + public DiagnosticAnalyzer Analyzer => _analyzer; + public VersionStamp AnalyzerVersion => _analyzerVersion; + + public bool ContainsAnyDocumentOrProjectDiagnostics(ProjectId projectId) + { + foreach (var state in GetActiveFileStates(projectId)) + { + if (!state.IsEmpty) + { + return true; + } + } + + ProjectState projectState; + if (!_projectStates.TryGetValue(projectId, out projectState)) + { + return false; + } + + return !projectState.IsEmpty(); + } + + public IEnumerable GetDocumentsWithDiagnostics(ProjectId projectId) + { + HashSet set = null; + foreach (var state in GetActiveFileStates(projectId)) + { + set = set ?? new HashSet(); + set.Add(state.DocumentId); + } + + ProjectState projectState; + if (!_projectStates.TryGetValue(projectId, out projectState) || projectState.IsEmpty()) + { + return set ?? SpecializedCollections.EmptyEnumerable(); + } + + set = set ?? new HashSet(); + set.UnionWith(projectState.GetDocumentsWithDiagnostics()); + + return set; + } + + private IEnumerable GetActiveFileStates(ProjectId projectId) + { + return _activeFileStates.Where(kv => kv.Key.ProjectId == projectId).Select(kv => kv.Value); + } + + public bool IsActiveFile(DocumentId documentId) + { + return _activeFileStates.ContainsKey(documentId); + } + + public bool TryGetActiveFileState(DocumentId documentId, out ActiveFileState state) + { + return _activeFileStates.TryGetValue(documentId, out state); + } + + public bool TryGetProjectState(ProjectId projectId, out ProjectState state) + { + return _projectStates.TryGetValue(projectId, out state); + } + + public ActiveFileState GetActiveFileState(DocumentId documentId) + { + return _activeFileStates.GetOrAdd(documentId, id => new ActiveFileState(id)); + } + + public ProjectState GetProjectState(ProjectId projectId) + { + return _projectStates.GetOrAdd(projectId, id => new ProjectState(this, id)); + } + + public bool OnDocumentClosed(DocumentId id) + { + return OnDocumentReset(id); + } + + public bool OnDocumentReset(DocumentId id) + { + // remove active file state + ActiveFileState state; + if (_activeFileStates.TryRemove(id, out state)) + { + return !state.IsEmpty; + } + + return false; + } + + public bool OnDocumentRemoved(DocumentId id) + { + // remove active file state for removed document + var removed = OnDocumentReset(id); + + // remove state for the file that got removed. + ProjectState state; + if (_projectStates.TryGetValue(id.ProjectId, out state)) + { + removed |= state.OnDocumentRemoved(id); + } + + return removed; + } + + public bool OnProjectRemoved(ProjectId id) + { + // remove state for project that got removed. + ProjectState state; + if (_projectStates.TryRemove(id, out state)) + { + return state.OnProjectRemoved(id); + } + + return false; + } + + public void OnRemoved() + { + // ths stateset is being removed. + // TODO: we do this since InMemoryCache is static type. we might consider making it instance object + // of something. + InMemoryStorage.DropCache(Analyzer); + } + + /// + /// Get the unique state name for the given analyzer. + /// Note that this name is used by the underlying persistence stream of the corresponding to Read/Write diagnostic data into the stream. + /// If any two distinct analyzer have the same diagnostic state name, we will end up sharing the persistence stream between them, leading to duplicate/missing/incorrect diagnostic data. + /// + private static ValueTuple GetNameAndVersion(DiagnosticAnalyzer analyzer) + { + Contract.ThrowIfNull(analyzer); + + // Get the unique ID for given diagnostic analyzer. + // note that we also put version stamp so that we can detect changed analyzer. + var tuple = analyzer.GetAnalyzerIdAndVersion(); + return ValueTuple.Create(UserDiagnosticsPrefixTableName + "_" + tuple.Item1, tuple.Item2); + } + } + } +} diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs index 50bbf3ab086d5..d3f9bb9616f12 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs @@ -1,23 +1,27 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Shared.Options; -using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 { - // TODO: implement correct events and etc. + /// + /// Diagnostic Analyzer Engine V2 + /// + /// This one follows pattern compiler has set for diagnostic analyzer. + /// internal partial class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncrementalAnalyzer { private readonly int _correlationId; + private readonly StateManager _stateManager; + private readonly Executor _executor; + private readonly CompilationManager _compilationManager; + public DiagnosticIncrementalAnalyzer( DiagnosticAnalyzerService owner, int correlationId, @@ -27,86 +31,157 @@ public DiagnosticIncrementalAnalyzer( : base(owner, workspace, hostAnalyzerManager, hostDiagnosticUpdateSource) { _correlationId = correlationId; - } - private static bool AnalysisEnabled(Document document) - { - // change it to check active file (or visible files), not open files if active file tracking is enabled. - // otherwise, use open file. - return document.IsOpen(); + _stateManager = new StateManager(hostAnalyzerManager); + _stateManager.ProjectAnalyzerReferenceChanged += OnProjectAnalyzerReferenceChanged; + + _executor = new Executor(this); + _compilationManager = new CompilationManager(this); } - private bool FullAnalysisEnabled(Workspace workspace, string language) + public override bool ContainsDiagnostics(Workspace workspace, ProjectId projectId) { - return workspace.Options.GetOption(ServiceFeatureOnOffOptions.ClosedFileDiagnostic, language) && - workspace.Options.GetOption(RuntimeOptions.FullSolutionAnalysis); + foreach (var stateSet in _stateManager.GetStateSets(projectId)) + { + if (stateSet.ContainsAnyDocumentOrProjectDiagnostics(projectId)) + { + return true; + } + } + + return false; } - private async Task> GetProjectDiagnosticsAsync(Project project, bool includeSuppressedDiagnostics, CancellationToken cancellationToken) + private bool SupportAnalysisKind(DiagnosticAnalyzer analyzer, string language, AnalysisKind kind) { - if (project == null) + // compiler diagnostic analyzer always support all kinds + if (HostAnalyzerManager.IsCompilerDiagnosticAnalyzer(language, analyzer)) { - return ImmutableArray.Empty; + return true; } - var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + switch (kind) + { + case AnalysisKind.Syntax: + return analyzer.SupportsSyntaxDiagnosticAnalysis(); + case AnalysisKind.Semantic: + return analyzer.SupportsSemanticDiagnosticAnalysis(); + default: + return Contract.FailWithReturn("shouldn't reach here"); + } + } - var analyzers = HostAnalyzerManager.CreateDiagnosticAnalyzers(project); + private void OnProjectAnalyzerReferenceChanged(object sender, ProjectAnalyzerReferenceChangedEventArgs e) + { + if (e.Removed.Length == 0) + { + // nothing to refresh + return; + } - var compilationWithAnalyzer = compilation.WithAnalyzers(analyzers, project.AnalyzerOptions, cancellationToken); + // events will be automatically serialized. + var project = e.Project; + var stateSets = e.Removed; - // REVIEW: this API is a bit strange. - // if getting diagnostic is cancelled, it has to create new compilation and do everything from scratch again? - var dxs = GetDiagnosticData(project, await compilationWithAnalyzer.GetAnalyzerDiagnosticsAsync().ConfigureAwait(false)).ToImmutableArrayOrEmpty(); + // make sure we drop cache related to the analyzers + foreach (var stateSet in stateSets) + { + stateSet.OnRemoved(); + } - return dxs; + ClearAllDiagnostics(stateSets, project.Id); } - private IEnumerable GetDiagnosticData(Project project, ImmutableArray diagnostics) + private void ClearAllDiagnostics(ImmutableArray stateSets, ProjectId projectId) { - foreach (var diagnostic in diagnostics) + Owner.RaiseBulkDiagnosticsUpdated(raiseEvents => { - if (diagnostic.Location == Location.None) + var handleActiveFile = true; + foreach (var stateSet in stateSets) { - yield return DiagnosticData.Create(project, diagnostic); - continue; - } + // PERF: don't fire events for ones that we dont have any diagnostics on + if (!stateSet.ContainsAnyDocumentOrProjectDiagnostics(projectId)) + { + continue; + } - var document = project.GetDocument(diagnostic.Location.SourceTree); - if (document == null) - { - continue; + RaiseProjectDiagnosticsRemoved(stateSet, projectId, stateSet.GetDocumentsWithDiagnostics(projectId), handleActiveFile, raiseEvents); } + }); + } - yield return DiagnosticData.Create(document, diagnostic); - } + private void RaiseDiagnosticsCreated( + Project project, StateSet stateSet, ImmutableArray items, Action raiseEvents) + { + Contract.ThrowIfFalse(project.Solution.Workspace == Workspace); + + raiseEvents(DiagnosticsUpdatedArgs.DiagnosticsCreated( + CreateId(stateSet.Analyzer, project.Id, AnalysisKind.NonLocal, stateSet.ErrorSourceName), + project.Solution.Workspace, + project.Solution, + project.Id, + documentId: null, + diagnostics: items)); } - private void RaiseEvents(Project project, ImmutableArray diagnostics) + private void RaiseDiagnosticsRemoved( + ProjectId projectId, Solution solution, StateSet stateSet, Action raiseEvents) { - var groups = diagnostics.GroupBy(d => d.DocumentId); + Contract.ThrowIfFalse(solution == null || solution.Workspace == Workspace); + + raiseEvents(DiagnosticsUpdatedArgs.DiagnosticsRemoved( + CreateId(stateSet.Analyzer, projectId, AnalysisKind.NonLocal, stateSet.ErrorSourceName), + Workspace, + solution, + projectId, + documentId: null)); + } - var solution = project.Solution; - var workspace = solution.Workspace; + private void RaiseDiagnosticsCreated( + Document document, StateSet stateSet, AnalysisKind kind, ImmutableArray items, Action raiseEvents) + { + Contract.ThrowIfFalse(document.Project.Solution.Workspace == Workspace); + + raiseEvents(DiagnosticsUpdatedArgs.DiagnosticsCreated( + CreateId(stateSet.Analyzer, document.Id, kind, stateSet.ErrorSourceName), + document.Project.Solution.Workspace, + document.Project.Solution, + document.Project.Id, + document.Id, + items)); + } - foreach (var kv in groups) - { - if (kv.Key == null) - { - Owner.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs.DiagnosticsCreated( - ValueTuple.Create(this, project.Id), workspace, solution, project.Id, null, kv.ToImmutableArrayOrEmpty())); - continue; - } + private void RaiseDiagnosticsRemoved( + DocumentId documentId, Solution solution, StateSet stateSet, AnalysisKind kind, Action raiseEvents) + { + Contract.ThrowIfFalse(solution == null || solution.Workspace == Workspace); + + raiseEvents(DiagnosticsUpdatedArgs.DiagnosticsRemoved( + CreateId(stateSet.Analyzer, documentId, kind, stateSet.ErrorSourceName), + Workspace, + solution, + documentId.ProjectId, + documentId)); + } - Owner.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs.DiagnosticsCreated( - ValueTuple.Create(this, kv.Key), workspace, solution, project.Id, kv.Key, kv.ToImmutableArrayOrEmpty())); - } + private object CreateId(DiagnosticAnalyzer analyzer, DocumentId key, AnalysisKind kind, string errorSourceName) + { + return CreateIdInternal(analyzer, key, kind, errorSourceName); } - public override bool ContainsDiagnostics(Workspace workspace, ProjectId projectId) + private object CreateId(DiagnosticAnalyzer analyzer, ProjectId key, AnalysisKind kind, string errorSourceName) { - // for now, it always return false; - return false; + return CreateIdInternal(analyzer, key, kind, errorSourceName); + } + + private static object CreateIdInternal(DiagnosticAnalyzer analyzer, object key, AnalysisKind kind, string errorSourceName) + { + return new LiveDiagnosticUpdateArgsId(analyzer, key, (int)kind, errorSourceName); + } + + public static Task GetDiagnosticVersionAsync(Project project, CancellationToken cancellationToken) + { + return project.GetDependentVersionAsync(cancellationToken); } } } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_BuildSynchronization.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_BuildSynchronization.cs index aa378e8c3aab4..0755cc990e6ba 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_BuildSynchronization.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_BuildSynchronization.cs @@ -1,6 +1,10 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Collections.Generic; using System.Collections.Immutable; +using System.Globalization; +using System.Linq; +using System.Threading; using System.Threading.Tasks; using Roslyn.Utilities; @@ -8,18 +12,183 @@ namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 { internal partial class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncrementalAnalyzer { - // TODO: this API will be changed to 1 API that gives all diagnostics under a project - // and that will simply replace project state - public override Task SynchronizeWithBuildAsync(DiagnosticAnalyzerService.BatchUpdateToken token, Project project, ImmutableArray diagnostics) + public override async Task SynchronizeWithBuildAsync(Workspace workspace, ImmutableDictionary> map) { - // TODO: for now, we dont do anything. - return SpecializedTasks.EmptyTask; + if (!PreferBuildErrors(workspace)) + { + // prefer live errors over build errors + return; + } + + var solution = workspace.CurrentSolution; + foreach (var projectEntry in map) + { + var project = solution.GetProject(projectEntry.Key); + if (project == null) + { + continue; + } + + // REVIEW: is build diagnostic contains suppressed diagnostics? + var stateSets = _stateManager.CreateBuildOnlyProjectStateSet(project); + var result = await CreateProjectAnalysisDataAsync(project, stateSets, projectEntry.Value).ConfigureAwait(false); + + foreach (var stateSet in stateSets) + { + var state = stateSet.GetProjectState(project.Id); + await state.SaveAsync(project, result.GetResult(stateSet.Analyzer)).ConfigureAwait(false); + } + + // REVIEW: this won't handle active files. might need to tweak it later. + RaiseProjectDiagnosticsIfNeeded(project, stateSets, result.OldResult, result.Result); + } + + if (PreferLiveErrorsOnOpenedFiles(workspace)) + { + // enqueue re-analysis of open documents. + this.Owner.Reanalyze(workspace, documentIds: workspace.GetOpenDocumentIds(), highPriority: true); + } + } + + private async Task CreateProjectAnalysisDataAsync(Project project, ImmutableArray stateSets, ImmutableArray diagnostics) + { + // we always load data sicne we don't know right version. + var avoidLoadingData = false; + var oldAnalysisData = await ProjectAnalysisData.CreateAsync(project, stateSets, avoidLoadingData, CancellationToken.None).ConfigureAwait(false); + var newResult = CreateAnalysisResults(project, stateSets, oldAnalysisData, diagnostics); + + return new ProjectAnalysisData(VersionStamp.Default, oldAnalysisData.Result, newResult); + } + + private ImmutableDictionary CreateAnalysisResults( + Project project, ImmutableArray stateSets, ProjectAnalysisData oldAnalysisData, ImmutableArray diagnostics) + { + using (var poolObject = SharedPools.Default>().GetPooledObject()) + { + // we can't distinguish locals and non locals from build diagnostics nor determine right snapshot version for the build. + // so we put everything in as semantic local with default version. this lets us to replace those to live diagnostics when needed easily. + var version = VersionStamp.Default; + var lookup = diagnostics.ToLookup(d => d.Id); + + var builder = ImmutableDictionary.CreateBuilder(); + foreach (var stateSet in stateSets) + { + var descriptors = HostAnalyzerManager.GetDiagnosticDescriptors(stateSet.Analyzer); + var liveDiagnostics = MergeDiagnostics(ConvertToLiveDiagnostics(lookup, descriptors, poolObject.Object), GetDiagnostics(oldAnalysisData.GetResult(stateSet.Analyzer))); + + var group = liveDiagnostics.GroupBy(d => d.DocumentId); + var result = new AnalysisResult( + project.Id, + version, + documentIds: group.Where(g => g.Key != null).Select(g => g.Key).ToImmutableHashSet(), + syntaxLocals: ImmutableDictionary>.Empty, + semanticLocals: group.Where(g => g.Key != null).ToImmutableDictionary(g => g.Key, g => g.ToImmutableArray()), + nonLocals: ImmutableDictionary>.Empty, + others: ImmutableArray.Empty); + + builder.Add(stateSet.Analyzer, result); + } + + return builder.ToImmutable(); + } + } + + private ImmutableArray GetDiagnostics(AnalysisResult result) + { + // PERF: don't allocation anything if not needed + if (result.IsAggregatedForm || result.IsEmpty) + { + return ImmutableArray.Empty; + } + + return result.SyntaxLocals.Values.SelectMany(v => v).Concat( + result.SemanticLocals.Values.SelectMany(v => v)).Concat( + result.NonLocals.Values.SelectMany(v => v)).Concat( + result.Others).ToImmutableArray(); + } + + private bool PreferBuildErrors(Workspace workspace) + { + return workspace.Options.GetOption(InternalDiagnosticsOptions.BuildErrorIsTheGod) || workspace.Options.GetOption(InternalDiagnosticsOptions.PreferBuildErrorsOverLiveErrors); + } + + private bool PreferLiveErrorsOnOpenedFiles(Workspace workspace) + { + return !workspace.Options.GetOption(InternalDiagnosticsOptions.BuildErrorIsTheGod) && workspace.Options.GetOption(InternalDiagnosticsOptions.PreferLiveErrorsOnOpenedFiles); + } + + private ImmutableArray MergeDiagnostics(ImmutableArray newDiagnostics, ImmutableArray existingDiagnostics) + { + ImmutableArray.Builder builder = null; + + if (newDiagnostics.Length > 0) + { + builder = ImmutableArray.CreateBuilder(); + builder.AddRange(newDiagnostics); + } + + if (existingDiagnostics.Length > 0) + { + // retain hidden live diagnostics since it won't be comes from build. + builder = builder ?? ImmutableArray.CreateBuilder(); + builder.AddRange(existingDiagnostics.Where(d => d.Severity == DiagnosticSeverity.Hidden)); + } + + return builder == null ? ImmutableArray.Empty : builder.ToImmutable(); + } + + private ImmutableArray ConvertToLiveDiagnostics( + ILookup lookup, ImmutableArray descriptors, HashSet seen) + { + if (lookup == null) + { + return ImmutableArray.Empty; + } + + ImmutableArray.Builder builder = null; + foreach (var descriptor in descriptors) + { + // make sure we don't report same id to multiple different analyzers + if (!seen.Add(descriptor.Id)) + { + // TODO: once we have information where diagnostic came from, we probably don't need this. + continue; + } + + var items = lookup[descriptor.Id]; + if (items == null) + { + continue; + } + + builder = builder ?? ImmutableArray.CreateBuilder(); + builder.AddRange(items.Select(d => CreateLiveDiagnostic(descriptor, d))); + } + + return builder == null ? ImmutableArray.Empty : builder.ToImmutable(); } - public override Task SynchronizeWithBuildAsync(DiagnosticAnalyzerService.BatchUpdateToken token, Document document, ImmutableArray diagnostics) + private static DiagnosticData CreateLiveDiagnostic(DiagnosticDescriptor descriptor, DiagnosticData diagnostic) { - // TODO: for now, we dont do anything. - return SpecializedTasks.EmptyTask; + return new DiagnosticData( + descriptor.Id, + descriptor.Category, + diagnostic.Message, + descriptor.GetBingHelpMessage(), + diagnostic.Severity, + descriptor.DefaultSeverity, + descriptor.IsEnabledByDefault, + diagnostic.WarningLevel, + descriptor.CustomTags.ToImmutableArray(), + diagnostic.Properties, + diagnostic.Workspace, + diagnostic.ProjectId, + diagnostic.DataLocation, + diagnostic.AdditionalLocations, + descriptor.Title.ToString(CultureInfo.CurrentUICulture), + descriptor.Description.ToString(CultureInfo.CurrentUICulture), + descriptor.HelpLinkUri, + isSuppressed: diagnostic.IsSuppressed); } } } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs index 093213a776ae3..2da4187726011 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; @@ -8,69 +9,466 @@ namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 { - // TODO: need to mimic what V1 does. use cache if possible, otherwise, calculate at the spot. - internal partial class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncrementalAnalyzer + internal partial class DiagnosticIncrementalAnalyzer { public override Task> GetSpecificCachedDiagnosticsAsync(Solution solution, object id, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken)) { - return GetSpecificDiagnosticsAsync(solution, id, includeSuppressedDiagnostics, cancellationToken); + return new IDECachedDiagnosticGetter(this, solution, id, includeSuppressedDiagnostics).GetSpecificDiagnosticsAsync(cancellationToken); } - public override Task> GetCachedDiagnosticsAsync(Solution solution, ProjectId projectId = null, DocumentId documentId = null, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken)) + public override Task> GetCachedDiagnosticsAsync(Solution solution, ProjectId projectId, DocumentId documentId, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken)) { - return GetDiagnosticsAsync(solution, projectId, documentId, includeSuppressedDiagnostics, cancellationToken); + return new IDECachedDiagnosticGetter(this, solution, projectId, documentId, includeSuppressedDiagnostics).GetDiagnosticsAsync(cancellationToken); } - public override async Task> GetSpecificDiagnosticsAsync(Solution solution, object id, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken)) + public override Task> GetSpecificDiagnosticsAsync(Solution solution, object id, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken)) { - if (id is ValueTuple) + return new IDELatestDiagnosticGetter(this, solution, id, includeSuppressedDiagnostics).GetSpecificDiagnosticsAsync(cancellationToken); + } + + public override Task> GetDiagnosticsAsync(Solution solution, ProjectId projectId, DocumentId documentId, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken)) + { + return new IDELatestDiagnosticGetter(this, solution, projectId, documentId, includeSuppressedDiagnostics).GetDiagnosticsAsync(cancellationToken); + } + + public override Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId projectId, DocumentId documentId, ImmutableHashSet diagnosticIds, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken)) + { + return new IDELatestDiagnosticGetter(this, diagnosticIds, solution, projectId, documentId, includeSuppressedDiagnostics).GetDiagnosticsAsync(cancellationToken); + } + + public override Task> GetProjectDiagnosticsForIdsAsync(Solution solution, ProjectId projectId, ImmutableHashSet diagnosticIds, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken)) + { + return new IDELatestDiagnosticGetter(this, diagnosticIds, solution, projectId, includeSuppressedDiagnostics).GetProjectDiagnosticsAsync(cancellationToken); + } + + private abstract class DiagnosticGetter + { + protected readonly DiagnosticIncrementalAnalyzer Owner; + + protected readonly Solution Solution; + protected readonly ProjectId ProjectId; + protected readonly DocumentId DocumentId; + protected readonly object Id; + protected readonly bool IncludeSuppressedDiagnostics; + + private ImmutableArray.Builder _builder; + + public DiagnosticGetter( + DiagnosticIncrementalAnalyzer owner, + Solution solution, + ProjectId projectId, + DocumentId documentId, + object id, + bool includeSuppressedDiagnostics) { - var key = (ValueTuple)id; - return await GetDiagnosticsAsync(solution, key.Item2.ProjectId, key.Item2, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); + Owner = owner; + Solution = solution; + + DocumentId = documentId; + ProjectId = projectId; + + Id = id; + IncludeSuppressedDiagnostics = includeSuppressedDiagnostics; + + // try to retrieve projectId/documentId from id if possible. + var argsId = id as LiveDiagnosticUpdateArgsId; + if (argsId != null) + { + DocumentId = DocumentId ?? argsId.Key as DocumentId; + ProjectId = ProjectId ?? (argsId.Key as ProjectId) ?? DocumentId.ProjectId; + } + + _builder = null; } - if (id is ValueTuple) + protected StateManager StateManager => this.Owner._stateManager; + + protected Project Project => Solution.GetProject(ProjectId); + protected Document Document => Solution.GetDocument(DocumentId); + + protected virtual bool ShouldIncludeDiagnostic(DiagnosticData diagnostic) => true; + + protected ImmutableArray GetDiagnosticData() { - var key = (ValueTuple)id; - var diagnostics = await GetDiagnosticsAsync(solution, key.Item2, null, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); - return diagnostics.Where(d => d.DocumentId == null).ToImmutableArray(); + return _builder != null ? _builder.ToImmutableArray() : ImmutableArray.Empty; } - return ImmutableArray.Empty; - } + protected abstract Task?> GetDiagnosticsAsync(StateSet stateSet, Project project, DocumentId documentId, AnalysisKind kind, CancellationToken cancellationToken); + protected abstract Task AppendDiagnosticsAsync(Project project, DocumentId targetDocumentId, IEnumerable documentIds, CancellationToken cancellationToken); - public override async Task> GetDiagnosticsAsync(Solution solution, ProjectId projectId = null, DocumentId documentId = null, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken)) - { - if (documentId != null) + public async Task> GetSpecificDiagnosticsAsync(CancellationToken cancellationToken) + { + if (Solution == null) + { + return ImmutableArray.Empty; + } + + var argsId = Id as LiveDiagnosticUpdateArgsId; + if (argsId == null) + { + return ImmutableArray.Empty; + } + + if (Project == null) + { + // when we return cached result, make sure we at least return something that exist in current solution + return ImmutableArray.Empty; + } + + var stateSet = this.StateManager.GetOrCreateStateSet(Project, argsId.Analyzer); + if (stateSet == null) + { + return ImmutableArray.Empty; + } + + var diagnostics = await GetDiagnosticsAsync(stateSet, Project, DocumentId, (AnalysisKind)argsId.Kind, cancellationToken).ConfigureAwait(false); + if (diagnostics == null) + { + // Document or project might have been removed from the solution. + return ImmutableArray.Empty; + } + + return FilterSuppressedDiagnostics(diagnostics.Value); + } + + public async Task> GetDiagnosticsAsync(CancellationToken cancellationToken) { - var diagnostics = await GetProjectDiagnosticsAsync(solution.GetProject(projectId), includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); - return diagnostics.Where(d => d.DocumentId == documentId).ToImmutableArrayOrEmpty(); + if (Solution == null) + { + return ImmutableArray.Empty; + } + + if (ProjectId != null) + { + if (Project == null) + { + return GetDiagnosticData(); + } + + var documentIds = DocumentId != null ? SpecializedCollections.SingletonEnumerable(DocumentId) : Project.DocumentIds; + + // return diagnostics specific to one project or document + await AppendDiagnosticsAsync(Project, DocumentId, documentIds, cancellationToken).ConfigureAwait(false); + return GetDiagnosticData(); + } + + await AppendDiagnosticsAsync(Solution, cancellationToken).ConfigureAwait(false); + return GetDiagnosticData(); } - if (projectId != null) + protected async Task AppendDiagnosticsAsync(Solution solution, CancellationToken cancellationToken) { - return await GetProjectDiagnosticsAsync(solution.GetProject(projectId), includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); + // when we return cached result, make sure we at least return something that exist in current solution + if (Project == null) + { + return; + } + + // PERF: should run this concurrently? analyzer driver itself is already running concurrently. + DocumentId nullTargetDocumentId = null; + foreach (var project in Solution.Projects) + { + await AppendDiagnosticsAsync(project, nullTargetDocumentId, project.DocumentIds, cancellationToken: cancellationToken).ConfigureAwait(false); + } } - var builder = ImmutableArray.CreateBuilder(); - foreach (var project in solution.Projects) + protected void AppendDiagnostics(IEnumerable items) { - builder.AddRange(await GetProjectDiagnosticsAsync(project, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false)); + if (items == null) + { + return; + } + + if (_builder == null) + { + Interlocked.CompareExchange(ref _builder, ImmutableArray.CreateBuilder(), null); + } + + lock (_builder) + { + _builder.AddRange(items.Where(ShouldIncludeSuppressedDiagnostic).Where(ShouldIncludeDiagnostic)); + } } - return builder.ToImmutable(); + private bool ShouldIncludeSuppressedDiagnostic(DiagnosticData diagnostic) + { + return IncludeSuppressedDiagnostics || !diagnostic.IsSuppressed; + } + + private ImmutableArray FilterSuppressedDiagnostics(ImmutableArray diagnostics) + { + if (IncludeSuppressedDiagnostics || diagnostics.IsDefaultOrEmpty) + { + return diagnostics; + } + + // create builder only if there is suppressed diagnostics + ImmutableArray.Builder builder = null; + for (int i = 0; i < diagnostics.Length; i++) + { + var diagnostic = diagnostics[i]; + if (diagnostic.IsSuppressed) + { + if (builder == null) + { + builder = ImmutableArray.CreateBuilder(); + for (int j = 0; j < i; j++) + { + builder.Add(diagnostics[j]); + } + } + } + else if (builder != null) + { + builder.Add(diagnostic); + } + } + + return builder != null ? builder.ToImmutable() : diagnostics; + } } - public override async Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId projectId = null, DocumentId documentId = null, ImmutableHashSet diagnosticIds = null, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken)) + private class IDECachedDiagnosticGetter : DiagnosticGetter { - var diagnostics = await GetDiagnosticsAsync(solution, projectId, documentId, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); - return diagnostics.Where(d => diagnosticIds.Contains(d.Id)).ToImmutableArrayOrEmpty(); + public IDECachedDiagnosticGetter(DiagnosticIncrementalAnalyzer owner, Solution solution, object id, bool includeSuppressedDiagnostics) : + base(owner, solution, projectId: null, documentId: null, id: id, includeSuppressedDiagnostics: includeSuppressedDiagnostics) + { + } + + public IDECachedDiagnosticGetter(DiagnosticIncrementalAnalyzer owner, Solution solution, ProjectId projectId, DocumentId documentId, bool includeSuppressedDiagnostics) : + base(owner, solution, projectId, documentId, id: null, includeSuppressedDiagnostics: includeSuppressedDiagnostics) + { + } + + protected override async Task AppendDiagnosticsAsync(Project project, DocumentId targetDocumentId, IEnumerable documentIds, CancellationToken cancellationToken) + { + // when we return cached result, make sure we at least return something that exist in current solution + if (Project == null) + { + return; + } + + foreach (var stateSet in StateManager.GetOrCreateStateSets(project)) + { + foreach (var documentId in documentIds) + { + AppendDiagnostics(await GetDiagnosticsAsync(stateSet, project, documentId, AnalysisKind.Syntax, cancellationToken).ConfigureAwait(false)); + AppendDiagnostics(await GetDiagnosticsAsync(stateSet, project, documentId, AnalysisKind.Semantic, cancellationToken).ConfigureAwait(false)); + AppendDiagnostics(await GetDiagnosticsAsync(stateSet, project, documentId, AnalysisKind.NonLocal, cancellationToken).ConfigureAwait(false)); + } + + if (targetDocumentId == null) + { + // include project diagnostics if there is no target document + AppendDiagnostics(await GetProjectDiagnosticsAsync(stateSet, project, targetDocumentId, AnalysisKind.NonLocal, cancellationToken).ConfigureAwait(false)); + } + } + } + + protected override async Task?> GetDiagnosticsAsync(StateSet stateSet, Project project, DocumentId documentId, AnalysisKind kind, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var activeFileDiagnostics = GetActiveFileDiagnostics(stateSet, documentId, kind); + if (activeFileDiagnostics.HasValue) + { + return activeFileDiagnostics.Value; + } + + var projectDiagnostics = await GetProjectDiagnosticsAsync(stateSet, project, documentId, kind, cancellationToken).ConfigureAwait(false); + if (projectDiagnostics.HasValue) + { + return projectDiagnostics.Value; + } + + return null; + } + + private ImmutableArray? GetActiveFileDiagnostics(StateSet stateSet, DocumentId documentId, AnalysisKind kind) + { + if (documentId == null) + { + return null; + } + + ActiveFileState state; + if (!stateSet.TryGetActiveFileState(documentId, out state)) + { + return null; + } + + return state.GetAnalysisData(kind).Items; + } + + private async Task?> GetProjectDiagnosticsAsync( + StateSet stateSet, Project project, DocumentId documentId, AnalysisKind kind, CancellationToken cancellationToken) + { + ProjectState state; + if (!stateSet.TryGetProjectState(project.Id, out state)) + { + // never analyzed this project yet. + return null; + } + + if (documentId != null) + { + // file doesn't exist in current solution + var document = Solution.GetDocument(documentId); + if (document == null) + { + return null; + } + + var result = await state.GetAnalysisDataAsync(document, avoidLoadingData: false, cancellationToken: cancellationToken).ConfigureAwait(false); + return GetResult(result, kind, documentId); + } + + Contract.ThrowIfFalse(kind == AnalysisKind.NonLocal); + var nonLocalResult = await state.GetProjectAnalysisDataAsync(project, avoidLoadingData: false, cancellationToken: cancellationToken).ConfigureAwait(false); + return nonLocalResult.Others; + } } - public override async Task> GetProjectDiagnosticsForIdsAsync(Solution solution, ProjectId projectId = null, ImmutableHashSet diagnosticIds = null, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken)) + private class IDELatestDiagnosticGetter : DiagnosticGetter { - var diagnostics = await GetDiagnosticsForIdsAsync(solution, projectId, null, diagnosticIds, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); - return diagnostics.Where(d => d.DocumentId == null).ToImmutableArray(); + private readonly ImmutableHashSet _diagnosticIds; + + public IDELatestDiagnosticGetter(DiagnosticIncrementalAnalyzer owner, Solution solution, object id, bool includeSuppressedDiagnostics) : + base(owner, solution, projectId: null, documentId: null, id: id, includeSuppressedDiagnostics: includeSuppressedDiagnostics) + { + _diagnosticIds = null; + } + + public IDELatestDiagnosticGetter(DiagnosticIncrementalAnalyzer owner, Solution solution, ProjectId projectId, DocumentId documentId, bool includeSuppressedDiagnostics) : + base(owner, solution, projectId, documentId, id: null, includeSuppressedDiagnostics: includeSuppressedDiagnostics) + { + _diagnosticIds = null; + } + + public IDELatestDiagnosticGetter(DiagnosticIncrementalAnalyzer owner, ImmutableHashSet diagnosticIds, Solution solution, ProjectId projectId, bool includeSuppressedDiagnostics) : + this(owner, diagnosticIds, solution, projectId, documentId: null, includeSuppressedDiagnostics: includeSuppressedDiagnostics) + { + } + + public IDELatestDiagnosticGetter(DiagnosticIncrementalAnalyzer owner, ImmutableHashSet diagnosticIds, Solution solution, ProjectId projectId, DocumentId documentId, bool includeSuppressedDiagnostics) : + base(owner, solution, projectId, documentId, id: null, includeSuppressedDiagnostics: includeSuppressedDiagnostics) + { + _diagnosticIds = diagnosticIds; + } + + public async Task> GetProjectDiagnosticsAsync(CancellationToken cancellationToken) + { + if (Solution == null) + { + return GetDiagnosticData(); + } + + DocumentId nullTargetDocumentId = null; + + if (ProjectId != null) + { + await AppendDiagnosticsAsync(Project, nullTargetDocumentId, SpecializedCollections.EmptyEnumerable(), cancellationToken).ConfigureAwait(false); + return GetDiagnosticData(); + } + + await AppendDiagnosticsAsync(Solution, cancellationToken).ConfigureAwait(false); + return GetDiagnosticData(); + } + + protected override bool ShouldIncludeDiagnostic(DiagnosticData diagnostic) + { + return _diagnosticIds == null || _diagnosticIds.Contains(diagnostic.Id); + } + + protected override async Task AppendDiagnosticsAsync(Project project, DocumentId targetDocumentId, IEnumerable documentIds, CancellationToken cancellationToken) + { + // when we return cached result, make sure we at least return something that exist in current solution + if (Project == null) + { + return; + } + + // get analyzers that are not suppressed. + // REVIEW: IsAnalyzerSuppressed call seems can be quite expensive in certain condition. is there any other way to do this? + var stateSets = StateManager.GetOrCreateStateSets(project).Where(s => ShouldIncludeStateSet(project, s)).ToImmutableArrayOrEmpty(); + + var concurrentAnalysis = true; + var analyzerDriver = await Owner._compilationManager.CreateAnalyzerDriverAsync(project, stateSets, concurrentAnalysis, IncludeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); + + var result = await Owner._executor.GetProjectAnalysisDataAsync(analyzerDriver, project, stateSets, cancellationToken).ConfigureAwait(false); + + foreach (var stateSet in stateSets) + { + var analysisResult = result.GetResult(stateSet.Analyzer); + + foreach (var documentId in documentIds) + { + AppendDiagnostics(GetResult(analysisResult, AnalysisKind.Syntax, documentId)); + AppendDiagnostics(GetResult(analysisResult, AnalysisKind.Semantic, documentId)); + AppendDiagnostics(GetResult(analysisResult, AnalysisKind.NonLocal, documentId)); + } + + if (targetDocumentId == null) + { + // include project diagnostics if there is no target document + AppendDiagnostics(analysisResult.Others); + } + } + } + + private bool ShouldIncludeStateSet(Project project, StateSet stateSet) + { + // REVIEW: this can be expensive. any way to do this cheaper? + var diagnosticService = Owner.Owner; + if (diagnosticService.IsAnalyzerSuppressed(stateSet.Analyzer, project)) + { + return false; + } + + if (_diagnosticIds != null && diagnosticService.GetDiagnosticDescriptors(stateSet.Analyzer).All(d => !_diagnosticIds.Contains(d.Id))) + { + return false; + } + + return true; + } + + protected override async Task?> GetDiagnosticsAsync(StateSet stateSet, Project project, DocumentId documentId, AnalysisKind kind, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var concurrentAnalysis = true; + var stateSets = SpecializedCollections.SingletonCollection(stateSet); + var analyzerDriver = await Owner._compilationManager.CreateAnalyzerDriverAsync(project, stateSets, concurrentAnalysis, IncludeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); + + if (documentId != null) + { + var document = Solution.GetDocument(documentId); + Contract.ThrowIfNull(document); + + switch (kind) + { + case AnalysisKind.Syntax: + case AnalysisKind.Semantic: + { + var result = await Owner._executor.GetDocumentAnalysisDataAsync(analyzerDriver, document, stateSet, kind, cancellationToken).ConfigureAwait(false); + return result.Items; + } + case AnalysisKind.NonLocal: + { + var nonLocalDocumentResult = await Owner._executor.GetProjectAnalysisDataAsync(analyzerDriver, project, stateSets, cancellationToken).ConfigureAwait(false); + var analysisResult = nonLocalDocumentResult.GetResult(stateSet.Analyzer); + return GetResult(analysisResult, AnalysisKind.NonLocal, documentId); + } + default: + return Contract.FailWithReturn?>("shouldn't reach here"); + } + } + + Contract.ThrowIfFalse(kind == AnalysisKind.NonLocal); + var projectResult = await Owner._executor.GetProjectAnalysisDataAsync(analyzerDriver, project, stateSets, cancellationToken).ConfigureAwait(false); + return projectResult.GetResult(stateSet.Analyzer).Others; + } } } } \ No newline at end of file diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs index e18b7d8c9651f..9b2daea2af9d5 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs @@ -1,27 +1,426 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 { - // TODO: we need to do what V1 does for LB for span. internal partial class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncrementalAnalyzer { public override async Task TryAppendDiagnosticsForSpanAsync(Document document, TextSpan range, List result, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken)) { - result.AddRange(await GetDiagnosticsForSpanAsync(document, range, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false)); - return true; + var blockForData = false; + var getter = await LatestDiagnosticsForSpanGetter.CreateAsync(this, document, range, blockForData, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); + return await getter.TryGetAsync(result, cancellationToken).ConfigureAwait(false); } public override async Task> GetDiagnosticsForSpanAsync(Document document, TextSpan range, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken)) { - var diagnostics = await GetDiagnosticsAsync(document.Project.Solution, document.Project.Id, document.Id, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); - return diagnostics.Where(d => range.IntersectsWith(d.TextSpan)); + var blockForData = true; + var getter = await LatestDiagnosticsForSpanGetter.CreateAsync(this, document, range, blockForData, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); + + var list = new List(); + var result = await getter.TryGetAsync(list, cancellationToken).ConfigureAwait(false); + Contract.Requires(result); + + return list; + } + + /// + /// Get diagnostics for given span either by using cache or calculating it on the spot. + /// + private class LatestDiagnosticsForSpanGetter + { + private readonly DiagnosticIncrementalAnalyzer _owner; + private readonly Project _project; + private readonly Document _document; + + private readonly IEnumerable _stateSets; + private readonly CompilationWithAnalyzers _analyzerDriver; + private readonly DiagnosticAnalyzer _compilerAnalyzer; + + private readonly TextSpan _range; + private readonly bool _blockForData; + private readonly bool _includeSuppressedDiagnostics; + + // cache of project result + private ImmutableDictionary _projectResultCache; + + private delegate Task> DiagnosticsGetterAsync(DiagnosticAnalyzer analyzer, CancellationToken cancellationToken); + + public static async Task CreateAsync( + DiagnosticIncrementalAnalyzer owner, Document document, TextSpan range, bool blockForData, bool includeSuppressedDiagnostics, CancellationToken cancellationToken) + { + var concurrentAnalysis = false; + var stateSets = owner._stateManager.GetOrCreateStateSets(document.Project); + var analyzerDriver = await owner._compilationManager.CreateAnalyzerDriverAsync(document.Project, stateSets, concurrentAnalysis, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); + + return new LatestDiagnosticsForSpanGetter(owner, document, stateSets, analyzerDriver, range, blockForData, includeSuppressedDiagnostics); + } + + private LatestDiagnosticsForSpanGetter( + DiagnosticIncrementalAnalyzer owner, + Document document, + IEnumerable stateSets, + CompilationWithAnalyzers analyzerDriver, + TextSpan range, bool blockForData, bool includeSuppressedDiagnostics) + { + _owner = owner; + + _project = document.Project; + _document = document; + + _stateSets = stateSets; + _analyzerDriver = analyzerDriver; + _compilerAnalyzer = _owner.HostAnalyzerManager.GetCompilerDiagnosticAnalyzer(_document.Project.Language); + + _range = range; + _blockForData = blockForData; + _includeSuppressedDiagnostics = includeSuppressedDiagnostics; + } + + public async Task TryGetAsync(List list, CancellationToken cancellationToken) + { + try + { + var containsFullResult = true; + foreach (var stateSet in _stateSets) + { + containsFullResult &= await TryGetSyntaxAndSemanticDiagnosticsAsync(stateSet, list, cancellationToken).ConfigureAwait(false); + + // check whether compilation end code fix is enabled + if (!_document.Project.Solution.Workspace.Options.GetOption(InternalDiagnosticsOptions.CompilationEndCodeFix)) + { + continue; + } + + // check whether heuristic is enabled + if (_blockForData && _document.Project.Solution.Workspace.Options.GetOption(InternalDiagnosticsOptions.UseCompilationEndCodeFixHeuristic)) + { + var avoidLoadingData = true; + var state = stateSet.GetProjectState(_project.Id); + var result = await state.GetAnalysisDataAsync(_document, avoidLoadingData, cancellationToken).ConfigureAwait(false); + + // no previous compilation end diagnostics in this file. + var version = await GetDiagnosticVersionAsync(_project, cancellationToken).ConfigureAwait(false); + if (state.IsEmpty(_document.Id) || result.Version != version) + { + continue; + } + } + + containsFullResult &= await TryGetProjectDiagnosticsAsync(stateSet, GetProjectDiagnosticsAsync, list, cancellationToken).ConfigureAwait(false); + } + + // if we are blocked for data, then we should always have full result. + Contract.Requires(!_blockForData || containsFullResult); + return containsFullResult; + } + catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) + { + throw ExceptionUtilities.Unreachable; + } + } + + private async Task TryGetSyntaxAndSemanticDiagnosticsAsync(StateSet stateSet, List list, CancellationToken cancellationToken) + { + // 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) + { + return await TryGetSyntaxAndSemanticCompilerDiagnostics(stateSet, list, cancellationToken).ConfigureAwait(false); + } + + var fullResult = true; + fullResult &= await TryGetDocumentDiagnosticsAsync(stateSet, AnalysisKind.Syntax, GetSyntaxDiagnosticsAsync, list, cancellationToken).ConfigureAwait(false); + fullResult &= await TryGetDocumentDiagnosticsAsync(stateSet, AnalysisKind.Semantic, GetSemanticDiagnosticsAsync, list, cancellationToken).ConfigureAwait(false); + + return fullResult; + } + + private async Task TryGetSyntaxAndSemanticCompilerDiagnostics(StateSet stateSet, List list, CancellationToken cancellationToken) + { + // First, get syntax errors and semantic errors + var fullResult = true; + fullResult &= await TryGetDocumentDiagnosticsAsync(stateSet, AnalysisKind.Syntax, GetCompilerSyntaxDiagnosticsAsync, list, cancellationToken).ConfigureAwait(false); + fullResult &= await TryGetDocumentDiagnosticsAsync(stateSet, AnalysisKind.Semantic, GetCompilerSemanticDiagnosticsAsync, list, cancellationToken).ConfigureAwait(false); + + return fullResult; + } + + private async Task> GetCompilerSyntaxDiagnosticsAsync(DiagnosticAnalyzer analyzer, CancellationToken cancellationToken) + { + var root = await _document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var diagnostics = root.GetDiagnostics(); + + return _owner._executor.ConvertToLocalDiagnostics(_document, diagnostics, _range); + } + + private async Task> GetCompilerSemanticDiagnosticsAsync(DiagnosticAnalyzer analyzer, CancellationToken cancellationToken) + { + var model = await _document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + VerifyDiagnostics(model); + + var root = await _document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var adjustedSpan = AdjustSpan(_document, root, _range); + var diagnostics = model.GetDeclarationDiagnostics(adjustedSpan, cancellationToken).Concat(model.GetMethodBodyDiagnostics(adjustedSpan, cancellationToken)); + + return _owner._executor.ConvertToLocalDiagnostics(_document, diagnostics, _range); + } + + private Task> GetSyntaxDiagnosticsAsync(DiagnosticAnalyzer analyzer, CancellationToken cancellationToken) + { + return _owner._executor.ComputeDiagnosticsAsync(_analyzerDriver, _document, analyzer, AnalysisKind.Syntax, _range, cancellationToken); + } + + private Task> GetSemanticDiagnosticsAsync(DiagnosticAnalyzer analyzer, CancellationToken cancellationToken) + { + var supportsSemanticInSpan = analyzer.SupportsSpanBasedSemanticDiagnosticAnalysis(); + + var analysisSpan = supportsSemanticInSpan ? (TextSpan?)_range : null; + return _owner._executor.ComputeDiagnosticsAsync(_analyzerDriver, _document, analyzer, AnalysisKind.Semantic, analysisSpan, cancellationToken); + } + + private async Task> GetProjectDiagnosticsAsync(DiagnosticAnalyzer analyzer, CancellationToken cancellationToken) + { + if (_projectResultCache == null) + { + // execute whole project as one shot and cache the result. + _projectResultCache = await _owner._executor.ComputeDiagnosticsAsync(_analyzerDriver, _project, _stateSets, cancellationToken).ConfigureAwait(false); + } + + AnalysisResult result; + if (!_projectResultCache.TryGetValue(analyzer, out result)) + { + return ImmutableArray.Empty; + } + + return GetResult(result, AnalysisKind.NonLocal, _document.Id); + } + + [Conditional("DEBUG")] + private void VerifyDiagnostics(SemanticModel model) + { +#if DEBUG + // Exclude unused import diagnostics since they are never reported when a span is passed. + // (See CSharp/VisualBasicCompilation.GetDiagnosticsForMethodBodiesInTree.) + Func shouldInclude = d => _range.IntersectsWith(d.Location.SourceSpan) && !IsUnusedImportDiagnostic(d); + + // make sure what we got from range is same as what we got from whole diagnostics + var rangeDeclaractionDiagnostics = model.GetDeclarationDiagnostics(_range).ToArray(); + var rangeMethodBodyDiagnostics = model.GetMethodBodyDiagnostics(_range).ToArray(); + var rangeDiagnostics = rangeDeclaractionDiagnostics.Concat(rangeMethodBodyDiagnostics).Where(shouldInclude).ToArray(); + + var wholeDeclarationDiagnostics = model.GetDeclarationDiagnostics().ToArray(); + var wholeMethodBodyDiagnostics = model.GetMethodBodyDiagnostics().ToArray(); + var wholeDiagnostics = wholeDeclarationDiagnostics.Concat(wholeMethodBodyDiagnostics).Where(shouldInclude).ToArray(); + + if (!AreEquivalent(rangeDiagnostics, wholeDiagnostics)) + { + // otherwise, report non-fatal watson so that we can fix those cases + FatalError.ReportWithoutCrash(new Exception("Bug in GetDiagnostics")); + + // make sure we hold onto these for debugging. + GC.KeepAlive(rangeDeclaractionDiagnostics); + GC.KeepAlive(rangeMethodBodyDiagnostics); + GC.KeepAlive(rangeDiagnostics); + GC.KeepAlive(wholeDeclarationDiagnostics); + GC.KeepAlive(wholeMethodBodyDiagnostics); + GC.KeepAlive(wholeDiagnostics); + } +#endif + } + + private static bool IsUnusedImportDiagnostic(Diagnostic d) + { + switch (d.Id) + { + case "CS8019": + case "BC50000": + case "BC50001": + return true; + default: + return false; + } + } + + private static TextSpan AdjustSpan(Document document, SyntaxNode root, TextSpan span) + { + // this is to workaround a bug (https://github.com/dotnet/roslyn/issues/1557) + // once that bug is fixed, we should be able to use given span as it is. + + var service = document.GetLanguageService(); + var startNode = service.GetContainingMemberDeclaration(root, span.Start); + var endNode = service.GetContainingMemberDeclaration(root, span.End); + + if (startNode == endNode) + { + // use full member span + if (service.IsMethodLevelMember(startNode)) + { + return startNode.FullSpan; + } + + // use span as it is + return span; + } + + var startSpan = service.IsMethodLevelMember(startNode) ? startNode.FullSpan : span; + var endSpan = service.IsMethodLevelMember(endNode) ? endNode.FullSpan : span; + + return TextSpan.FromBounds(Math.Min(startSpan.Start, endSpan.Start), Math.Max(startSpan.End, endSpan.End)); + } + + private async Task TryGetDocumentDiagnosticsAsync( + StateSet stateSet, + AnalysisKind kind, + DiagnosticsGetterAsync diagnosticGetterAsync, + List list, + CancellationToken cancellationToken) + { + // REVIEW: IsAnalyzerSuppressed can be quite expensive in some cases. try to find a way to make it cheaper + if (!_owner.SupportAnalysisKind(stateSet.Analyzer, stateSet.Language, kind) || + _owner.Owner.IsAnalyzerSuppressed(stateSet.Analyzer, _document.Project)) + { + return true; + } + + // make sure we get state even when none of our analyzer has ran yet. + // but this shouldn't create analyzer that doesn't belong to this project (language) + var state = stateSet.GetActiveFileState(_document.Id); + + // see whether we can use existing info + var existingData = state.GetAnalysisData(kind); + var version = await GetDiagnosticVersionAsync(_document.Project, cancellationToken).ConfigureAwait(false); + if (existingData.Version == version) + { + if (existingData.Items.IsEmpty) + { + return true; + } + + list.AddRange(existingData.Items.Where(ShouldInclude)); + return true; + } + + // check whether we want up-to-date document wide diagnostics + var supportsSemanticInSpan = stateSet.Analyzer.SupportsSpanBasedSemanticDiagnosticAnalysis(); + if (!BlockForData(kind, supportsSemanticInSpan)) + { + return false; + } + + var dx = await diagnosticGetterAsync(stateSet.Analyzer, cancellationToken).ConfigureAwait(false); + if (dx != null) + { + // no state yet + list.AddRange(dx.Where(ShouldInclude)); + } + + return true; + } + + private async Task TryGetProjectDiagnosticsAsync( + StateSet stateSet, + DiagnosticsGetterAsync diagnosticGetterAsync, + List list, + CancellationToken cancellationToken) + { + // REVIEW: IsAnalyzerSuppressed can be quite expensive in some cases. try to find a way to make it cheaper + if (!stateSet.Analyzer.SupportsProjectDiagnosticAnalysis() || + _owner.Owner.IsAnalyzerSuppressed(stateSet.Analyzer, _document.Project)) + { + return true; + } + + // make sure we get state even when none of our analyzer has ran yet. + // but this shouldn't create analyzer that doesn't belong to this project (language) + var state = stateSet.GetProjectState(_document.Project.Id); + + // see whether we can use existing info + var result = await state.GetAnalysisDataAsync(_document, avoidLoadingData: true, cancellationToken: cancellationToken).ConfigureAwait(false); + var version = await GetDiagnosticVersionAsync(_document.Project, cancellationToken).ConfigureAwait(false); + if (result.Version == version) + { + var existingData = GetResult(result, AnalysisKind.NonLocal, _document.Id); + if (existingData.IsEmpty) + { + return true; + } + + list.AddRange(existingData.Where(ShouldInclude)); + return true; + } + + // check whether we want up-to-date document wide diagnostics + var supportsSemanticInSpan = stateSet.Analyzer.SupportsSpanBasedSemanticDiagnosticAnalysis(); + if (!BlockForData(AnalysisKind.NonLocal, supportsSemanticInSpan)) + { + return false; + } + + var dx = await diagnosticGetterAsync(stateSet.Analyzer, cancellationToken).ConfigureAwait(false); + if (dx != null) + { + // no state yet + list.AddRange(dx.Where(ShouldInclude)); + } + + return true; + } + + private bool ShouldInclude(DiagnosticData diagnostic) + { + return diagnostic.DocumentId == _document.Id && _range.IntersectsWith(diagnostic.TextSpan) && (_includeSuppressedDiagnostics || !diagnostic.IsSuppressed); + } + + private bool BlockForData(AnalysisKind kind, bool supportsSemanticInSpan) + { + if (kind == AnalysisKind.Semantic && !supportsSemanticInSpan && !_blockForData) + { + return false; + } + + if (kind == AnalysisKind.NonLocal && !_blockForData) + { + return false; + } + + return true; + } + } + +#if DEBUG + internal static bool AreEquivalent(Diagnostic[] diagnosticsA, Diagnostic[] diagnosticsB) + { + var set = new HashSet(diagnosticsA, DiagnosticComparer.Instance); + return set.SetEquals(diagnosticsB); + } + + private sealed class DiagnosticComparer : IEqualityComparer + { + internal static readonly DiagnosticComparer Instance = new DiagnosticComparer(); + + public bool Equals(Diagnostic x, Diagnostic y) + { + return x.Id == y.Id && x.Location == y.Location; + } + + public int GetHashCode(Diagnostic obj) + { + return Hash.Combine(obj.Id.GetHashCode(), obj.Location.GetHashCode()); + } } +#endif } } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs index 4ab5d95895d67..6e2eb81973a55 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs @@ -1,10 +1,13 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 @@ -12,139 +15,403 @@ namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 // TODO: make it to use cache internal partial class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncrementalAnalyzer { - public async override Task AnalyzeSyntaxAsync(Document document, CancellationToken cancellationToken) + public override Task AnalyzeSyntaxAsync(Document document, CancellationToken cancellationToken) { - if (!AnalysisEnabled(document)) - { - return; - } + return AnalyzeDocumentForKindAsync(document, AnalysisKind.Syntax, cancellationToken); + } - // TODO: make active file state to cache compilationWithAnalyzer - // REVIEW: this is really wierd that we need compilation for syntax diagnostics which basically defeat any reason - // we have syntax diagnostics. - var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); - var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + public override Task AnalyzeDocumentAsync(Document document, SyntaxNode bodyOpt, CancellationToken cancellationToken) + { + return AnalyzeDocumentForKindAsync(document, AnalysisKind.Semantic, cancellationToken); + } - // TODO: make it to use state manager - var analyzers = HostAnalyzerManager.CreateDiagnosticAnalyzers(document.Project); + private async Task AnalyzeDocumentForKindAsync(Document document, AnalysisKind kind, CancellationToken cancellationToken) + { + try + { + if (!AnalysisEnabled(document)) + { + // to reduce allocations, here, we don't clear existing diagnostics since it is dealt by other entry point such as + // DocumentReset or DocumentClosed. + return; + } - // Create driver that holds onto compilation and associated analyzers - // TODO: use CompilationWithAnalyzerOption instead of AnalyzerOption so that we can have exception filter and etc - var analyzerDriver = compilation.WithAnalyzers(analyzers, document.Project.AnalyzerOptions, cancellationToken); + var stateSets = _stateManager.GetOrUpdateStateSets(document.Project); + var analyzerDriver = await _compilationManager.GetAnalyzerDriverAsync(document.Project, stateSets, cancellationToken).ConfigureAwait(false); - foreach (var analyzer in analyzers) - { - // TODO: implement perf optimization not to run analyzers that are not needed. - // REVIEW: more unnecessary allocations just to get diagnostics per analyzer - var oneAnalyzers = ImmutableArray.Create(analyzer); + foreach (var stateSet in stateSets) + { + var analyzer = stateSet.Analyzer; - // TODO: use cache for perf optimization - var diagnostics = await analyzerDriver.GetAnalyzerSyntaxDiagnosticsAsync(tree, oneAnalyzers, cancellationToken).ConfigureAwait(false); + var result = await _executor.GetDocumentAnalysisDataAsync(analyzerDriver, document, stateSet, kind, cancellationToken).ConfigureAwait(false); + if (result.FromCache) + { + RaiseDocumentDiagnosticsIfNeeded(document, stateSet, kind, result.Items); + continue; + } - // we only care about local diagnostics - var diagnosticData = GetDiagnosticData(document.Project, diagnostics).Where(d => d.DocumentId == document.Id); + // no cancellation after this point. + var state = stateSet.GetActiveFileState(document.Id); + state.Save(kind, result.ToPersistData()); - // TODO: update using right arguments - Owner.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs.DiagnosticsCreated( - ValueTuple.Create(this, "Syntax", document.Id), document.Project.Solution.Workspace, document.Project.Solution, document.Project.Id, document.Id, diagnosticData.ToImmutableArrayOrEmpty())); + RaiseDocumentDiagnosticsIfNeeded(document, stateSet, kind, result.OldItems, result.Items); + } + } + catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) + { + throw ExceptionUtilities.Unreachable; } } - public override async Task AnalyzeDocumentAsync(Document document, SyntaxNode bodyOpt, CancellationToken cancellationToken) + public override async Task AnalyzeProjectAsync(Project project, bool semanticsChanged, CancellationToken cancellationToken) { - if (!AnalysisEnabled(document)) + try { - return; - } + var stateSets = _stateManager.GetOrUpdateStateSets(project); - // TODO: make active file state to cache compilationWithAnalyzer - var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); - var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + // get analyzers that are not suppressed. + // REVIEW: IsAnalyzerSuppressed call seems can be quite expensive in certain condition. is there any other way to do this? + var activeAnalyzers = stateSets.Select(s => s.Analyzer).Where(a => !Owner.IsAnalyzerSuppressed(a, project)).ToImmutableArrayOrEmpty(); - // TODO: make it to use state manager - var analyzers = HostAnalyzerManager.CreateDiagnosticAnalyzers(document.Project); + // get driver only with active analyzers. + var includeSuppressedDiagnostics = true; + var analyzerDriver = await _compilationManager.CreateAnalyzerDriverAsync(project, activeAnalyzers, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); - // Create driver that holds onto compilation and associated analyzers - // TODO: use CompilationWithAnalyzerOption instead of AnalyzerOption so that we can have exception filter and etc - var analyzerDriver = compilation.WithAnalyzers(analyzers, document.Project.AnalyzerOptions, cancellationToken); + var result = await _executor.GetProjectAnalysisDataAsync(analyzerDriver, project, stateSets, cancellationToken).ConfigureAwait(false); + if (result.FromCache) + { + RaiseProjectDiagnosticsIfNeeded(project, stateSets, result.Result); + return; + } - var noSpanFilter = default(TextSpan?); - foreach (var analyzer in analyzers) - { - // REVIEW: more unnecessary allocations just to get diagnostics per analyzer - var oneAnalyzers = ImmutableArray.Create(analyzer); + // no cancellation after this point. + foreach (var stateSet in stateSets) + { + var state = stateSet.GetProjectState(project.Id); + await state.SaveAsync(project, result.GetResult(stateSet.Analyzer)).ConfigureAwait(false); + } - // TODO: use cache for perf optimization - // REVIEW: I think we don't even need member tracking optimization - var diagnostics = await analyzerDriver.GetAnalyzerSemanticDiagnosticsAsync(model, noSpanFilter, oneAnalyzers, cancellationToken).ConfigureAwait(false); + RaiseProjectDiagnosticsIfNeeded(project, stateSets, result.OldResult, result.Result); + } + catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) + { + throw ExceptionUtilities.Unreachable; + } + } - var diagnosticData = GetDiagnosticData(document.Project, diagnostics).Where(d => d.DocumentId == document.Id); + public override Task DocumentOpenAsync(Document document, CancellationToken cancellationToken) + { + // let other component knows about this event + _compilationManager.OnDocumentOpened(); - // TODO: update using right arguments - Owner.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs.DiagnosticsCreated( - ValueTuple.Create(this, "Semantic", document.Id), document.Project.Solution.Workspace, document.Project.Solution, document.Project.Id, document.Id, diagnosticData.ToImmutableArrayOrEmpty())); - } + // here we dont need to raise any event, it will be taken cared by analyze methods. + return SpecializedTasks.EmptyTask; } public override Task DocumentCloseAsync(Document document, CancellationToken cancellationToken) { - // TODO: at this event, if file being closed is active file (the one in ActiveFileState), we should put that data into - // ProjectState + var stateSets = _stateManager.GetStateSets(document.Project); + + // let other components knows about this event + _compilationManager.OnDocumentClosed(); + var changed = _stateManager.OnDocumentClosed(stateSets, document.Id); + + // replace diagnostics from project state over active file state + RaiseLocalDocumentEventsFromProjectOverActiveFile(stateSets, document, changed); + return SpecializedTasks.EmptyTask; } public override Task DocumentResetAsync(Document document, CancellationToken cancellationToken) { - // REVIEW: this should reset both active file and project state the document belong to. + var stateSets = _stateManager.GetStateSets(document.Project); + + // let other components knows about this event + _compilationManager.OnDocumentReset(); + var changed = _stateManager.OnDocumentReset(stateSets, document.Id); + + // replace diagnostics from project state over active file state + RaiseLocalDocumentEventsFromProjectOverActiveFile(stateSets, document, changed); + return SpecializedTasks.EmptyTask; } public override void RemoveDocument(DocumentId documentId) { - // TODO: do proper eventing - Owner.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs.DiagnosticsRemoved( - ValueTuple.Create(this, "Syntax", documentId), Workspace, null, null, null)); + var stateSets = _stateManager.GetStateSets(documentId.ProjectId); - Owner.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs.DiagnosticsRemoved( - ValueTuple.Create(this, "Semantic", documentId), Workspace, null, null, null)); + // let other components knows about this event + _compilationManager.OnDocumentRemoved(); + var changed = _stateManager.OnDocumentRemoved(stateSets, documentId); - Owner.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs.DiagnosticsRemoved( - ValueTuple.Create(this, documentId), Workspace, null, null, null)); + // if there was no diagnostic reported for this document, nothing to clean up + if (!changed) + { + // this is Perf to reduce raising events unnecessarily. + return; + } + + // remove all diagnostics for the document + Owner.RaiseBulkDiagnosticsUpdated(raiseEvents => + { + Solution nullSolution = null; + foreach (var stateSet in stateSets) + { + // clear all doucment diagnostics + RaiseDiagnosticsRemoved(documentId, nullSolution, stateSet, AnalysisKind.Syntax, raiseEvents); + RaiseDiagnosticsRemoved(documentId, nullSolution, stateSet, AnalysisKind.Semantic, raiseEvents); + RaiseDiagnosticsRemoved(documentId, nullSolution, stateSet, AnalysisKind.NonLocal, raiseEvents); + } + }); } - public override async Task AnalyzeProjectAsync(Project project, bool semanticsChanged, CancellationToken cancellationToken) + public override void RemoveProject(ProjectId projectId) { - if (!FullAnalysisEnabled(project.Solution.Workspace, project.Language)) + var stateSets = _stateManager.GetStateSets(projectId); + + // let other components knows about this event + _compilationManager.OnProjectRemoved(); + var changed = _stateManager.OnProjectRemoved(stateSets, projectId); + + // if there was no diagnostic reported for this project, nothing to clean up + if (!changed) { - // TODO: check whether there is existing state, if so, raise events to remove them all. + // this is Perf to reduce raising events unnecessarily. return; } - // TODO: make this to use cache. - // TODO: use CompilerDiagnosticExecutor - var diagnostics = await GetDiagnosticsAsync(project.Solution, project.Id, null, includeSuppressedDiagnostics: true, cancellationToken: cancellationToken).ConfigureAwait(false); + // remove all diagnostics for the project + Owner.RaiseBulkDiagnosticsUpdated(raiseEvents => + { + Solution nullSolution = null; + foreach (var stateSet in stateSets) + { + // clear all project diagnostics + RaiseDiagnosticsRemoved(projectId, nullSolution, stateSet, raiseEvents); + } + }); + } + + public override Task NewSolutionSnapshotAsync(Solution solution, CancellationToken cancellationToken) + { + // let other components knows about this event + _compilationManager.OnNewSolution(); - // TODO: do proper event - RaiseEvents(project, diagnostics); + return SpecializedTasks.EmptyTask; } - public override void RemoveProject(ProjectId projectId) + private static bool AnalysisEnabled(Document document) { - // TODO: do proper event - Owner.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs.DiagnosticsRemoved( - ValueTuple.Create(this, projectId), Workspace, null, null, null)); + // change it to check active file (or visible files), not open files if active file tracking is enabled. + // otherwise, use open file. + return document.IsOpen(); } - public override Task DocumentOpenAsync(Document document, CancellationToken cancellationToken) + private static ImmutableArray GetResult(AnalysisResult result, AnalysisKind kind, DocumentId id) { - // Review: I think we don't need to care about it - return SpecializedTasks.EmptyTask; + switch (kind) + { + case AnalysisKind.Syntax: + return result.GetResultOrEmpty(result.SyntaxLocals, id); + case AnalysisKind.Semantic: + return result.GetResultOrEmpty(result.SemanticLocals, id); + case AnalysisKind.NonLocal: + return result.GetResultOrEmpty(result.NonLocals, id); + default: + return Contract.FailWithReturn>("shouldn't reach here"); + } } - public override Task NewSolutionSnapshotAsync(Solution solution, CancellationToken cancellationToken) + private void RaiseLocalDocumentEventsFromProjectOverActiveFile(IEnumerable stateSets, Document document, bool activeFileDiagnosticExist) { - // we don't use this one. - return SpecializedTasks.EmptyTask; + // PERF: activeFileDiagnosticExist is perf optimization to reduce raising events unnecessarily. + + // this removes diagnostic reported by active file and replace those with ones from project. + Owner.RaiseBulkDiagnosticsUpdated(async raiseEvents => + { + // this basically means always load data + var avoidLoadingData = false; + + foreach (var stateSet in stateSets) + { + // get project state + var state = stateSet.GetProjectState(document.Project.Id); + + // this is perf optimization to reduce events; + if (!activeFileDiagnosticExist && state.IsEmpty(document.Id)) + { + // there is nothing reported before. we don't need to do anything. + continue; + } + + // no cancellation since event can't be cancelled. + // now get diagnostic information from project + var result = await state.GetAnalysisDataAsync(document, avoidLoadingData, CancellationToken.None).ConfigureAwait(false); + if (result.IsAggregatedForm) + { + // something made loading data failed. + // clear all existing diagnostics + RaiseDiagnosticsRemoved(document.Id, document.Project.Solution, stateSet, AnalysisKind.Syntax, raiseEvents); + RaiseDiagnosticsRemoved(document.Id, document.Project.Solution, stateSet, AnalysisKind.Semantic, raiseEvents); + continue; + } + + // we have data, do actual event raise that will replace diagnostics from active file + var syntaxItems = GetResult(result, AnalysisKind.Syntax, document.Id); + RaiseDiagnosticsCreated(document, stateSet, AnalysisKind.Syntax, syntaxItems, raiseEvents); + + var semanticItems = GetResult(result, AnalysisKind.Semantic, document.Id); + RaiseDiagnosticsCreated(document, stateSet, AnalysisKind.Semantic, semanticItems, raiseEvents); + } + }); + } + + private void RaiseProjectDiagnosticsIfNeeded( + Project project, + IEnumerable stateSets, + ImmutableDictionary result) + { + RaiseProjectDiagnosticsIfNeeded(project, stateSets, ImmutableDictionary.Empty, result); + } + + private void RaiseProjectDiagnosticsIfNeeded( + Project project, + IEnumerable stateSets, + ImmutableDictionary oldResult, + ImmutableDictionary newResult) + { + if (oldResult.Count == 0 && newResult.Count == 0) + { + // there is nothing to update + return; + } + + Owner.RaiseBulkDiagnosticsUpdated(raiseEvents => + { + foreach (var stateSet in stateSets) + { + var analyzer = stateSet.Analyzer; + + var oldAnalysisResult = ImmutableDictionary.GetValueOrDefault(oldResult, analyzer); + var newAnalysisResult = ImmutableDictionary.GetValueOrDefault(newResult, analyzer); + + // Perf - 4 different cases. + // upper 3 cases can be removed and it will still work. but this is hot path so if we can bail out + // without any allocations, that's better. + if (oldAnalysisResult.IsEmpty && newAnalysisResult.IsEmpty) + { + // nothing to do + continue; + } + + if (!oldAnalysisResult.IsEmpty && newAnalysisResult.IsEmpty) + { + // remove old diagnostics + RaiseProjectDiagnosticsRemoved(stateSet, oldAnalysisResult.ProjectId, oldAnalysisResult.DocumentIds, raiseEvents); + continue; + } + + if (oldAnalysisResult.IsEmpty && !newAnalysisResult.IsEmpty) + { + // add new diagnostics + RaiseProjectDiagnosticsCreated(project, stateSet, oldAnalysisResult, newAnalysisResult, raiseEvents); + continue; + } + + // both old and new has items in them. update existing items + + // first remove ones no longer needed. + var documentsToRemove = oldAnalysisResult.DocumentIds.Except(newAnalysisResult.DocumentIds); + RaiseProjectDiagnosticsRemoved(stateSet, oldAnalysisResult.ProjectId, documentsToRemove, raiseEvents); + + // next update or create new ones + RaiseProjectDiagnosticsCreated(project, stateSet, oldAnalysisResult, newAnalysisResult, raiseEvents); + } + }); + } + + private void RaiseDocumentDiagnosticsIfNeeded(Document document, StateSet stateSet, AnalysisKind kind, ImmutableArray items) + { + RaiseDocumentDiagnosticsIfNeeded(document, stateSet, kind, ImmutableArray.Empty, items); + } + + private void RaiseDocumentDiagnosticsIfNeeded( + Document document, StateSet stateSet, AnalysisKind kind, ImmutableArray oldItems, ImmutableArray newItems) + { + RaiseDocumentDiagnosticsIfNeeded(document, stateSet, kind, oldItems, newItems, Owner.RaiseDiagnosticsUpdated); + } + + private void RaiseDocumentDiagnosticsIfNeeded( + Document document, StateSet stateSet, AnalysisKind kind, + AnalysisResult oldResult, AnalysisResult newResult, + Action raiseEvents) + { + var oldItems = GetResult(oldResult, kind, document.Id); + var newItems = GetResult(newResult, kind, document.Id); + + RaiseDocumentDiagnosticsIfNeeded(document, stateSet, kind, oldItems, newItems, raiseEvents); + } + + private void RaiseDocumentDiagnosticsIfNeeded( + Document document, StateSet stateSet, AnalysisKind kind, + ImmutableArray oldItems, ImmutableArray newItems, + Action raiseEvents) + { + if (oldItems.IsEmpty && newItems.IsEmpty) + { + // there is nothing to update + return; + } + + RaiseDiagnosticsCreated(document, stateSet, kind, newItems, raiseEvents); + } + + private void RaiseProjectDiagnosticsCreated(Project project, StateSet stateSet, AnalysisResult oldAnalysisResult, AnalysisResult newAnalysisResult, Action raiseEvents) + { + foreach (var documentId in newAnalysisResult.DocumentIds) + { + var document = project.GetDocument(documentId); + Contract.ThrowIfNull(document); + + RaiseDocumentDiagnosticsIfNeeded(document, stateSet, AnalysisKind.NonLocal, oldAnalysisResult, newAnalysisResult, raiseEvents); + + // we don't raise events for active file. it will be taken cared by active file analysis + if (stateSet.IsActiveFile(documentId)) + { + continue; + } + + RaiseDocumentDiagnosticsIfNeeded(document, stateSet, AnalysisKind.Syntax, oldAnalysisResult, newAnalysisResult, raiseEvents); + RaiseDocumentDiagnosticsIfNeeded(document, stateSet, AnalysisKind.Semantic, oldAnalysisResult, newAnalysisResult, raiseEvents); + } + + RaiseDiagnosticsCreated(project, stateSet, newAnalysisResult.Others, raiseEvents); + } + + private void RaiseProjectDiagnosticsRemoved(StateSet stateSet, ProjectId projectId, IEnumerable documentIds, Action raiseEvents) + { + var handleActiveFile = false; + RaiseProjectDiagnosticsRemoved(stateSet, projectId, documentIds, handleActiveFile, raiseEvents); + } + + private void RaiseProjectDiagnosticsRemoved(StateSet stateSet, ProjectId projectId, IEnumerable documentIds, bool handleActiveFile, Action raiseEvents) + { + Solution nullSolution = null; + foreach (var documentId in documentIds) + { + RaiseDiagnosticsRemoved(documentId, nullSolution, stateSet, AnalysisKind.NonLocal, raiseEvents); + + // we don't raise events for active file. it will be taken cared by active file analysis + if (!handleActiveFile && stateSet.IsActiveFile(documentId)) + { + continue; + } + + RaiseDiagnosticsRemoved(documentId, nullSolution, stateSet, AnalysisKind.Syntax, raiseEvents); + RaiseDiagnosticsRemoved(documentId, nullSolution, stateSet, AnalysisKind.Semantic, raiseEvents); + } + + RaiseDiagnosticsRemoved(projectId, nullSolution, stateSet, raiseEvents); } } } diff --git a/src/Features/Core/Portable/Diagnostics/LiveDiagnosticUpdateArgsId.cs b/src/Features/Core/Portable/Diagnostics/LiveDiagnosticUpdateArgsId.cs index 150a54ddf9e6c..354163b2ef1c0 100644 --- a/src/Features/Core/Portable/Diagnostics/LiveDiagnosticUpdateArgsId.cs +++ b/src/Features/Core/Portable/Diagnostics/LiveDiagnosticUpdateArgsId.cs @@ -11,14 +11,11 @@ internal class LiveDiagnosticUpdateArgsId : AnalyzerUpdateArgsId public readonly object Key; public readonly int Kind; - public LiveDiagnosticUpdateArgsId(DiagnosticAnalyzer analyzer, object key, int kind) : - this(analyzer, key, kind, analyzerPackageName: null) - { - } - public LiveDiagnosticUpdateArgsId(DiagnosticAnalyzer analyzer, object key, int kind, string analyzerPackageName) : base(analyzer) { + Contract.ThrowIfNull(key); + Key = key; Kind = kind; diff --git a/src/Features/Core/Portable/Features.csproj b/src/Features/Core/Portable/Features.csproj index 4329b0d062201..eaaf2ff3737da 100644 --- a/src/Features/Core/Portable/Features.csproj +++ b/src/Features/Core/Portable/Features.csproj @@ -197,6 +197,18 @@ + + + + + + + + + + + + diff --git a/src/VisualStudio/Core/Def/Implementation/TaskList/ExternalErrorDiagnosticUpdateSource.cs b/src/VisualStudio/Core/Def/Implementation/TaskList/ExternalErrorDiagnosticUpdateSource.cs index 1d73e1507af9e..2c087d11ad81d 100644 --- a/src/VisualStudio/Core/Def/Implementation/TaskList/ExternalErrorDiagnosticUpdateSource.cs +++ b/src/VisualStudio/Core/Def/Implementation/TaskList/ExternalErrorDiagnosticUpdateSource.cs @@ -203,13 +203,8 @@ internal void OnSolutionBuild(object sender, UIContextChangedEventArgs e) var diagnosticService = _diagnosticService as DiagnosticAnalyzerService; if (diagnosticService != null) { - using (var batchUpdateToken = diagnosticService.BeginBatchBuildDiagnosticsUpdate(solution)) - { - await CleanupAllLiveErrorsIfNeededAsync(diagnosticService, batchUpdateToken, solution, inprogressState).ConfigureAwait(false); - - await SyncBuildErrorsAndReportAsync(diagnosticService, batchUpdateToken, solution, liveDiagnosticChecker, inprogressState.GetDocumentAndErrors(solution)).ConfigureAwait(false); - await SyncBuildErrorsAndReportAsync(diagnosticService, batchUpdateToken, solution, liveDiagnosticChecker, inprogressState.GetProjectAndErrors(solution)).ConfigureAwait(false); - } + await CleanupAllLiveErrorsIfNeededAsync(diagnosticService, solution, inprogressState).ConfigureAwait(false); + await SyncBuildErrorsAndReportAsync(diagnosticService, inprogressState.GetLiveDiagnosticsPerProject(liveDiagnosticChecker)).ConfigureAwait(false); } inprogressState.Done(); @@ -217,90 +212,66 @@ internal void OnSolutionBuild(object sender, UIContextChangedEventArgs e) }).CompletesAsyncOperation(asyncToken); } - private async System.Threading.Tasks.Task CleanupAllLiveErrorsIfNeededAsync( - DiagnosticAnalyzerService diagnosticService, IDisposable batchUpdateToken, - Solution solution, InprogressState state) + private async System.Threading.Tasks.Task CleanupAllLiveErrorsIfNeededAsync(DiagnosticAnalyzerService diagnosticService, Solution solution, InprogressState state) { if (_workspace.Options.GetOption(InternalDiagnosticsOptions.BuildErrorIsTheGod)) { - await CleanupAllLiveErrors(diagnosticService, batchUpdateToken, solution, state, solution.Projects).ConfigureAwait(false); + await CleanupAllLiveErrors(diagnosticService, solution.ProjectIds).ConfigureAwait(false); return; } if (_workspace.Options.GetOption(InternalDiagnosticsOptions.ClearLiveErrorsForProjectBuilt)) { - await CleanupAllLiveErrors(diagnosticService, batchUpdateToken, solution, state, state.GetProjectsBuilt(solution)).ConfigureAwait(false); + await CleanupAllLiveErrors(diagnosticService, state.GetProjectsBuilt(solution)).ConfigureAwait(false); return; } - await CleanupAllLiveErrors(diagnosticService, batchUpdateToken, solution, state, state.GetProjectsWithoutErrors(solution)).ConfigureAwait(false); + await CleanupAllLiveErrors(diagnosticService, state.GetProjectsWithoutErrors(solution)).ConfigureAwait(false); return; } - private static async System.Threading.Tasks.Task CleanupAllLiveErrors( - DiagnosticAnalyzerService diagnosticService, IDisposable batchUpdateToken, - Solution solution, InprogressState state, IEnumerable projects) + private System.Threading.Tasks.Task CleanupAllLiveErrors(DiagnosticAnalyzerService diagnosticService, IEnumerable projects) { - foreach (var project in projects) - { - foreach (var document in project.Documents) - { - await SynchronizeWithBuildAsync(diagnosticService, batchUpdateToken, document, ImmutableArray.Empty).ConfigureAwait(false); - } - - await SynchronizeWithBuildAsync(diagnosticService, batchUpdateToken, project, ImmutableArray.Empty).ConfigureAwait(false); - } + var map = projects.ToImmutableDictionary(p => p, _ => ImmutableArray.Empty); + return diagnosticService.SynchronizeWithBuildAsync(_workspace, map); } - private async System.Threading.Tasks.Task SyncBuildErrorsAndReportAsync( - DiagnosticAnalyzerService diagnosticService, IDisposable batchUpdateToken, Solution solution, - Func liveDiagnosticChecker, IEnumerable>> items) + private async System.Threading.Tasks.Task SyncBuildErrorsAndReportAsync(DiagnosticAnalyzerService diagnosticService, ImmutableDictionary> map) { - foreach (var kv in items) - { - // get errors that can be reported by live diagnostic analyzer - var liveErrors = kv.Value.Where(liveDiagnosticChecker).ToImmutableArray(); - - // make those errors live errors - await SynchronizeWithBuildAsync(diagnosticService, batchUpdateToken, kv.Key, liveErrors).ConfigureAwait(false); + // make those errors live errors + await diagnosticService.SynchronizeWithBuildAsync(_workspace, map).ConfigureAwait(false); - // raise events for ones left-out - if (liveErrors.Length != kv.Value.Count) + // raise events for ones left-out + var buildErrors = GetBuildErrors().Except(map.Values.SelectMany(v => v)).GroupBy(k => k.DocumentId); + foreach (var group in buildErrors) + { + if (group.Key == null) { - var buildErrors = kv.Value.Except(liveErrors).ToImmutableArray(); - ReportBuildErrors(kv.Key, buildErrors); + foreach (var projectGroup in group.GroupBy(g => g.ProjectId)) + { + Contract.ThrowIfNull(projectGroup.Key); + ReportBuildErrors(projectGroup.Key, projectGroup.ToImmutableArray()); + } + + continue; } - } - } - private static async System.Threading.Tasks.Task SynchronizeWithBuildAsync( - DiagnosticAnalyzerService diagnosticService, IDisposable batchUpdateToken, - T item, ImmutableArray liveErrors) - { - var project = item as Project; - if (project != null) - { - await diagnosticService.SynchronizeWithBuildAsync(batchUpdateToken, project, liveErrors).ConfigureAwait(false); - return; + ReportBuildErrors(group.Key, group.ToImmutableArray()); } - - // must be not null - var document = item as Document; - await diagnosticService.SynchronizeWithBuildAsync(batchUpdateToken, document, liveErrors).ConfigureAwait(false); } private void ReportBuildErrors(T item, ImmutableArray buildErrors) { - var project = item as Project; - if (project != null) + var projectId = item as ProjectId; + if (projectId != null) { - RaiseDiagnosticsCreated(project.Id, project.Id, null, buildErrors); + RaiseDiagnosticsCreated(projectId, projectId, null, buildErrors); return; } // must be not null - var document = item as Document; - RaiseDiagnosticsCreated(document.Id, document.Project.Id, document.Id, buildErrors); + var documentId = item as DocumentId; + RaiseDiagnosticsCreated(documentId, documentId.ProjectId, documentId, buildErrors); } private Dictionary> GetSupportedLiveDiagnosticId(Solution solution, InprogressState state) @@ -308,8 +279,14 @@ private Dictionary> GetSupportedLiveDiagnosticId(Solu var map = new Dictionary>(); // here, we don't care about perf that much since build is already expensive work - foreach (var project in state.GetProjectsWithErrors(solution)) + foreach (var projectId in state.GetProjectsWithErrors(solution)) { + var project = solution.GetProject(projectId); + if (project == null) + { + continue; + } + var descriptorMap = _diagnosticService.GetDiagnosticDescriptors(project); map.Add(project.Id, new HashSet(descriptorMap.Values.SelectMany(v => v.Select(d => d.Id)))); } @@ -442,56 +419,32 @@ public void Built(ProjectId projectId) _builtProjects.Add(projectId); } - public IEnumerable GetProjectsBuilt(Solution solution) + public IEnumerable GetProjectsBuilt(Solution solution) { - return solution.Projects.Where(p => _builtProjects.Contains(p.Id)); + return solution.ProjectIds.Where(p => _builtProjects.Contains(p)); } - public IEnumerable GetProjectsWithErrors(Solution solution) + public IEnumerable GetProjectsWithErrors(Solution solution) { - foreach (var projectId in _documentMap.Keys.Select(k => k.ProjectId).Concat(_projectMap.Keys).Distinct()) - { - var project = solution.GetProject(projectId); - if (project == null) - { - continue; - } - - yield return project; - } + return _documentMap.Keys.Select(k => k.ProjectId).Concat(_projectMap.Keys).Distinct(); } - public IEnumerable GetProjectsWithoutErrors(Solution solution) + public IEnumerable GetProjectsWithoutErrors(Solution solution) { return GetProjectsBuilt(solution).Except(GetProjectsWithErrors(solution)); } - public IEnumerable>> GetDocumentAndErrors(Solution solution) + public ImmutableDictionary> GetLiveDiagnosticsPerProject(Func liveDiagnosticChecker) { - foreach (var kv in _documentMap) + var builder = ImmutableDictionary.CreateBuilder>(); + foreach (var projectKv in _projectMap) { - var document = solution.GetDocument(kv.Key); - if (document == null) - { - continue; - } - - yield return KeyValuePair.Create(document, kv.Value); + // get errors that can be reported by live diagnostic analyzer + var diagnostics = ImmutableArray.CreateRange(projectKv.Value.Concat(_documentMap.Where(kv => kv.Key.ProjectId == projectKv.Key).SelectMany(kv => kv.Value)).Where(liveDiagnosticChecker)); + builder.Add(projectKv.Key, diagnostics); } - } - - public IEnumerable>> GetProjectAndErrors(Solution solution) - { - foreach (var kv in _projectMap) - { - var project = solution.GetProject(kv.Key); - if (project == null) - { - continue; - } - yield return KeyValuePair.Create(project, kv.Value); - } + return builder.ToImmutable(); } public void AddErrors(DocumentId key, HashSet diagnostics) @@ -511,17 +464,17 @@ public void AddError(DocumentId key, DiagnosticData diagnostic) private void AddErrors(Dictionary> map, T key, HashSet diagnostics) { - var errors = GetErrors(map, key); + var errors = GetErrorSet(map, key); errors.UnionWith(diagnostics); } private void AddError(Dictionary> map, T key, DiagnosticData diagnostic) { - var errors = GetErrors(map, key); + var errors = GetErrorSet(map, key); errors.Add(diagnostic); } - private HashSet GetErrors(Dictionary> map, T key) + private HashSet GetErrorSet(Dictionary> map, T key) { return map.GetOrAdd(key, _ => new HashSet(DiagnosticDataComparer.Instance)); } From 1362e5b6dca7b11575a30d77637e4c7e0b0c05ac Mon Sep 17 00:00:00 2001 From: Heejae Chang Date: Mon, 11 Apr 2016 05:09:06 -0700 Subject: [PATCH 08/24] added unit tests for diagnostic data serializer --- .../DiagnosticDataSerializerTests.cs | 291 ++++++++++++++++++ .../Test/EditorServicesTest.csproj | 3 +- 2 files changed, 293 insertions(+), 1 deletion(-) create mode 100644 src/EditorFeatures/Test/Diagnostics/DiagnosticDataSerializerTests.cs diff --git a/src/EditorFeatures/Test/Diagnostics/DiagnosticDataSerializerTests.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticDataSerializerTests.cs new file mode 100644 index 0000000000000..0f968fb01aecc --- /dev/null +++ b/src/EditorFeatures/Test/Diagnostics/DiagnosticDataSerializerTests.cs @@ -0,0 +1,291 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Diagnostics.EngineV2; +using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Test.Utilities; +using Roslyn.Utilities; +using Xunit; +using Traits = Microsoft.CodeAnalysis.Test.Utilities.Traits; + +namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics +{ + public class DiagnosticDataSerializerTests : TestBase + { + [Fact, Trait(Traits.Feature, Traits.Features.Diagnostics)] + public async Task SerializationTest_Document() + { + using (var workspace = new TestWorkspace(TestExportProvider.ExportProviderWithCSharpAndVisualBasic, workspaceKind: "DiagnosticDataSerializerTest")) + { + var document = workspace.CurrentSolution.AddProject("TestProject", "TestProject", LanguageNames.CSharp).AddDocument("TestDocument", ""); + + var diagnostics = new[] + { + new DiagnosticData( + "test1", "Test", "test1 message", "test1 message format", + DiagnosticSeverity.Info, DiagnosticSeverity.Info, false, 1, + ImmutableArray.Empty, ImmutableDictionary.Empty, + workspace, document.Project.Id, new DiagnosticDataLocation(document.Id, + new TextSpan(10, 20), "originalFile1", 30, 30, 40, 40, "mappedFile1", 10, 10, 20, 20)), + new DiagnosticData( + "test2", "Test", "test2 message", "test2 message format", + DiagnosticSeverity.Warning, DiagnosticSeverity.Warning, true, 0, + ImmutableArray.Create("Test2"), ImmutableDictionary.Empty.Add("propertyKey", "propertyValue"), + workspace, document.Project.Id, new DiagnosticDataLocation(document.Id, + new TextSpan(30, 40), "originalFile2", 70, 70, 80, 80, "mappedFile2", 50, 50, 60, 60), title: "test2 title", description: "test2 description", helpLink: "http://test2link"), + new DiagnosticData( + "test3", "Test", "test3 message", "test3 message format", + DiagnosticSeverity.Error, DiagnosticSeverity.Warning, true, 2, + ImmutableArray.Create("Test3", "Test3_2"), ImmutableDictionary.Empty.Add("p1Key", "p1Value").Add("p2Key", "p2Value"), + workspace, document.Project.Id, new DiagnosticDataLocation(document.Id, + new TextSpan(50, 60), "originalFile3", 110, 110, 120, 120, "mappedFile3", 90, 90, 100, 100), title: "test3 title", description: "test3 description", helpLink: "http://test3link"), + }.ToImmutableArray(); + + var utcTime = DateTime.UtcNow; + var analyzerVersion = VersionStamp.Create(utcTime); + var version = VersionStamp.Create(utcTime.AddDays(1)); + + var key = "document"; + var serializer = new DiagnosticDataSerializer(analyzerVersion, version); + + Assert.True(await serializer.SerializeAsync(document, key, diagnostics, CancellationToken.None).ConfigureAwait(false)); + var recovered = await serializer.DeserializeAsync(document, key, CancellationToken.None); + + AssertDiagnostics(diagnostics, recovered); + } + } + + [Fact, Trait(Traits.Feature, Traits.Features.Diagnostics)] + public async Task SerializationTest_Project() + { + using (var workspace = new TestWorkspace(TestExportProvider.ExportProviderWithCSharpAndVisualBasic, workspaceKind: "DiagnosticDataSerializerTest")) + { + var document = workspace.CurrentSolution.AddProject("TestProject", "TestProject", LanguageNames.CSharp).AddDocument("TestDocument", ""); + + var diagnostics = new[] + { + new DiagnosticData( + "test1", "Test", "test1 message", "test1 message format", + DiagnosticSeverity.Info, DiagnosticSeverity.Info, false, 1, + ImmutableArray.Empty, ImmutableDictionary.Empty, + workspace, document.Project.Id, description: "test1 description", helpLink: "http://test1link"), + new DiagnosticData( + "test2", "Test", "test2 message", "test2 message format", + DiagnosticSeverity.Warning, DiagnosticSeverity.Warning, true, 0, + ImmutableArray.Create("Test2"), ImmutableDictionary.Empty.Add("p1Key", "p2Value"), + workspace, document.Project.Id), + new DiagnosticData( + "test3", "Test", "test3 message", "test3 message format", + DiagnosticSeverity.Error, DiagnosticSeverity.Warning, true, 2, + ImmutableArray.Create("Test3", "Test3_2"), ImmutableDictionary.Empty.Add("p2Key", "p2Value").Add("p1Key", "p1Value"), + workspace, document.Project.Id, description: "test3 description", helpLink: "http://test3link"), + }.ToImmutableArray(); + + var utcTime = DateTime.UtcNow; + var analyzerVersion = VersionStamp.Create(utcTime); + var version = VersionStamp.Create(utcTime.AddDays(1)); + + var key = "project"; + var serializer = new DiagnosticDataSerializer(analyzerVersion, version); + + Assert.True(await serializer.SerializeAsync(document, key, diagnostics, CancellationToken.None).ConfigureAwait(false)); + var recovered = await serializer.DeserializeAsync(document, key, CancellationToken.None); + + AssertDiagnostics(diagnostics, recovered); + } + } + + [WorkItem(6104, "https://github.com/dotnet/roslyn/issues/6104")] + [Fact] + public void DiagnosticEquivalence() + { +#if DEBUG + var source = +@"class C +{ + static int F(string s) { return 1; } + static int x = F(new { }); + static int y = F(new { A = 1 }); +}"; + var tree = SyntaxFactory.ParseSyntaxTree(source); + var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, concurrentBuild: false); + var compilation = CSharpCompilation.Create(GetUniqueName(), new[] { tree }, new[] { MscorlibRef }, options); + var model = compilation.GetSemanticModel(tree); + + // Each call to GetDiagnostics will bind field initializers + // (see https://github.com/dotnet/roslyn/issues/6264). + var diagnostics1 = model.GetDiagnostics().ToArray(); + var diagnostics2 = model.GetDiagnostics().ToArray(); + + diagnostics1.Verify( + // (4,22): error CS1503: Argument 1: cannot convert from '' to 'string' + // static int x = F(new { }); + Diagnostic(1503, "new { }").WithArguments("1", "", "string").WithLocation(4, 22), + // (5,22): error CS1503: Argument 1: cannot convert from '' to 'string' + // static int y = F(new { A = 1 }); + Diagnostic(1503, "new { A = 1 }").WithArguments("1", "", "string").WithLocation(5, 22)); + + Assert.NotSame(diagnostics1[0], diagnostics2[0]); + Assert.NotSame(diagnostics1[1], diagnostics2[1]); + Assert.Equal(diagnostics1, diagnostics2); + Assert.True(DiagnosticIncrementalAnalyzer.AreEquivalent(diagnostics1, diagnostics2)); + + // Verify that not all collections are treated as equivalent. + diagnostics1 = new[] { diagnostics1[0] }; + diagnostics2 = new[] { diagnostics2[1] }; + + Assert.NotEqual(diagnostics1, diagnostics2); + Assert.False(DiagnosticIncrementalAnalyzer.AreEquivalent(diagnostics1, diagnostics2)); +#endif + } + + private static void AssertDiagnostics(ImmutableArray items1, ImmutableArray items2) + { + Assert.Equal(items1.Length, items2.Length); + + for (var i = 0; i < items1.Length; i++) + { + AssertDiagnostics(items1[i], items2[i]); + } + } + + private static void AssertDiagnostics(DiagnosticData item1, DiagnosticData item2) + { + Assert.Equal(item1.Id, item2.Id); + Assert.Equal(item1.Category, item2.Category); + Assert.Equal(item1.Message, item2.Message); + Assert.Equal(item1.ENUMessageForBingSearch, item2.ENUMessageForBingSearch); + Assert.Equal(item1.Severity, item2.Severity); + Assert.Equal(item1.IsEnabledByDefault, item2.IsEnabledByDefault); + Assert.Equal(item1.WarningLevel, item2.WarningLevel); + Assert.Equal(item1.DefaultSeverity, item2.DefaultSeverity); + + Assert.Equal(item1.CustomTags.Count, item2.CustomTags.Count); + for (var j = 0; j < item1.CustomTags.Count; j++) + { + Assert.Equal(item1.CustomTags[j], item2.CustomTags[j]); + } + + Assert.Equal(item1.Properties.Count, item2.Properties.Count); + Assert.True(item1.Properties.SetEquals(item2.Properties)); + + Assert.Equal(item1.Workspace, item2.Workspace); + Assert.Equal(item1.ProjectId, item2.ProjectId); + Assert.Equal(item1.DocumentId, item2.DocumentId); + + Assert.Equal(item1.HasTextSpan, item2.HasTextSpan); + if (item1.HasTextSpan) + { + Assert.Equal(item1.TextSpan, item2.TextSpan); + } + + Assert.Equal(item1.DataLocation?.MappedFilePath, item2.DataLocation?.MappedFilePath); + Assert.Equal(item1.DataLocation?.MappedStartLine, item2.DataLocation?.MappedStartLine); + Assert.Equal(item1.DataLocation?.MappedStartColumn, item2.DataLocation?.MappedStartColumn); + Assert.Equal(item1.DataLocation?.MappedEndLine, item2.DataLocation?.MappedEndLine); + Assert.Equal(item1.DataLocation?.MappedEndColumn, item2.DataLocation?.MappedEndColumn); + + Assert.Equal(item1.DataLocation?.OriginalFilePath, item2.DataLocation?.OriginalFilePath); + Assert.Equal(item1.DataLocation?.OriginalStartLine, item2.DataLocation?.OriginalStartLine); + Assert.Equal(item1.DataLocation?.OriginalStartColumn, item2.DataLocation?.OriginalStartColumn); + Assert.Equal(item1.DataLocation?.OriginalEndLine, item2.DataLocation?.OriginalEndLine); + Assert.Equal(item1.DataLocation?.OriginalEndColumn, item2.DataLocation?.OriginalEndColumn); + + Assert.Equal(item1.Description, item2.Description); + Assert.Equal(item1.HelpLink, item2.HelpLink); + } + + [ExportWorkspaceServiceFactory(typeof(IPersistentStorageService), "DiagnosticDataSerializerTest"), Shared] + public class PersistentStorageServiceFactory : IWorkspaceServiceFactory + { + public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) + { + return new Service(); + } + + public class Service : IPersistentStorageService + { + private readonly Storage _instance = new Storage(); + + IPersistentStorage IPersistentStorageService.GetStorage(Solution solution) + { + return _instance; + } + + internal class Storage : IPersistentStorage + { + private readonly Dictionary _map = new Dictionary(); + + public Task ReadStreamAsync(string name, CancellationToken cancellationToken = default(CancellationToken)) + { + var stream = _map[name]; + stream.Position = 0; + + return Task.FromResult(stream); + } + + public Task ReadStreamAsync(Project project, string name, CancellationToken cancellationToken = default(CancellationToken)) + { + var stream = _map[Tuple.Create(project, name)]; + stream.Position = 0; + + return Task.FromResult(stream); + } + + public Task ReadStreamAsync(Document document, string name, CancellationToken cancellationToken = default(CancellationToken)) + { + var stream = _map[Tuple.Create(document, name)]; + stream.Position = 0; + + return Task.FromResult(stream); + } + + public Task WriteStreamAsync(string name, Stream stream, CancellationToken cancellationToken = default(CancellationToken)) + { + _map[name] = new MemoryStream(); + stream.CopyTo(_map[name]); + + return SpecializedTasks.True; + } + + public Task WriteStreamAsync(Project project, string name, Stream stream, CancellationToken cancellationToken = default(CancellationToken)) + { + _map[Tuple.Create(project, name)] = new MemoryStream(); + stream.CopyTo(_map[Tuple.Create(project, name)]); + + return SpecializedTasks.True; + } + + public Task WriteStreamAsync(Document document, string name, Stream stream, CancellationToken cancellationToken = default(CancellationToken)) + { + _map[Tuple.Create(document, name)] = new MemoryStream(); + stream.CopyTo(_map[Tuple.Create(document, name)]); + + return SpecializedTasks.True; + } + + protected virtual void Dispose(bool disposing) + { + } + + public void Dispose() + { + Dispose(true); + } + } + } + } + } +} diff --git a/src/EditorFeatures/Test/EditorServicesTest.csproj b/src/EditorFeatures/Test/EditorServicesTest.csproj index 23c80b95d3679..204932a3565d2 100644 --- a/src/EditorFeatures/Test/EditorServicesTest.csproj +++ b/src/EditorFeatures/Test/EditorServicesTest.csproj @@ -230,6 +230,7 @@ + @@ -369,4 +370,4 @@ - + \ No newline at end of file From 6185f759f7a1a4f1580dd10a6d9cebacdfb9d60d Mon Sep 17 00:00:00 2001 From: Heejae Chang Date: Tue, 12 Apr 2016 04:07:35 -0700 Subject: [PATCH 09/24] made all test pass --- .../TestDiagnosticAnalyzerService.cs | 5 + .../Diagnostics/DiagnosticServiceTests.vb | 56 +++-- .../Diagnostics/EngineV2/AnalysisResult.cs | 59 ++++- .../EngineV2/CompilerDiagnosticExecutor.cs | 89 +++++++- ...gnosticIncrementalAnalyzer.AnalysisData.cs | 33 +-- ...cIncrementalAnalyzer.CompilationManager.cs | 45 ++-- .../DiagnosticIncrementalAnalyzer.Executor.cs | 201 ++++++++++++------ ...sticIncrementalAnalyzer.InMemoryStorage.cs | 3 +- ...gnosticIncrementalAnalyzer.ProjectState.cs | 191 +++++++++++++---- .../EngineV2/DiagnosticIncrementalAnalyzer.cs | 31 +++ ...ncrementalAnalyzer_BuildSynchronization.cs | 4 +- ...osticIncrementalAnalyzer_GetDiagnostics.cs | 80 ++++--- ...crementalAnalyzer_GetDiagnosticsForSpan.cs | 28 ++- ...IncrementalAnalyzer_IncrementalAnalyzer.cs | 27 +-- 14 files changed, 606 insertions(+), 246 deletions(-) diff --git a/src/EditorFeatures/Test/Diagnostics/TestDiagnosticAnalyzerService.cs b/src/EditorFeatures/Test/Diagnostics/TestDiagnosticAnalyzerService.cs index 4a598c4cbaebe..6cf13eb6c5115 100644 --- a/src/EditorFeatures/Test/Diagnostics/TestDiagnosticAnalyzerService.cs +++ b/src/EditorFeatures/Test/Diagnostics/TestDiagnosticAnalyzerService.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Collections.Immutable; using Microsoft.CodeAnalysis.Diagnostics.Log; using Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics; @@ -11,6 +12,7 @@ namespace Microsoft.CodeAnalysis.Diagnostics internal sealed class TestDiagnosticAnalyzerService : DiagnosticAnalyzerService { private readonly Action _onAnalyzerException; + private readonly ImmutableDictionary _hostAnalyzerReferenceMap; internal TestDiagnosticAnalyzerService( string language, @@ -54,6 +56,7 @@ private TestDiagnosticAnalyzerService( IDiagnosticUpdateSourceRegistrationService registrationService = null) : base(hostAnalyzerManager, hostDiagnosticUpdateSource, registrationService ?? new MockDiagnosticUpdateSourceRegistrationService()) { + _hostAnalyzerReferenceMap = hostAnalyzerManager.CreateAnalyzerReferencesMap(projectOpt: null); _onAnalyzerException = onAnalyzerException; } @@ -92,5 +95,7 @@ internal override Action GetOnAnalyze { return _onAnalyzerException ?? base.GetOnAnalyzerException(projectId, diagnosticLogAggregator); } + + internal IEnumerable HostAnalyzerReferences => _hostAnalyzerReferenceMap.Values; } } diff --git a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb index 1d7e6b84de81c..05dc8a9be2512 100644 --- a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb @@ -117,14 +117,12 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Assert.Equal(workspaceDiagnosticAnalyzer.DiagDescriptor.Id, descriptors(0).Id) ' Add an existing workspace analyzer to the project, ensure no duplicate diagnostics. - Dim duplicateProjectAnalyzers = ImmutableArray.Create(Of DiagnosticAnalyzer)(workspaceDiagnosticAnalyzer) - Dim duplicateProjectAnalyzersReference = New AnalyzerImageReference(duplicateProjectAnalyzers) + Dim duplicateProjectAnalyzersReference = diagnosticService.HostAnalyzerReferences.FirstOrDefault() project = project.WithAnalyzerReferences({duplicateProjectAnalyzersReference}) ' Verify duplicate descriptors or diagnostics. - ' We don't do de-duplication of analyzer that belong to different layer (host and project) descriptorsMap = diagnosticService.GetDiagnosticDescriptors(project) - Assert.Equal(2, descriptorsMap.Count) + Assert.Equal(1, descriptorsMap.Count) descriptors = descriptorsMap.Values.SelectMany(Function(d) d).OrderBy(Function(d) d.Id).ToImmutableArray() Assert.Equal(workspaceDiagnosticAnalyzer.DiagDescriptor.Id, descriptors(0).Id) @@ -132,7 +130,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests diagnostics = diagnosticService.GetDiagnosticsForSpanAsync(document, document.GetSyntaxRootAsync().WaitAndGetResult(CancellationToken.None).FullSpan ).WaitAndGetResult(CancellationToken.None) - Assert.Equal(2, diagnostics.Count()) + Assert.Equal(1, diagnostics.Count()) End Using End Sub @@ -732,23 +730,37 @@ class AnonymousFunctions Assert.Equal(1, diagnostics.Count()) Assert.Equal(document.Id, diagnostics.First().DocumentId) - ' Verify compilation diagnostics are reported with correct location info when asked for project diagnostics. - Dim projectDiagnostics = diagnosticService.GetProjectDiagnosticsForIdsAsync(project.Solution, project.Id).WaitAndGetResult(CancellationToken.None) - Assert.Equal(2, projectDiagnostics.Count()) - - Dim noLocationDiagnostic = projectDiagnostics.First() - Assert.Equal(CompilationEndedAnalyzer.Descriptor.Id, noLocationDiagnostic.Id) - Assert.Equal(False, noLocationDiagnostic.HasTextSpan) - - Dim withDocumentLocationDiagnostic = projectDiagnostics.Last() - Assert.Equal(CompilationEndedAnalyzer.Descriptor.Id, withDocumentLocationDiagnostic.Id) - Assert.Equal(True, withDocumentLocationDiagnostic.HasTextSpan) - Assert.NotNull(withDocumentLocationDiagnostic.DocumentId) - Dim diagnosticDocument = project.GetDocument(withDocumentLocationDiagnostic.DocumentId) - Dim tree = diagnosticDocument.GetSyntaxTreeAsync().Result - Dim actualLocation = withDocumentLocationDiagnostic.ToDiagnosticAsync(project, CancellationToken.None).Result.Location - Dim expectedLocation = document.GetSyntaxRootAsync().Result.GetLocation - Assert.Equal(expectedLocation, actualLocation) + If options.GetOption(InternalDiagnosticsOptions.UseDiagnosticEngineV2) Then + ' Verify compilation diagnostics are reported with correct location info when asked for project diagnostics. + Dim projectDiagnostics = diagnosticService.GetProjectDiagnosticsForIdsAsync(project.Solution, project.Id).WaitAndGetResult(CancellationToken.None) + Assert.Equal(1, projectDiagnostics.Count()) + + Dim noLocationDiagnostic = projectDiagnostics.First() + Assert.Equal(CompilationEndedAnalyzer.Descriptor.Id, noLocationDiagnostic.Id) + Assert.Equal(False, noLocationDiagnostic.HasTextSpan) + Else + ' REVIEW: GetProjectDiagnosticsForIdsAsync is for project diagnostics with no location. not sure why in v1, this API returns + ' diagnostic with location? + + ' Verify compilation diagnostics are reported with correct location info when asked for project diagnostics. + Dim projectDiagnostics = diagnosticService.GetProjectDiagnosticsForIdsAsync(project.Solution, project.Id).WaitAndGetResult(CancellationToken.None) + Assert.Equal(2, projectDiagnostics.Count()) + + Dim noLocationDiagnostic = projectDiagnostics.First() + Assert.Equal(CompilationEndedAnalyzer.Descriptor.Id, noLocationDiagnostic.Id) + Assert.Equal(False, noLocationDiagnostic.HasTextSpan) + + Dim withDocumentLocationDiagnostic = projectDiagnostics.Last() + Assert.Equal(CompilationEndedAnalyzer.Descriptor.Id, withDocumentLocationDiagnostic.Id) + Assert.Equal(True, withDocumentLocationDiagnostic.HasTextSpan) + Assert.NotNull(withDocumentLocationDiagnostic.DocumentId) + + Dim diagnosticDocument = project.GetDocument(withDocumentLocationDiagnostic.DocumentId) + Dim tree = diagnosticDocument.GetSyntaxTreeAsync().Result + Dim actualLocation = withDocumentLocationDiagnostic.ToDiagnosticAsync(project, CancellationToken.None).Result.Location + Dim expectedLocation = document.GetSyntaxRootAsync().Result.GetLocation + Assert.Equal(expectedLocation, actualLocation) + End If End Using End Sub diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/AnalysisResult.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/AnalysisResult.cs index f6cd1c6e2b6db..f666a0c0af1d5 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/AnalysisResult.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/AnalysisResult.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Immutable; +using System.Linq; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 @@ -27,6 +28,16 @@ internal struct AnalysisResult private readonly ImmutableDictionary> _nonLocals; private readonly ImmutableArray _others; + public AnalysisResult(ProjectId projectId, VersionStamp version) : this( + projectId, version, + documentIds: ImmutableHashSet.Empty, + syntaxLocals: ImmutableDictionary>.Empty, + semanticLocals: ImmutableDictionary>.Empty, + nonLocals: ImmutableDictionary>.Empty, + others: ImmutableArray.Empty) + { + } + public AnalysisResult( ProjectId projectId, VersionStamp version, ImmutableHashSet documentIds, bool isEmpty) { @@ -43,32 +54,42 @@ public AnalysisResult( public AnalysisResult( ProjectId projectId, VersionStamp version, - ImmutableHashSet documentIds, ImmutableDictionary> syntaxLocals, ImmutableDictionary> semanticLocals, ImmutableDictionary> nonLocals, - ImmutableArray others) + ImmutableArray others, + ImmutableHashSet documentIds) { ProjectId = projectId; Version = version; - DocumentIds = documentIds; _syntaxLocals = syntaxLocals; _semanticLocals = semanticLocals; _nonLocals = nonLocals; _others = others; + DocumentIds = documentIds; + IsEmpty = false; + + // do after all fields are assigned. + DocumentIds = DocumentIds ?? CreateDocumentIds(); IsEmpty = DocumentIds.IsEmpty && _others.IsEmpty; } // aggregated form means it has aggregated information but no actual data. public bool IsAggregatedForm => _syntaxLocals == null; + // default analysis result + public bool IsDefault => DocumentIds == null; + + // make sure we don't return null + public ImmutableHashSet DocumentIdsOrEmpty => DocumentIds ?? ImmutableHashSet.Empty; + // this shouldn't be called for aggregated form. - public ImmutableDictionary> SyntaxLocals => ReturnIfNotDefalut(_syntaxLocals); - public ImmutableDictionary> SemanticLocals => ReturnIfNotDefalut(_semanticLocals); - public ImmutableDictionary> NonLocals => ReturnIfNotDefalut(_nonLocals); - public ImmutableArray Others => ReturnIfNotDefalut(_others); + public ImmutableDictionary> SyntaxLocals => ReturnIfNotDefault(_syntaxLocals); + public ImmutableDictionary> SemanticLocals => ReturnIfNotDefault(_semanticLocals); + public ImmutableDictionary> NonLocals => ReturnIfNotDefault(_nonLocals); + public ImmutableArray Others => ReturnIfNotDefault(_others); public ImmutableArray GetResultOrEmpty(ImmutableDictionary> map, DocumentId key) { @@ -76,6 +97,7 @@ public ImmutableArray GetResultOrEmpty(ImmutableDictionary diagnostics; if (map.TryGetValue(key, out diagnostics)) { + Contract.ThrowIfFalse(DocumentIds.Contains(key)); return diagnostics; } @@ -87,7 +109,7 @@ public AnalysisResult ToAggregatedForm() return new AnalysisResult(ProjectId, Version, DocumentIds, IsEmpty); } - private T ReturnIfNotDefalut(T value) + private T ReturnIfNotDefault(T value) { if (object.Equals(value, default(T))) { @@ -96,5 +118,26 @@ private T ReturnIfNotDefalut(T value) return value; } + + private ImmutableHashSet CreateDocumentIds() + { + var documents = SpecializedCollections.EmptyEnumerable(); + if (_syntaxLocals != null) + { + documents = documents.Concat(_syntaxLocals.Keys); + } + + if (_semanticLocals != null) + { + documents = documents.Concat(_semanticLocals.Keys); + } + + if (_nonLocals != null) + { + documents = documents.Concat(_nonLocals.Keys); + } + + return ImmutableHashSet.CreateRange(documents); + } } } \ No newline at end of file diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/CompilerDiagnosticExecutor.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/CompilerDiagnosticExecutor.cs index e0222a07e02d3..2af770fa9a930 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/CompilerDiagnosticExecutor.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/CompilerDiagnosticExecutor.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; @@ -94,7 +95,7 @@ public AnalysisResult ToResult() var nonLocals = Convert(_lazyNonLocals); var others = _lazyOthers == null ? ImmutableArray.Empty : _lazyOthers.ToImmutableArray(); - return new AnalysisResult(_project.Id, _version, documentIds, syntaxLocals, semanticLocals, nonLocals, others); + return new AnalysisResult(_project.Id, _version, syntaxLocals, semanticLocals, nonLocals, others, documentIds); } private ImmutableDictionary> Convert(Dictionary> map) @@ -102,6 +103,84 @@ private ImmutableDictionary> Convert( return map == null ? ImmutableDictionary>.Empty : map.ToImmutableDictionary(kv => kv.Key, kv => kv.Value.ToImmutableArray()); } + public void AddSyntaxDiagnostics(DocumentId documentId, IEnumerable diagnostics) + { + AddDiagnostics(ref _lazySyntaxLocals, documentId, diagnostics); + } + + public void AddSemanticDiagnostics(DocumentId documentId, IEnumerable diagnostics) + { + AddDiagnostics(ref _lazySemanticLocals, documentId, diagnostics); + } + + private void AddDiagnostics( + ref Dictionary> lazyLocals, DocumentId documentId, IEnumerable diagnostics) + { + Contract.ThrowIfTrue(_project.SupportsCompilation); + + foreach (var diagnostic in diagnostics) + { + // REVIEW: what is our plan for additional locations? + switch (diagnostic.Location.Kind) + { + case LocationKind.ExternalFile: + { + var diagnosticDocumentId = GetExternalDocumentId(diagnostic); + if (documentId == diagnosticDocumentId) + { + var document = _project.GetDocument(diagnosticDocumentId); + if (document != null) + { + // local diagnostics to a file + lazyLocals = lazyLocals ?? new Dictionary>(); + lazyLocals.GetOrAdd(document.Id, _ => new List()).Add(DiagnosticData.Create(document, diagnostic)); + + SetDocument(document); + } + } + else if (diagnosticDocumentId != null) + { + var document = _project.GetDocument(diagnosticDocumentId); + if (document != null) + { + // non local diagnostics to a file + _lazyNonLocals = _lazyNonLocals ?? new Dictionary>(); + _lazyNonLocals.GetOrAdd(document.Id, _ => new List()).Add(DiagnosticData.Create(document, diagnostic)); + + SetDocument(document); + } + } + else + { + // non local diagnostics without location + _lazyOthers = _lazyOthers ?? new List(); + _lazyOthers.Add(DiagnosticData.Create(_project, diagnostic)); + } + + break; + } + case LocationKind.None: + { + _lazyOthers = _lazyOthers ?? new List(); + _lazyOthers.Add(DiagnosticData.Create(_project, diagnostic)); + break; + } + case LocationKind.SourceFile: + case LocationKind.MetadataFile: + case LocationKind.XmlFile: + { + // something we don't care + continue; + } + default: + { + Contract.Fail("should not reach"); + break; + } + } + } + } + public void AddSyntaxDiagnostics(SyntaxTree tree, IEnumerable diagnostics) { AddDiagnostics(ref _lazySyntaxLocals, tree, diagnostics); @@ -200,6 +279,14 @@ private Document GetDocument(Diagnostic diagnostic) { return _project.GetDocument(diagnostic.Location.SourceTree); } + + private DocumentId GetExternalDocumentId(Diagnostic diagnostic) + { + var projectId = _project.Id; + var lineSpan = diagnostic.Location.GetLineSpan(); + + return _project.Solution.GetDocumentIdsWithFilePath(lineSpan.Path).FirstOrDefault(id => id.ProjectId == projectId); + } } } } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.AnalysisData.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.AnalysisData.cs index db144799e240f..5dab0b020e873 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.AnalysisData.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.AnalysisData.cs @@ -46,34 +46,33 @@ public bool FromCache private struct ProjectAnalysisData { - public static readonly ProjectAnalysisData Empty = new ProjectAnalysisData( - VersionStamp.Default, ImmutableDictionary.Empty, ImmutableDictionary.Empty); - + public readonly ProjectId ProjectId; public readonly VersionStamp Version; public readonly ImmutableDictionary OldResult; public readonly ImmutableDictionary Result; - - public ProjectAnalysisData(VersionStamp version, ImmutableDictionary result) + public ProjectAnalysisData(ProjectId projectId, VersionStamp version, ImmutableDictionary result) { - this.Version = version; - this.Result = result; + ProjectId = projectId; + Version = version; + Result = result; - this.OldResult = null; + OldResult = null; } public ProjectAnalysisData( + ProjectId projectId, VersionStamp version, ImmutableDictionary oldResult, ImmutableDictionary newResult) : - this(version, newResult) + this(projectId, version, newResult) { this.OldResult = oldResult; } public AnalysisResult GetResult(DiagnosticAnalyzer analyzer) { - return IDictionaryExtensions.GetValueOrDefault(Result, analyzer); + return GetResultOrEmpty(Result, analyzer, ProjectId, Version); } public bool FromCache @@ -90,15 +89,19 @@ public static async Task CreateAsync(Project project, IEnum { var state = stateSet.GetProjectState(project.Id); var result = await state.GetAnalysisDataAsync(project, avoidLoadingData, cancellationToken).ConfigureAwait(false); + Contract.ThrowIfFalse(project.Id == result.ProjectId); if (!version.HasValue) { - version = result.Version; + if (result.Version != VersionStamp.Default) + { + version = result.Version; + } } else { - // all version must be same. - Contract.ThrowIfFalse(version == result.Version); + // all version must be same or default (means not there yet) + Contract.Requires(version == result.Version || result.Version == VersionStamp.Default); } builder.Add(stateSet.Analyzer, result); @@ -107,10 +110,10 @@ public static async Task CreateAsync(Project project, IEnum if (!version.HasValue) { // there is no saved data to return. - return ProjectAnalysisData.Empty; + return new ProjectAnalysisData(project.Id, VersionStamp.Default, ImmutableDictionary.Empty); } - return new ProjectAnalysisData(version.Value, builder.ToImmutable()); + return new ProjectAnalysisData(project.Id, version.Value, builder.ToImmutable()); } } } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs index 254ed34f2fefd..96866e9f9db92 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs @@ -35,32 +35,35 @@ public CompilationManager(DiagnosticIncrementalAnalyzer owner) /// public async Task GetAnalyzerDriverAsync(Project project, IEnumerable stateSets, CancellationToken cancellationToken) { - Contract.ThrowIfFalse(project.SupportsCompilation); + if (!project.SupportsCompilation) + { + return null; + } - CompilationWithAnalyzers analyzerDriver; - if (_map.TryGetValue(project, out analyzerDriver)) + CompilationWithAnalyzers analyzerDriverOpt; + if (_map.TryGetValue(project, out analyzerDriverOpt)) { // we have cached one, return that. - AssertAnalyzers(analyzerDriver, stateSets); - return analyzerDriver; + AssertAnalyzers(analyzerDriverOpt, stateSets); + return analyzerDriverOpt; } // Create driver that holds onto compilation and associated analyzers var concurrentAnalysis = false; var includeSuppressedDiagnostics = true; - var newAnalyzerDriver = await CreateAnalyzerDriverAsync(project, stateSets, concurrentAnalysis, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); + var newAnalyzerDriverOpt = await CreateAnalyzerDriverAsync(project, stateSets, concurrentAnalysis, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); // Add new analyzer driver to the map - analyzerDriver = _map.GetValue(project, _ => newAnalyzerDriver); + analyzerDriverOpt = _map.GetValue(project, _ => newAnalyzerDriverOpt); // if somebody has beat us, make sure analyzers are good. - if (analyzerDriver != newAnalyzerDriver) + if (analyzerDriverOpt != newAnalyzerDriverOpt) { - AssertAnalyzers(analyzerDriver, stateSets); + AssertAnalyzers(analyzerDriverOpt, stateSets); } // return driver - return analyzerDriver; + return analyzerDriverOpt; } public Task CreateAnalyzerDriverAsync( @@ -77,10 +80,13 @@ public Task CreateAnalyzerDriverAsync( return CreateAnalyzerDriverAsync(project, analyzers, concurrentAnalysis, includeSuppressedDiagnostics, cancellationToken); } - public async Task CreateAnalyzerDriverAsync( + private async Task CreateAnalyzerDriverAsync( Project project, ImmutableArray analyzers, bool concurrentAnalysis, bool includeSuppressedDiagnostics, CancellationToken cancellationToken) { - Contract.ThrowIfFalse(project.SupportsCompilation); + if (!project.SupportsCompilation) + { + return null; + } var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); @@ -89,7 +95,7 @@ public async Task CreateAnalyzerDriverAsync( project, compilation, analyzers, concurrentAnalysis: concurrentAnalysis, logAnalyzerExecutionTime: false, reportSuppressedDiagnostics: includeSuppressedDiagnostics); } - public CompilationWithAnalyzers CreateAnalyzerDriver( + private CompilationWithAnalyzers CreateAnalyzerDriver( Project project, Compilation compilation, ImmutableArray analyzers, @@ -97,6 +103,13 @@ public CompilationWithAnalyzers CreateAnalyzerDriver( bool logAnalyzerExecutionTime, bool reportSuppressedDiagnostics) { + // PERF: there is no analyzers for this compilation. + // compilationWithAnalyzer will throw if it is created with no analyzers which is perf optimization. + if (analyzers.IsEmpty) + { + return null; + } + Contract.ThrowIfFalse(project.SupportsCompilation); AssertCompilation(project, compilation); @@ -154,6 +167,12 @@ private void ResetAnalyzerDriverMap() [Conditional("DEBUG")] private void AssertAnalyzers(CompilationWithAnalyzers analyzerDriver, IEnumerable stateSets) { + if (analyzerDriver == null) + { + // this can happen if project doesn't support compilation or no stateSets are given. + return; + } + // make sure analyzers are same. Contract.ThrowIfFalse(analyzerDriver.Analyzers.SetEquals(stateSets.Select(s => s.Analyzer))); } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs index e67526e4765bc..fef2e560f9178 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs @@ -33,25 +33,16 @@ public IEnumerable ConvertToLocalDiagnostics(Document targetDocu { var project = targetDocument.Project; - foreach (var diagnostic in diagnostics) + if (project.SupportsCompilation) { - var document = project.GetDocument(diagnostic.Location.SourceTree); - if (document == null || document != targetDocument) - { - continue; - } - - if (span.HasValue && !span.Value.Contains(diagnostic.Location.SourceSpan)) - { - continue; - } - - yield return DiagnosticData.Create(document, diagnostic); + return ConvertToLocalDiagnosticsWithCompilation(targetDocument, diagnostics, span); } + + return ConvertToLocalDiagnosticsWithoutCompilation(targetDocument, diagnostics, span); } public async Task GetDocumentAnalysisDataAsync( - CompilationWithAnalyzers analyzerDriver, Document document, StateSet stateSet, AnalysisKind kind, CancellationToken cancellationToken) + CompilationWithAnalyzers analyzerDriverOpt, Document document, StateSet stateSet, AnalysisKind kind, CancellationToken cancellationToken) { try { @@ -72,7 +63,7 @@ public async Task GetDocumentAnalysisDataAsync( } var nullFilterSpan = (TextSpan?)null; - var diagnostics = await ComputeDiagnosticsAsync(analyzerDriver, document, stateSet.Analyzer, kind, nullFilterSpan, cancellationToken).ConfigureAwait(false); + var diagnostics = await ComputeDiagnosticsAsync(analyzerDriverOpt, document, stateSet.Analyzer, kind, nullFilterSpan, cancellationToken).ConfigureAwait(false); // we only care about local diagnostics return new DocumentAnalysisData(version, existingData.Items, diagnostics.ToImmutableArrayOrEmpty()); @@ -83,7 +74,8 @@ public async Task GetDocumentAnalysisDataAsync( } } - public async Task GetProjectAnalysisDataAsync(CompilationWithAnalyzers analyzerDriver, Project project, IEnumerable stateSets, CancellationToken cancellationToken) + public async Task GetProjectAnalysisDataAsync( + CompilationWithAnalyzers analyzerDriverOpt, Project project, IEnumerable stateSets, CancellationToken cancellationToken) { try { @@ -100,12 +92,12 @@ public async Task GetProjectAnalysisDataAsync(CompilationWi // perf optimization. check whether we want to analyze this project or not. if (!await FullAnalysisEnabledAsync(project, cancellationToken).ConfigureAwait(false)) { - return new ProjectAnalysisData(version, existingData.Result, ImmutableDictionary.Empty); + return new ProjectAnalysisData(project.Id, version, existingData.Result, ImmutableDictionary.Empty); } - var result = await ComputeDiagnosticsAsync(analyzerDriver, project, stateSets, cancellationToken).ConfigureAwait(false); + var result = await ComputeDiagnosticsAsync(analyzerDriverOpt, project, stateSets, cancellationToken).ConfigureAwait(false); - return new ProjectAnalysisData(version, existingData.Result, result); + return new ProjectAnalysisData(project.Id, version, existingData.Result, result); } catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) { @@ -114,74 +106,111 @@ public async Task GetProjectAnalysisDataAsync(CompilationWi } public async Task> ComputeDiagnosticsAsync( - CompilationWithAnalyzers analyzerDriver, Document document, DiagnosticAnalyzer analyzer, AnalysisKind kind, TextSpan? spanOpt, CancellationToken cancellationToken) + CompilationWithAnalyzers analyzerDriverOpt, Document document, DiagnosticAnalyzer analyzer, AnalysisKind kind, TextSpan? spanOpt, CancellationToken cancellationToken) { var documentAnalyzer = analyzer as DocumentDiagnosticAnalyzer; if (documentAnalyzer != null) { - var diagnostics = await ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync(document, documentAnalyzer, kind, analyzerDriver.Compilation, cancellationToken).ConfigureAwait(false); + var diagnostics = await ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync(document, documentAnalyzer, kind, analyzerDriverOpt?.Compilation, cancellationToken).ConfigureAwait(false); return ConvertToLocalDiagnostics(document, diagnostics); } - var documentDiagnostics = await ComputeDiagnosticAnalyzerDiagnosticsAsync(analyzerDriver, document, analyzer, kind, spanOpt, cancellationToken).ConfigureAwait(false); + var documentDiagnostics = await ComputeDiagnosticAnalyzerDiagnosticsAsync(analyzerDriverOpt, document, analyzer, kind, spanOpt, cancellationToken).ConfigureAwait(false); return ConvertToLocalDiagnostics(document, documentDiagnostics); } public async Task> ComputeDiagnosticsAsync( - CompilationWithAnalyzers analyzerDriver, Project project, IEnumerable stateSets, CancellationToken cancellationToken) + CompilationWithAnalyzers analyzerDriverOpt, Project project, IEnumerable stateSets, CancellationToken cancellationToken) { - // calculate regular diagnostic analyzers diagnostics - var result = await analyzerDriver.AnalyzeAsync(project, cancellationToken).ConfigureAwait(false); + var result = ImmutableDictionary.Empty; + + // analyzerDriver can be null if given project doesn't support compilation. + if (analyzerDriverOpt != null) + { + // calculate regular diagnostic analyzers diagnostics + result = await analyzerDriverOpt.AnalyzeAsync(project, cancellationToken).ConfigureAwait(false); - // record telemetry data - await UpdateAnalyzerTelemetryDataAsync(analyzerDriver, project, cancellationToken).ConfigureAwait(false); + // record telemetry data + await UpdateAnalyzerTelemetryDataAsync(analyzerDriverOpt, project, cancellationToken).ConfigureAwait(false); + } // check whether there is IDE specific project diagnostic analyzer - return await MergeProjectDiagnosticAnalyzerDiagnosticsAsync(project, stateSets, analyzerDriver.Compilation, result, cancellationToken).ConfigureAwait(false); + return await MergeProjectDiagnosticAnalyzerDiagnosticsAsync(project, stateSets, analyzerDriverOpt?.Compilation, result, cancellationToken).ConfigureAwait(false); } private async Task> MergeProjectDiagnosticAnalyzerDiagnosticsAsync( - Project project, IEnumerable stateSets, Compilation compilation, ImmutableDictionary result, CancellationToken cancellationToken) + Project project, IEnumerable stateSets, Compilation compilationOpt, ImmutableDictionary result, CancellationToken cancellationToken) { // check whether there is IDE specific project diagnostic analyzer - var projectAnalyzers = stateSets.Select(s => s.Analyzer).OfType().ToImmutableArrayOrEmpty(); - if (projectAnalyzers.Length <= 0) + var ideAnalyzers = stateSets.Select(s => s.Analyzer).Where(a => a is ProjectDiagnosticAnalyzer || a is DocumentDiagnosticAnalyzer).ToImmutableArrayOrEmpty(); + if (ideAnalyzers.Length <= 0) { return result; } + // create result map var version = await GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false); - using (var diagnostics = SharedPools.Default>().GetPooledObject()) + var builder = new CompilerDiagnosticExecutor.Builder(project, version); + + foreach (var analyzer in ideAnalyzers) { - foreach (var analyzer in projectAnalyzers) + var documentAnalyzer = analyzer as DocumentDiagnosticAnalyzer; + if (documentAnalyzer != null) { - // reset pooled list - diagnostics.Object.Clear(); - - try - { - await analyzer.AnalyzeProjectAsync(project, diagnostics.Object.Add, cancellationToken).ConfigureAwait(false); - } - catch (Exception e) when (!IsCanceled(e, cancellationToken)) + foreach (var document in project.Documents) { - OnAnalyzerException(analyzer, project.Id, compilation, e); - continue; + if (project.SupportsCompilation) + { + var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + builder.AddSyntaxDiagnostics(tree, await ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync(document, documentAnalyzer, AnalysisKind.Syntax, compilationOpt, cancellationToken).ConfigureAwait(false)); + builder.AddSemanticDiagnostics(tree, await ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync(document, documentAnalyzer, AnalysisKind.Semantic, compilationOpt, cancellationToken).ConfigureAwait(false)); + } + else + { + builder.AddSyntaxDiagnostics(document.Id, await ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync(document, documentAnalyzer, AnalysisKind.Syntax, compilationOpt, cancellationToken).ConfigureAwait(false)); + builder.AddSemanticDiagnostics(document.Id, await ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync(document, documentAnalyzer, AnalysisKind.Semantic, compilationOpt, cancellationToken).ConfigureAwait(false)); + } } + } - // create result map - var builder = new CompilerDiagnosticExecutor.Builder(project, version); - builder.AddCompilationDiagnostics(diagnostics.Object); - - // merge the result to existing one. - result = result.Add(analyzer, builder.ToResult()); + var projectAnalyzer = analyzer as ProjectDiagnosticAnalyzer; + if (projectAnalyzer != null) + { + builder.AddCompilationDiagnostics(await ComputeProjectDiagnosticAnalyzerDiagnosticsAsync(project, projectAnalyzer, compilationOpt, cancellationToken).ConfigureAwait(false)); } + + // merge the result to existing one. + result = result.Add(analyzer, builder.ToResult()); } return result; } + private async Task> ComputeProjectDiagnosticAnalyzerDiagnosticsAsync( + Project project, ProjectDiagnosticAnalyzer analyzer, Compilation compilationOpt, CancellationToken cancellationToken) + { + using (var pooledObject = SharedPools.Default>().GetPooledObject()) + { + var diagnostics = pooledObject.Object; + cancellationToken.ThrowIfCancellationRequested(); + + try + { + await analyzer.AnalyzeProjectAsync(project, diagnostics.Add, cancellationToken).ConfigureAwait(false); + + // REVIEW: V1 doesn't convert diagnostics to effective diagnostics. not sure why. + return compilationOpt == null ? diagnostics.ToImmutableArrayOrEmpty() : CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, compilationOpt); + } + catch (Exception e) when (!IsCanceled(e, cancellationToken)) + { + OnAnalyzerException(analyzer, project.Id, compilationOpt, e); + return ImmutableArray.Empty; + } + } + } + private async Task> ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync( - Document document, DocumentDiagnosticAnalyzer analyzer, AnalysisKind kind, Compilation compilation, CancellationToken cancellationToken) + Document document, DocumentDiagnosticAnalyzer analyzer, AnalysisKind kind, Compilation compilationOpt, CancellationToken cancellationToken) { using (var pooledObject = SharedPools.Default>().GetPooledObject()) { @@ -194,28 +223,27 @@ private async Task> ComputeDocumentDiagnosticAnalyzerDia { case AnalysisKind.Syntax: await analyzer.AnalyzeSyntaxAsync(document, diagnostics.Add, cancellationToken).ConfigureAwait(false); - return CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, compilation); + return compilationOpt == null ? diagnostics.ToImmutableArrayOrEmpty() : CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, compilationOpt); case AnalysisKind.Semantic: await analyzer.AnalyzeSemanticsAsync(document, diagnostics.Add, cancellationToken).ConfigureAwait(false); - return CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, compilation); + return compilationOpt == null ? diagnostics.ToImmutableArrayOrEmpty() : CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, compilationOpt); default: return Contract.FailWithReturn>("shouldn't reach here"); } } catch (Exception e) when (!IsCanceled(e, cancellationToken)) { - OnAnalyzerException(analyzer, document.Project.Id, compilation, e); - + OnAnalyzerException(analyzer, document.Project.Id, compilationOpt, e); return ImmutableArray.Empty; } } } private async Task> ComputeDiagnosticAnalyzerDiagnosticsAsync( - CompilationWithAnalyzers analyzerDriver, Document document, DiagnosticAnalyzer analyzer, AnalysisKind kind, TextSpan? spanOpt, CancellationToken cancellationToken) + CompilationWithAnalyzers analyzerDriverOpt, Document document, DiagnosticAnalyzer analyzer, AnalysisKind kind, TextSpan? spanOpt, CancellationToken cancellationToken) { // quick optimization to reduce allocations. - if (!_owner.SupportAnalysisKind(analyzer, document.Project.Language, kind)) + if (analyzerDriverOpt == null || !_owner.SupportAnalysisKind(analyzer, document.Project.Language, kind)) { return ImmutableArray.Empty; } @@ -227,12 +255,12 @@ private async Task> ComputeDiagnosticAnalyzerDiagnostics { case AnalysisKind.Syntax: var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - var diagnostics = await analyzerDriver.GetAnalyzerSyntaxDiagnosticsAsync(tree, oneAnalyzers, cancellationToken).ConfigureAwait(false); - return CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, analyzerDriver.Compilation); + var diagnostics = await analyzerDriverOpt.GetAnalyzerSyntaxDiagnosticsAsync(tree, oneAnalyzers, cancellationToken).ConfigureAwait(false); + return CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, analyzerDriverOpt.Compilation); case AnalysisKind.Semantic: var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); - diagnostics = await analyzerDriver.GetAnalyzerSemanticDiagnosticsAsync(model, spanOpt, oneAnalyzers, cancellationToken).ConfigureAwait(false); - return CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, analyzerDriver.Compilation); + diagnostics = await analyzerDriverOpt.GetAnalyzerSemanticDiagnosticsAsync(model, spanOpt, oneAnalyzers, cancellationToken).ConfigureAwait(false); + return CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, analyzerDriverOpt.Compilation); default: return Contract.FailWithReturn>("shouldn't reach here"); } @@ -278,18 +306,65 @@ private static bool IsCanceled(Exception ex, CancellationToken cancellationToken return (ex as OperationCanceledException)?.CancellationToken == cancellationToken; } - private void OnAnalyzerException(DiagnosticAnalyzer analyzer, ProjectId projectId, Compilation compilation, Exception ex) + private void OnAnalyzerException(DiagnosticAnalyzer analyzer, ProjectId projectId, Compilation compilationOpt, Exception ex) { var exceptionDiagnostic = AnalyzerHelper.CreateAnalyzerExceptionDiagnostic(analyzer, ex); - if (compilation != null) + if (compilationOpt != null) { - exceptionDiagnostic = CompilationWithAnalyzers.GetEffectiveDiagnostics(ImmutableArray.Create(exceptionDiagnostic), compilation).SingleOrDefault(); + exceptionDiagnostic = CompilationWithAnalyzers.GetEffectiveDiagnostics(ImmutableArray.Create(exceptionDiagnostic), compilationOpt).SingleOrDefault(); } var onAnalyzerException = _owner.GetOnAnalyzerException(projectId); onAnalyzerException(ex, analyzer, exceptionDiagnostic); } + + private IEnumerable ConvertToLocalDiagnosticsWithoutCompilation(Document targetDocument, IEnumerable diagnostics, TextSpan? span = null) + { + var project = targetDocument.Project; + Contract.ThrowIfTrue(project.SupportsCompilation); + + foreach (var diagnostic in diagnostics) + { + var location = diagnostic.Location; + if (location.Kind != LocationKind.ExternalFile) + { + continue; + } + + var lineSpan = location.GetLineSpan(); + + var documentIds = project.Solution.GetDocumentIdsWithFilePath(lineSpan.Path); + if (documentIds.IsEmpty || documentIds.Any(id => id != targetDocument.Id)) + { + continue; + } + + yield return DiagnosticData.Create(targetDocument, diagnostic); + } + } + + private IEnumerable ConvertToLocalDiagnosticsWithCompilation(Document targetDocument, IEnumerable diagnostics, TextSpan? span = null) + { + var project = targetDocument.Project; + Contract.ThrowIfFalse(project.SupportsCompilation); + + foreach (var diagnostic in diagnostics) + { + var document = project.GetDocument(diagnostic.Location.SourceTree); + if (document == null || document != targetDocument) + { + continue; + } + + if (span.HasValue && !span.Value.Contains(diagnostic.Location.SourceSpan)) + { + continue; + } + + yield return DiagnosticData.Create(document, diagnostic); + } + } } } } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InMemoryStorage.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InMemoryStorage.cs index 21e3ccd9edddc..de82d860e8bba 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InMemoryStorage.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InMemoryStorage.cs @@ -69,7 +69,8 @@ public static void DropCache(DiagnosticAnalyzer analyzer) // make sure key is either documentId or projectId private static void AssertKey(object key) { - Contract.ThrowIfFalse(key is DocumentId || key is ProjectId); + var tuple = (ValueTuple)key; + Contract.ThrowIfFalse(tuple.Item1 is DocumentId || tuple.Item1 is ProjectId); } } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs index 0c958928ccd42..956bf342b04da 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs @@ -24,12 +24,12 @@ private class ProjectState public ProjectState(StateSet owner, ProjectId projectId) { _owner = owner; - _lastResult = new AnalysisResult(projectId, VersionStamp.Default, ImmutableHashSet.Empty, isEmpty: true); + _lastResult = new AnalysisResult(projectId, VersionStamp.Default, documentIds: null, isEmpty: true); } public ImmutableHashSet GetDocumentsWithDiagnostics() { - return _lastResult.DocumentIds; + return _lastResult.DocumentIdsOrEmpty; } public bool IsEmpty() @@ -39,45 +39,56 @@ public bool IsEmpty() public bool IsEmpty(DocumentId documentId) { - return !_lastResult.DocumentIds.Contains(documentId); + return IsEmpty(_lastResult, documentId); } public async Task GetAnalysisDataAsync(Project project, bool avoidLoadingData, CancellationToken cancellationToken) { // make a copy of last result. var lastResult = _lastResult; + Contract.ThrowIfFalse(lastResult.ProjectId == project.Id); - var version = await GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false); - var versionToLoad = GetVersionToLoad(lastResult.Version, version); + if (lastResult.IsDefault) + { + return await LoadInitialAnalysisDataAsync(project, cancellationToken).ConfigureAwait(false); + } // PERF: avoid loading data if version is not right one. // avoid loading data flag is there as a strickly perf optimization. - if (avoidLoadingData && versionToLoad != version) + var version = await GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false); + if (avoidLoadingData && lastResult.Version != version) { return lastResult; } + // if given document doesnt have any diagnostics, return empty. + if (lastResult.IsEmpty) + { + return new AnalysisResult(lastResult.ProjectId, lastResult.Version); + } + // loading data can be cancelled any time. - var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, versionToLoad); - var builder = new Builder(project.Id, versionToLoad, lastResult.DocumentIds); + var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, lastResult.Version); + var builder = new Builder(project.Id, lastResult.Version, lastResult.DocumentIds); foreach (var documentId in lastResult.DocumentIds) { var document = project.GetDocument(documentId); if (document == null) { - return lastResult; + continue; } if (!await TryDeserializeDocumentAsync(serializer, document, builder, cancellationToken).ConfigureAwait(false)) { - return lastResult; + Contract.Requires(false, "How this can happen?"); + continue; } } if (!await TryDeserializeAsync(serializer, project, project.Id, _owner.NonLocalStateName, builder.AddOthers, cancellationToken).ConfigureAwait(false)) { - return lastResult; + Contract.Requires(false, "How this can happen?"); } return builder.ToResult(); @@ -87,22 +98,32 @@ public async Task GetAnalysisDataAsync(Document document, bool a { // make a copy of last result. var lastResult = _lastResult; + Contract.ThrowIfFalse(lastResult.ProjectId == document.Project.Id); - var version = await GetDiagnosticVersionAsync(document.Project, cancellationToken).ConfigureAwait(false); - var versionToLoad = GetVersionToLoad(lastResult.Version, version); + if (lastResult.IsDefault) + { + return await LoadInitialAnalysisDataAsync(document, cancellationToken).ConfigureAwait(false); + } - if (avoidLoadingData && versionToLoad != version) + var version = await GetDiagnosticVersionAsync(document.Project, cancellationToken).ConfigureAwait(false); + if (avoidLoadingData && lastResult.Version != version) { return lastResult; } + // if given document doesnt have any diagnostics, return empty. + if (IsEmpty(lastResult, document.Id)) + { + return new AnalysisResult(lastResult.ProjectId, lastResult.Version); + } + // loading data can be cancelled any time. - var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, versionToLoad); - var builder = new Builder(document.Project.Id, versionToLoad, lastResult.DocumentIds); + var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, lastResult.Version); + var builder = new Builder(document.Project.Id, lastResult.Version); if (!await TryDeserializeDocumentAsync(serializer, document, builder, cancellationToken).ConfigureAwait(false)) { - return lastResult; + Contract.Requires(false, "How this can happen?"); } return builder.ToResult(); @@ -112,22 +133,32 @@ public async Task GetProjectAnalysisDataAsync(Project project, b { // make a copy of last result. var lastResult = _lastResult; + Contract.ThrowIfFalse(lastResult.ProjectId == project.Id); - var version = await GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false); - var versionToLoad = GetVersionToLoad(lastResult.Version, version); + if (lastResult.IsDefault) + { + return await LoadInitialProjectAnalysisDataAsync(project, cancellationToken).ConfigureAwait(false); + } - if (avoidLoadingData && versionToLoad != version) + var version = await GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false); + if (avoidLoadingData && lastResult.Version != version) { return lastResult; } + // if given document doesnt have any diagnostics, return empty. + if (lastResult.IsEmpty) + { + return new AnalysisResult(lastResult.ProjectId, lastResult.Version); + } + // loading data can be cancelled any time. - var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, versionToLoad); - var builder = new Builder(project.Id, versionToLoad, lastResult.DocumentIds); + var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, lastResult.Version); + var builder = new Builder(project.Id, lastResult.Version); if (!await TryDeserializeAsync(serializer, project, project.Id, _owner.NonLocalStateName, builder.AddOthers, cancellationToken).ConfigureAwait(false)) { - return lastResult; + Contract.Requires(false, "How this can happen?"); } return builder.ToResult(); @@ -135,6 +166,10 @@ public async Task GetProjectAnalysisDataAsync(Project project, b public async Task SaveAsync(Project project, AnalysisResult result) { + Contract.ThrowIfTrue(result.IsAggregatedForm); + + RemoveInMemoryCache(_lastResult); + // save last aggregated form of analysis result _lastResult = result.ToAggregatedForm(); @@ -155,37 +190,94 @@ public async Task SaveAsync(Project project, AnalysisResult result) public bool OnDocumentRemoved(DocumentId id) { - RemoveInMemoryCacheEntry(id); - + RemoveInMemoryCacheEntries(id); return !IsEmpty(id); } public bool OnProjectRemoved(ProjectId id) { - RemoveInMemoryCacheEntry(id); - + RemoveInMemoryCacheEntry(id, _owner.NonLocalStateName); return !IsEmpty(); } + private async Task LoadInitialAnalysisDataAsync(Project project, CancellationToken cancellationToken) + { + // loading data can be cancelled any time. + var version = await GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false); + var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, version); + var builder = new Builder(project.Id, version); + + foreach (var document in project.Documents) + { + if (!await TryDeserializeDocumentAsync(serializer, document, builder, cancellationToken).ConfigureAwait(false)) + { + continue; + } + } + + if (!await TryDeserializeAsync(serializer, project, project.Id, _owner.NonLocalStateName, builder.AddOthers, cancellationToken).ConfigureAwait(false)) + { + return new AnalysisResult(project.Id, VersionStamp.Default, ImmutableHashSet.Empty, isEmpty: true); + } + + return builder.ToResult(); + } + + private async Task LoadInitialAnalysisDataAsync(Document document, CancellationToken cancellationToken) + { + // loading data can be cancelled any time. + var project = document.Project; + + var version = await GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false); + var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, version); + var builder = new Builder(project.Id, version); + + if (!await TryDeserializeDocumentAsync(serializer, document, builder, cancellationToken).ConfigureAwait(false)) + { + return new AnalysisResult(project.Id, VersionStamp.Default, ImmutableHashSet.Empty, isEmpty: true); + } + + return builder.ToResult(); + } + + private async Task LoadInitialProjectAnalysisDataAsync(Project project, CancellationToken cancellationToken) + { + // loading data can be cancelled any time. + var version = await GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false); + var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, version); + var builder = new Builder(project.Id, version); + + if (!await TryDeserializeAsync(serializer, project, project.Id, _owner.NonLocalStateName, builder.AddOthers, cancellationToken).ConfigureAwait(false)) + { + return new AnalysisResult(project.Id, VersionStamp.Default, ImmutableHashSet.Empty, isEmpty: true); + } + + return builder.ToResult(); + } + private async Task SerializeAsync(DiagnosticDataSerializer serializer, object documentOrProject, object key, string stateKey, ImmutableArray diagnostics) { // try to serialize it if (await serializer.SerializeAsync(documentOrProject, stateKey, diagnostics, CancellationToken.None).ConfigureAwait(false)) { // we succeeded saving it to persistent storage. remove it from in memory cache if it exists - RemoveInMemoryCacheEntry(key); + RemoveInMemoryCacheEntry(key, stateKey); return; } // if serialization fail, hold it in the memory - InMemoryStorage.Cache(_owner.Analyzer, key, new CacheEntry(serializer.Version, diagnostics)); + InMemoryStorage.Cache(_owner.Analyzer, ValueTuple.Create(key, stateKey), new CacheEntry(serializer.Version, diagnostics)); } private async Task TryDeserializeDocumentAsync(DiagnosticDataSerializer serializer, Document document, Builder builder, CancellationToken cancellationToken) { - return await TryDeserializeAsync(serializer, document, document.Id, _owner.SyntaxStateName, builder.AddSyntaxLocals, cancellationToken).ConfigureAwait(false) && - await TryDeserializeAsync(serializer, document, document.Id, _owner.SemanticStateName, builder.AddSemanticLocals, cancellationToken).ConfigureAwait(false) && - await TryDeserializeAsync(serializer, document, document.Id, _owner.NonLocalStateName, builder.AddNonLocals, cancellationToken).ConfigureAwait(false); + var result = true; + + result &= await TryDeserializeAsync(serializer, document, document.Id, _owner.SyntaxStateName, builder.AddSyntaxLocals, cancellationToken).ConfigureAwait(false); + result &= await TryDeserializeAsync(serializer, document, document.Id, _owner.SemanticStateName, builder.AddSemanticLocals, cancellationToken).ConfigureAwait(false); + result &= await TryDeserializeAsync(serializer, document, document.Id, _owner.NonLocalStateName, builder.AddNonLocals, cancellationToken).ConfigureAwait(false); + + return result; } private async Task TryDeserializeAsync( @@ -208,7 +300,7 @@ private async Task> DeserializeAsync(DiagnosticDa { // check cache first CacheEntry entry; - if (InMemoryStorage.TryGetValue(_owner.Analyzer, key, out entry) && serializer.Version == entry.Version) + if (InMemoryStorage.TryGetValue(_owner.Analyzer, ValueTuple.Create(key, stateKey), out entry) && serializer.Version == entry.Version) { return entry.Diagnostics; } @@ -217,17 +309,31 @@ private async Task> DeserializeAsync(DiagnosticDa return await serializer.DeserializeAsync(documentOrProject, stateKey, cancellationToken).ConfigureAwait(false); } - private void RemoveInMemoryCacheEntry(object key) + private void RemoveInMemoryCache(AnalysisResult lastResult) + { + // remove old cache + foreach (var documentId in lastResult.DocumentIdsOrEmpty) + { + RemoveInMemoryCacheEntries(documentId); + } + } + + private void RemoveInMemoryCacheEntries(DocumentId id) + { + RemoveInMemoryCacheEntry(id, _owner.SyntaxStateName); + RemoveInMemoryCacheEntry(id, _owner.SemanticStateName); + RemoveInMemoryCacheEntry(id, _owner.NonLocalStateName); + } + + private void RemoveInMemoryCacheEntry(object key, string stateKey) { // remove in memory cache if entry exist - InMemoryStorage.Remove(_owner.Analyzer, key); + InMemoryStorage.Remove(_owner.Analyzer, ValueTuple.Create(key, stateKey)); } - private static VersionStamp GetVersionToLoad(VersionStamp savedVersion, VersionStamp currentVersion) + private bool IsEmpty(AnalysisResult result, DocumentId documentId) { - // if we don't have saved version, use currrent version. - // this will let us deal with case where we want to load saved data from last vs session. - return savedVersion == VersionStamp.Default ? currentVersion : savedVersion; + return !result.DocumentIdsOrEmpty.Contains(documentId); } // we have this builder to avoid allocating collections unnecessarily. @@ -242,7 +348,7 @@ private class Builder private ImmutableDictionary>.Builder _nonLocals; private ImmutableArray _others; - public Builder(ProjectId projectId, VersionStamp version, ImmutableHashSet documentIds) + public Builder(ProjectId projectId, VersionStamp version, ImmutableHashSet documentIds = null) { _projectId = projectId; _version = version; @@ -277,11 +383,12 @@ private void Add(ref ImmutableDictionary>.Empty, _semanticLocals?.ToImmutable() ?? ImmutableDictionary>.Empty, _nonLocals?.ToImmutable() ?? ImmutableDictionary>.Empty, - _others.IsDefault ? ImmutableArray.Empty : _others); + _others.IsDefault ? ImmutableArray.Empty : _others, + _documentIds); } } } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs index d3f9bb9616f12..b884b8ebf0f7d 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs @@ -183,5 +183,36 @@ public static Task GetDiagnosticVersionAsync(Project project, Canc { return project.GetDependentVersionAsync(cancellationToken); } + + private static AnalysisResult GetResultOrEmpty(ImmutableDictionary map, DiagnosticAnalyzer analyzer, ProjectId projectId, VersionStamp version) + { + AnalysisResult result; + if (map.TryGetValue(analyzer, out result)) + { + return result; + } + + return new AnalysisResult(projectId, version); + } + + private static ImmutableArray GetResult(AnalysisResult result, AnalysisKind kind, DocumentId id) + { + if (result.IsEmpty || !result.DocumentIds.Contains(id) || result.IsAggregatedForm) + { + return ImmutableArray.Empty; + } + + switch (kind) + { + case AnalysisKind.Syntax: + return result.GetResultOrEmpty(result.SyntaxLocals, id); + case AnalysisKind.Semantic: + return result.GetResultOrEmpty(result.SemanticLocals, id); + case AnalysisKind.NonLocal: + return result.GetResultOrEmpty(result.NonLocals, id); + default: + return Contract.FailWithReturn>("shouldn't reach here"); + } + } } } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_BuildSynchronization.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_BuildSynchronization.cs index 0755cc990e6ba..d8556a9113332 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_BuildSynchronization.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_BuildSynchronization.cs @@ -57,7 +57,7 @@ private async Task CreateProjectAnalysisDataAsync(Project p var oldAnalysisData = await ProjectAnalysisData.CreateAsync(project, stateSets, avoidLoadingData, CancellationToken.None).ConfigureAwait(false); var newResult = CreateAnalysisResults(project, stateSets, oldAnalysisData, diagnostics); - return new ProjectAnalysisData(VersionStamp.Default, oldAnalysisData.Result, newResult); + return new ProjectAnalysisData(project.Id, VersionStamp.Default, oldAnalysisData.Result, newResult); } private ImmutableDictionary CreateAnalysisResults( @@ -84,7 +84,7 @@ private ImmutableDictionary CreateAnalysisRe syntaxLocals: ImmutableDictionary>.Empty, semanticLocals: group.Where(g => g.Key != null).ToImmutableDictionary(g => g.Key, g => g.ToImmutableArray()), nonLocals: ImmutableDictionary>.Empty, - others: ImmutableArray.Empty); + others: group.Where(g => g.Key == null).SelectMany(g => g).ToImmutableArrayOrEmpty()); builder.Add(stateSet.Analyzer, result); } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs index 2da4187726011..f67a6eba13e65 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs @@ -45,9 +45,9 @@ private abstract class DiagnosticGetter { protected readonly DiagnosticIncrementalAnalyzer Owner; - protected readonly Solution Solution; - protected readonly ProjectId ProjectId; - protected readonly DocumentId DocumentId; + protected readonly Solution CurrentSolution; + protected readonly ProjectId CurrentProjectId; + protected readonly DocumentId CurrentDocumentId; protected readonly object Id; protected readonly bool IncludeSuppressedDiagnostics; @@ -62,10 +62,10 @@ public DiagnosticGetter( bool includeSuppressedDiagnostics) { Owner = owner; - Solution = solution; + CurrentSolution = solution; - DocumentId = documentId; - ProjectId = projectId; + CurrentDocumentId = documentId; + CurrentProjectId = projectId; Id = id; IncludeSuppressedDiagnostics = includeSuppressedDiagnostics; @@ -74,8 +74,8 @@ public DiagnosticGetter( var argsId = id as LiveDiagnosticUpdateArgsId; if (argsId != null) { - DocumentId = DocumentId ?? argsId.Key as DocumentId; - ProjectId = ProjectId ?? (argsId.Key as ProjectId) ?? DocumentId.ProjectId; + CurrentDocumentId = CurrentDocumentId ?? argsId.Key as DocumentId; + CurrentProjectId = CurrentProjectId ?? (argsId.Key as ProjectId) ?? CurrentDocumentId.ProjectId; } _builder = null; @@ -83,8 +83,8 @@ public DiagnosticGetter( protected StateManager StateManager => this.Owner._stateManager; - protected Project Project => Solution.GetProject(ProjectId); - protected Document Document => Solution.GetDocument(DocumentId); + protected Project CurrentProject => CurrentSolution.GetProject(CurrentProjectId); + protected Document CurrentDocument => CurrentSolution.GetDocument(CurrentDocumentId); protected virtual bool ShouldIncludeDiagnostic(DiagnosticData diagnostic) => true; @@ -98,7 +98,7 @@ protected ImmutableArray GetDiagnosticData() public async Task> GetSpecificDiagnosticsAsync(CancellationToken cancellationToken) { - if (Solution == null) + if (CurrentSolution == null) { return ImmutableArray.Empty; } @@ -109,19 +109,19 @@ public async Task> GetSpecificDiagnosticsAsync(Ca return ImmutableArray.Empty; } - if (Project == null) + if (CurrentProject == null) { // when we return cached result, make sure we at least return something that exist in current solution return ImmutableArray.Empty; } - var stateSet = this.StateManager.GetOrCreateStateSet(Project, argsId.Analyzer); + var stateSet = this.StateManager.GetOrCreateStateSet(CurrentProject, argsId.Analyzer); if (stateSet == null) { return ImmutableArray.Empty; } - var diagnostics = await GetDiagnosticsAsync(stateSet, Project, DocumentId, (AnalysisKind)argsId.Kind, cancellationToken).ConfigureAwait(false); + var diagnostics = await GetDiagnosticsAsync(stateSet, CurrentProject, CurrentDocumentId, (AnalysisKind)argsId.Kind, cancellationToken).ConfigureAwait(false); if (diagnostics == null) { // Document or project might have been removed from the solution. @@ -133,40 +133,34 @@ public async Task> GetSpecificDiagnosticsAsync(Ca public async Task> GetDiagnosticsAsync(CancellationToken cancellationToken) { - if (Solution == null) + if (CurrentSolution == null) { return ImmutableArray.Empty; } - if (ProjectId != null) + if (CurrentProjectId != null) { - if (Project == null) + if (CurrentProject == null) { return GetDiagnosticData(); } - var documentIds = DocumentId != null ? SpecializedCollections.SingletonEnumerable(DocumentId) : Project.DocumentIds; + var documentIds = CurrentDocumentId != null ? SpecializedCollections.SingletonEnumerable(CurrentDocumentId) : CurrentProject.DocumentIds; // return diagnostics specific to one project or document - await AppendDiagnosticsAsync(Project, DocumentId, documentIds, cancellationToken).ConfigureAwait(false); + await AppendDiagnosticsAsync(CurrentProject, CurrentDocumentId, documentIds, cancellationToken).ConfigureAwait(false); return GetDiagnosticData(); } - await AppendDiagnosticsAsync(Solution, cancellationToken).ConfigureAwait(false); + await AppendDiagnosticsAsync(CurrentSolution, cancellationToken).ConfigureAwait(false); return GetDiagnosticData(); } protected async Task AppendDiagnosticsAsync(Solution solution, CancellationToken cancellationToken) { - // when we return cached result, make sure we at least return something that exist in current solution - if (Project == null) - { - return; - } - // PERF: should run this concurrently? analyzer driver itself is already running concurrently. DocumentId nullTargetDocumentId = null; - foreach (var project in Solution.Projects) + foreach (var project in solution.Projects) { await AppendDiagnosticsAsync(project, nullTargetDocumentId, project.DocumentIds, cancellationToken: cancellationToken).ConfigureAwait(false); } @@ -243,12 +237,12 @@ public IDECachedDiagnosticGetter(DiagnosticIncrementalAnalyzer owner, Solution s protected override async Task AppendDiagnosticsAsync(Project project, DocumentId targetDocumentId, IEnumerable documentIds, CancellationToken cancellationToken) { // when we return cached result, make sure we at least return something that exist in current solution - if (Project == null) + if (project == null) { return; } - foreach (var stateSet in StateManager.GetOrCreateStateSets(project)) + foreach (var stateSet in StateManager.GetStateSets(project.Id)) { foreach (var documentId in documentIds) { @@ -286,7 +280,7 @@ protected override async Task AppendDiagnosticsAsync(Project project, DocumentId private ImmutableArray? GetActiveFileDiagnostics(StateSet stateSet, DocumentId documentId, AnalysisKind kind) { - if (documentId == null) + if (documentId == null || kind == AnalysisKind.NonLocal) { return null; } @@ -313,7 +307,7 @@ protected override async Task AppendDiagnosticsAsync(Project project, DocumentId if (documentId != null) { // file doesn't exist in current solution - var document = Solution.GetDocument(documentId); + var document = project.Solution.GetDocument(documentId); if (document == null) { return null; @@ -358,20 +352,20 @@ public IDELatestDiagnosticGetter(DiagnosticIncrementalAnalyzer owner, ImmutableH public async Task> GetProjectDiagnosticsAsync(CancellationToken cancellationToken) { - if (Solution == null) + if (CurrentSolution == null) { return GetDiagnosticData(); } DocumentId nullTargetDocumentId = null; - if (ProjectId != null) + if (CurrentProjectId != null) { - await AppendDiagnosticsAsync(Project, nullTargetDocumentId, SpecializedCollections.EmptyEnumerable(), cancellationToken).ConfigureAwait(false); + await AppendDiagnosticsAsync(CurrentProject, nullTargetDocumentId, SpecializedCollections.EmptyEnumerable(), cancellationToken).ConfigureAwait(false); return GetDiagnosticData(); } - await AppendDiagnosticsAsync(Solution, cancellationToken).ConfigureAwait(false); + await AppendDiagnosticsAsync(CurrentSolution, cancellationToken).ConfigureAwait(false); return GetDiagnosticData(); } @@ -383,7 +377,7 @@ protected override bool ShouldIncludeDiagnostic(DiagnosticData diagnostic) protected override async Task AppendDiagnosticsAsync(Project project, DocumentId targetDocumentId, IEnumerable documentIds, CancellationToken cancellationToken) { // when we return cached result, make sure we at least return something that exist in current solution - if (Project == null) + if (project == null) { return; } @@ -393,9 +387,9 @@ protected override async Task AppendDiagnosticsAsync(Project project, DocumentId var stateSets = StateManager.GetOrCreateStateSets(project).Where(s => ShouldIncludeStateSet(project, s)).ToImmutableArrayOrEmpty(); var concurrentAnalysis = true; - var analyzerDriver = await Owner._compilationManager.CreateAnalyzerDriverAsync(project, stateSets, concurrentAnalysis, IncludeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); + var analyzerDriverOpt = await Owner._compilationManager.CreateAnalyzerDriverAsync(project, stateSets, concurrentAnalysis, IncludeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); - var result = await Owner._executor.GetProjectAnalysisDataAsync(analyzerDriver, project, stateSets, cancellationToken).ConfigureAwait(false); + var result = await Owner._executor.GetProjectAnalysisDataAsync(analyzerDriverOpt, project, stateSets, cancellationToken).ConfigureAwait(false); foreach (var stateSet in stateSets) { @@ -439,11 +433,11 @@ private bool ShouldIncludeStateSet(Project project, StateSet stateSet) var concurrentAnalysis = true; var stateSets = SpecializedCollections.SingletonCollection(stateSet); - var analyzerDriver = await Owner._compilationManager.CreateAnalyzerDriverAsync(project, stateSets, concurrentAnalysis, IncludeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); + var analyzerDriverOpt = await Owner._compilationManager.CreateAnalyzerDriverAsync(project, stateSets, concurrentAnalysis, IncludeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); if (documentId != null) { - var document = Solution.GetDocument(documentId); + var document = project.Solution.GetDocument(documentId); Contract.ThrowIfNull(document); switch (kind) @@ -451,12 +445,12 @@ private bool ShouldIncludeStateSet(Project project, StateSet stateSet) case AnalysisKind.Syntax: case AnalysisKind.Semantic: { - var result = await Owner._executor.GetDocumentAnalysisDataAsync(analyzerDriver, document, stateSet, kind, cancellationToken).ConfigureAwait(false); + var result = await Owner._executor.GetDocumentAnalysisDataAsync(analyzerDriverOpt, document, stateSet, kind, cancellationToken).ConfigureAwait(false); return result.Items; } case AnalysisKind.NonLocal: { - var nonLocalDocumentResult = await Owner._executor.GetProjectAnalysisDataAsync(analyzerDriver, project, stateSets, cancellationToken).ConfigureAwait(false); + var nonLocalDocumentResult = await Owner._executor.GetProjectAnalysisDataAsync(analyzerDriverOpt, project, stateSets, cancellationToken).ConfigureAwait(false); var analysisResult = nonLocalDocumentResult.GetResult(stateSet.Analyzer); return GetResult(analysisResult, AnalysisKind.NonLocal, documentId); } @@ -466,7 +460,7 @@ private bool ShouldIncludeStateSet(Project project, StateSet stateSet) } Contract.ThrowIfFalse(kind == AnalysisKind.NonLocal); - var projectResult = await Owner._executor.GetProjectAnalysisDataAsync(analyzerDriver, project, stateSets, cancellationToken).ConfigureAwait(false); + var projectResult = await Owner._executor.GetProjectAnalysisDataAsync(analyzerDriverOpt, project, stateSets, cancellationToken).ConfigureAwait(false); return projectResult.GetResult(stateSet.Analyzer).Others; } } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs index 9b2daea2af9d5..03c0ade9ff973 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs @@ -46,7 +46,7 @@ private class LatestDiagnosticsForSpanGetter private readonly Document _document; private readonly IEnumerable _stateSets; - private readonly CompilationWithAnalyzers _analyzerDriver; + private readonly CompilationWithAnalyzers _analyzerDriverOpt; private readonly DiagnosticAnalyzer _compilerAnalyzer; private readonly TextSpan _range; @@ -62,17 +62,19 @@ public static async Task CreateAsync( DiagnosticIncrementalAnalyzer owner, Document document, TextSpan range, bool blockForData, bool includeSuppressedDiagnostics, CancellationToken cancellationToken) { var concurrentAnalysis = false; - var stateSets = owner._stateManager.GetOrCreateStateSets(document.Project); - var analyzerDriver = await owner._compilationManager.CreateAnalyzerDriverAsync(document.Project, stateSets, concurrentAnalysis, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); - return new LatestDiagnosticsForSpanGetter(owner, document, stateSets, analyzerDriver, range, blockForData, includeSuppressedDiagnostics); + // REVIEW: IsAnalyzerSuppressed can be quite expensive in some cases. try to find a way to make it cheaper + var stateSets = owner._stateManager.GetOrCreateStateSets(document.Project).Where(s => !owner.Owner.IsAnalyzerSuppressed(s.Analyzer, document.Project)); + var analyzerDriverOpt = await owner._compilationManager.CreateAnalyzerDriverAsync(document.Project, stateSets, concurrentAnalysis, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); + + return new LatestDiagnosticsForSpanGetter(owner, analyzerDriverOpt, document, stateSets, range, blockForData, includeSuppressedDiagnostics); } private LatestDiagnosticsForSpanGetter( DiagnosticIncrementalAnalyzer owner, + CompilationWithAnalyzers analyzerDriverOpt, Document document, IEnumerable stateSets, - CompilationWithAnalyzers analyzerDriver, TextSpan range, bool blockForData, bool includeSuppressedDiagnostics) { _owner = owner; @@ -81,7 +83,7 @@ private LatestDiagnosticsForSpanGetter( _document = document; _stateSets = stateSets; - _analyzerDriver = analyzerDriver; + _analyzerDriverOpt = analyzerDriverOpt; _compilerAnalyzer = _owner.HostAnalyzerManager.GetCompilerDiagnosticAnalyzer(_document.Project.Language); _range = range; @@ -180,7 +182,7 @@ private async Task> GetCompilerSemanticDiagnosticsAs private Task> GetSyntaxDiagnosticsAsync(DiagnosticAnalyzer analyzer, CancellationToken cancellationToken) { - return _owner._executor.ComputeDiagnosticsAsync(_analyzerDriver, _document, analyzer, AnalysisKind.Syntax, _range, cancellationToken); + return _owner._executor.ComputeDiagnosticsAsync(_analyzerDriverOpt, _document, analyzer, AnalysisKind.Syntax, _range, cancellationToken); } private Task> GetSemanticDiagnosticsAsync(DiagnosticAnalyzer analyzer, CancellationToken cancellationToken) @@ -188,7 +190,7 @@ private Task> GetSemanticDiagnosticsAsync(Diagnostic var supportsSemanticInSpan = analyzer.SupportsSpanBasedSemanticDiagnosticAnalysis(); var analysisSpan = supportsSemanticInSpan ? (TextSpan?)_range : null; - return _owner._executor.ComputeDiagnosticsAsync(_analyzerDriver, _document, analyzer, AnalysisKind.Semantic, analysisSpan, cancellationToken); + return _owner._executor.ComputeDiagnosticsAsync(_analyzerDriverOpt, _document, analyzer, AnalysisKind.Semantic, analysisSpan, cancellationToken); } private async Task> GetProjectDiagnosticsAsync(DiagnosticAnalyzer analyzer, CancellationToken cancellationToken) @@ -196,7 +198,7 @@ private async Task> GetProjectDiagnosticsAsync(Diagn if (_projectResultCache == null) { // execute whole project as one shot and cache the result. - _projectResultCache = await _owner._executor.ComputeDiagnosticsAsync(_analyzerDriver, _project, _stateSets, cancellationToken).ConfigureAwait(false); + _projectResultCache = await _owner._executor.ComputeDiagnosticsAsync(_analyzerDriverOpt, _project, _stateSets, cancellationToken).ConfigureAwait(false); } AnalysisResult result; @@ -288,9 +290,7 @@ private async Task TryGetDocumentDiagnosticsAsync( List list, CancellationToken cancellationToken) { - // REVIEW: IsAnalyzerSuppressed can be quite expensive in some cases. try to find a way to make it cheaper - if (!_owner.SupportAnalysisKind(stateSet.Analyzer, stateSet.Language, kind) || - _owner.Owner.IsAnalyzerSuppressed(stateSet.Analyzer, _document.Project)) + if (!_owner.SupportAnalysisKind(stateSet.Analyzer, stateSet.Language, kind)) { return true; } @@ -336,9 +336,7 @@ private async Task TryGetProjectDiagnosticsAsync( List list, CancellationToken cancellationToken) { - // REVIEW: IsAnalyzerSuppressed can be quite expensive in some cases. try to find a way to make it cheaper - if (!stateSet.Analyzer.SupportsProjectDiagnosticAnalysis() || - _owner.Owner.IsAnalyzerSuppressed(stateSet.Analyzer, _document.Project)) + if (!stateSet.Analyzer.SupportsProjectDiagnosticAnalysis()) { return true; } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs index 6e2eb81973a55..f0fe49d324bb6 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs @@ -37,13 +37,13 @@ private async Task AnalyzeDocumentForKindAsync(Document document, AnalysisKind k } var stateSets = _stateManager.GetOrUpdateStateSets(document.Project); - var analyzerDriver = await _compilationManager.GetAnalyzerDriverAsync(document.Project, stateSets, cancellationToken).ConfigureAwait(false); + var analyzerDriverOpt = await _compilationManager.GetAnalyzerDriverAsync(document.Project, stateSets, cancellationToken).ConfigureAwait(false); foreach (var stateSet in stateSets) { var analyzer = stateSet.Analyzer; - var result = await _executor.GetDocumentAnalysisDataAsync(analyzerDriver, document, stateSet, kind, cancellationToken).ConfigureAwait(false); + var result = await _executor.GetDocumentAnalysisDataAsync(analyzerDriverOpt, document, stateSet, kind, cancellationToken).ConfigureAwait(false); if (result.FromCache) { RaiseDocumentDiagnosticsIfNeeded(document, stateSet, kind, result.Items); @@ -75,9 +75,9 @@ public override async Task AnalyzeProjectAsync(Project project, bool semanticsCh // get driver only with active analyzers. var includeSuppressedDiagnostics = true; - var analyzerDriver = await _compilationManager.CreateAnalyzerDriverAsync(project, activeAnalyzers, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); + var analyzerDriverOpt = await _compilationManager.CreateAnalyzerDriverAsync(project, activeAnalyzers, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); - var result = await _executor.GetProjectAnalysisDataAsync(analyzerDriver, project, stateSets, cancellationToken).ConfigureAwait(false); + var result = await _executor.GetProjectAnalysisDataAsync(analyzerDriverOpt, project, stateSets, cancellationToken).ConfigureAwait(false); if (result.FromCache) { RaiseProjectDiagnosticsIfNeeded(project, stateSets, result.Result); @@ -207,21 +207,6 @@ private static bool AnalysisEnabled(Document document) return document.IsOpen(); } - private static ImmutableArray GetResult(AnalysisResult result, AnalysisKind kind, DocumentId id) - { - switch (kind) - { - case AnalysisKind.Syntax: - return result.GetResultOrEmpty(result.SyntaxLocals, id); - case AnalysisKind.Semantic: - return result.GetResultOrEmpty(result.SemanticLocals, id); - case AnalysisKind.NonLocal: - return result.GetResultOrEmpty(result.NonLocals, id); - default: - return Contract.FailWithReturn>("shouldn't reach here"); - } - } - private void RaiseLocalDocumentEventsFromProjectOverActiveFile(IEnumerable stateSets, Document document, bool activeFileDiagnosticExist) { // PERF: activeFileDiagnosticExist is perf optimization to reduce raising events unnecessarily. @@ -292,8 +277,8 @@ private void RaiseProjectDiagnosticsIfNeeded( { var analyzer = stateSet.Analyzer; - var oldAnalysisResult = ImmutableDictionary.GetValueOrDefault(oldResult, analyzer); - var newAnalysisResult = ImmutableDictionary.GetValueOrDefault(newResult, analyzer); + var oldAnalysisResult = GetResultOrEmpty(oldResult, analyzer, project.Id, VersionStamp.Default); + var newAnalysisResult = GetResultOrEmpty(newResult, analyzer, project.Id, VersionStamp.Default); // Perf - 4 different cases. // upper 3 cases can be removed and it will still work. but this is hot path so if we can bail out From 7e4d0717acf983bcdc571b8fd6ce9456fa5b3003 Mon Sep 17 00:00:00 2001 From: Heejae Chang Date: Tue, 12 Apr 2016 11:56:40 -0700 Subject: [PATCH 10/24] use compilation based concurrency (async) rather than driver based one. (synchronous) --- ...cIncrementalAnalyzer.CompilationManager.cs | 27 +++++++------------ ...osticIncrementalAnalyzer_GetDiagnostics.cs | 16 +++++++---- ...crementalAnalyzer_GetDiagnosticsForSpan.cs | 4 +-- 3 files changed, 21 insertions(+), 26 deletions(-) diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs index 96866e9f9db92..125f42f18bb6d 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs @@ -49,9 +49,8 @@ public async Task GetAnalyzerDriverAsync(Project proje } // Create driver that holds onto compilation and associated analyzers - var concurrentAnalysis = false; var includeSuppressedDiagnostics = true; - var newAnalyzerDriverOpt = await CreateAnalyzerDriverAsync(project, stateSets, concurrentAnalysis, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); + var newAnalyzerDriverOpt = await CreateAnalyzerDriverAsync(project, stateSets, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); // Add new analyzer driver to the map analyzerDriverOpt = _map.GetValue(project, _ => newAnalyzerDriverOpt); @@ -66,22 +65,14 @@ public async Task GetAnalyzerDriverAsync(Project proje return analyzerDriverOpt; } - public Task CreateAnalyzerDriverAsync( - Project project, IEnumerable stateSets, bool concurrentAnalysis, bool includeSuppressedDiagnostics, CancellationToken cancellationToken) + public Task CreateAnalyzerDriverAsync(Project project, IEnumerable stateSets, bool includeSuppressedDiagnostics, CancellationToken cancellationToken) { var analyzers = stateSets.Select(s => s.Analyzer).ToImmutableArrayOrEmpty(); - return CreateAnalyzerDriverAsync(project, analyzers, concurrentAnalysis, includeSuppressedDiagnostics, cancellationToken); + return CreateAnalyzerDriverAsync(project, analyzers, includeSuppressedDiagnostics, cancellationToken); } - public Task CreateAnalyzerDriverAsync( + public async Task CreateAnalyzerDriverAsync( Project project, ImmutableArray analyzers, bool includeSuppressedDiagnostics, CancellationToken cancellationToken) - { - var concurrentAnalysis = false; - return CreateAnalyzerDriverAsync(project, analyzers, concurrentAnalysis, includeSuppressedDiagnostics, cancellationToken); - } - - private async Task CreateAnalyzerDriverAsync( - Project project, ImmutableArray analyzers, bool concurrentAnalysis, bool includeSuppressedDiagnostics, CancellationToken cancellationToken) { if (!project.SupportsCompilation) { @@ -92,14 +83,13 @@ private async Task CreateAnalyzerDriverAsync( // Create driver that holds onto compilation and associated analyzers return CreateAnalyzerDriver( - project, compilation, analyzers, concurrentAnalysis: concurrentAnalysis, logAnalyzerExecutionTime: false, reportSuppressedDiagnostics: includeSuppressedDiagnostics); + project, compilation, analyzers, logAnalyzerExecutionTime: false, reportSuppressedDiagnostics: includeSuppressedDiagnostics); } private CompilationWithAnalyzers CreateAnalyzerDriver( Project project, Compilation compilation, ImmutableArray analyzers, - bool concurrentAnalysis, bool logAnalyzerExecutionTime, bool reportSuppressedDiagnostics) { @@ -113,7 +103,7 @@ private CompilationWithAnalyzers CreateAnalyzerDriver( Contract.ThrowIfFalse(project.SupportsCompilation); AssertCompilation(project, compilation); - var analysisOptions = GetAnalyzerOptions(project, concurrentAnalysis, logAnalyzerExecutionTime, reportSuppressedDiagnostics); + var analysisOptions = GetAnalyzerOptions(project, logAnalyzerExecutionTime, reportSuppressedDiagnostics); // Create driver that holds onto compilation and associated analyzers return compilation.WithAnalyzers(analyzers, analysisOptions); @@ -121,15 +111,16 @@ private CompilationWithAnalyzers CreateAnalyzerDriver( private CompilationWithAnalyzersOptions GetAnalyzerOptions( Project project, - bool concurrentAnalysis, bool logAnalyzerExecutionTime, bool reportSuppressedDiagnostics) { + // in IDE, we always set concurrentAnalysis == false otherwise, we can get into thread starvation due to + // async being used with syncronous blocking concurrency. return new CompilationWithAnalyzersOptions( options: new WorkspaceAnalyzerOptions(project.AnalyzerOptions, project.Solution.Workspace), onAnalyzerException: GetOnAnalyzerException(project.Id), analyzerExceptionFilter: GetAnalyzerExceptionFilter(project), - concurrentAnalysis: concurrentAnalysis, + concurrentAnalysis: false, logAnalyzerExecutionTime: logAnalyzerExecutionTime, reportSuppressedDiagnostics: reportSuppressedDiagnostics); } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs index f67a6eba13e65..f9a6b547254a7 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs @@ -160,10 +160,18 @@ protected async Task AppendDiagnosticsAsync(Solution solution, CancellationToken { // PERF: should run this concurrently? analyzer driver itself is already running concurrently. DocumentId nullTargetDocumentId = null; + + var tasks = new Task[solution.ProjectIds.Count]; + var index = 0; foreach (var project in solution.Projects) { - await AppendDiagnosticsAsync(project, nullTargetDocumentId, project.DocumentIds, cancellationToken: cancellationToken).ConfigureAwait(false); + var localProject = project; + tasks[index++] = Task.Run( + () => AppendDiagnosticsAsync( + localProject, nullTargetDocumentId, localProject.DocumentIds, cancellationToken: cancellationToken), cancellationToken); } + + await Task.WhenAll(tasks).ConfigureAwait(false); } protected void AppendDiagnostics(IEnumerable items) @@ -386,8 +394,7 @@ protected override async Task AppendDiagnosticsAsync(Project project, DocumentId // REVIEW: IsAnalyzerSuppressed call seems can be quite expensive in certain condition. is there any other way to do this? var stateSets = StateManager.GetOrCreateStateSets(project).Where(s => ShouldIncludeStateSet(project, s)).ToImmutableArrayOrEmpty(); - var concurrentAnalysis = true; - var analyzerDriverOpt = await Owner._compilationManager.CreateAnalyzerDriverAsync(project, stateSets, concurrentAnalysis, IncludeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); + var analyzerDriverOpt = await Owner._compilationManager.CreateAnalyzerDriverAsync(project, stateSets, IncludeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); var result = await Owner._executor.GetProjectAnalysisDataAsync(analyzerDriverOpt, project, stateSets, cancellationToken).ConfigureAwait(false); @@ -431,9 +438,8 @@ private bool ShouldIncludeStateSet(Project project, StateSet stateSet) { cancellationToken.ThrowIfCancellationRequested(); - var concurrentAnalysis = true; var stateSets = SpecializedCollections.SingletonCollection(stateSet); - var analyzerDriverOpt = await Owner._compilationManager.CreateAnalyzerDriverAsync(project, stateSets, concurrentAnalysis, IncludeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); + var analyzerDriverOpt = await Owner._compilationManager.CreateAnalyzerDriverAsync(project, stateSets, IncludeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); if (documentId != null) { diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs index 03c0ade9ff973..d331f2057acc8 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs @@ -61,11 +61,9 @@ private class LatestDiagnosticsForSpanGetter public static async Task CreateAsync( DiagnosticIncrementalAnalyzer owner, Document document, TextSpan range, bool blockForData, bool includeSuppressedDiagnostics, CancellationToken cancellationToken) { - var concurrentAnalysis = false; - // REVIEW: IsAnalyzerSuppressed can be quite expensive in some cases. try to find a way to make it cheaper var stateSets = owner._stateManager.GetOrCreateStateSets(document.Project).Where(s => !owner.Owner.IsAnalyzerSuppressed(s.Analyzer, document.Project)); - var analyzerDriverOpt = await owner._compilationManager.CreateAnalyzerDriverAsync(document.Project, stateSets, concurrentAnalysis, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); + var analyzerDriverOpt = await owner._compilationManager.CreateAnalyzerDriverAsync(document.Project, stateSets, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); return new LatestDiagnosticsForSpanGetter(owner, analyzerDriverOpt, document, stateSets, range, blockForData, includeSuppressedDiagnostics); } From 11199c488446ce65e4b4b805f91c804027da7211 Mon Sep 17 00:00:00 2001 From: Heejae Chang Date: Tue, 12 Apr 2016 12:43:12 -0700 Subject: [PATCH 11/24] some clean up --- .../Diagnostics/EngineV2/CompilerDiagnosticExecutor.cs | 10 +++++----- .../EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/CompilerDiagnosticExecutor.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/CompilerDiagnosticExecutor.cs index 2af770fa9a930..50a0e63fdc0ec 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/CompilerDiagnosticExecutor.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/CompilerDiagnosticExecutor.cs @@ -103,17 +103,17 @@ private ImmutableDictionary> Convert( return map == null ? ImmutableDictionary>.Empty : map.ToImmutableDictionary(kv => kv.Key, kv => kv.Value.ToImmutableArray()); } - public void AddSyntaxDiagnostics(DocumentId documentId, IEnumerable diagnostics) + public void AddExternalSyntaxDiagnostics(DocumentId documentId, IEnumerable diagnostics) { - AddDiagnostics(ref _lazySyntaxLocals, documentId, diagnostics); + AddExternalDiagnostics(ref _lazySyntaxLocals, documentId, diagnostics); } - public void AddSemanticDiagnostics(DocumentId documentId, IEnumerable diagnostics) + public void AddExternalSemanticDiagnostics(DocumentId documentId, IEnumerable diagnostics) { - AddDiagnostics(ref _lazySemanticLocals, documentId, diagnostics); + AddExternalDiagnostics(ref _lazySemanticLocals, documentId, diagnostics); } - private void AddDiagnostics( + private void AddExternalDiagnostics( ref Dictionary> lazyLocals, DocumentId documentId, IEnumerable diagnostics) { Contract.ThrowIfTrue(_project.SupportsCompilation); diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs index fef2e560f9178..058820aad211a 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs @@ -167,8 +167,8 @@ private async Task> Merg } else { - builder.AddSyntaxDiagnostics(document.Id, await ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync(document, documentAnalyzer, AnalysisKind.Syntax, compilationOpt, cancellationToken).ConfigureAwait(false)); - builder.AddSemanticDiagnostics(document.Id, await ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync(document, documentAnalyzer, AnalysisKind.Semantic, compilationOpt, cancellationToken).ConfigureAwait(false)); + builder.AddExternalSyntaxDiagnostics(document.Id, await ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync(document, documentAnalyzer, AnalysisKind.Syntax, compilationOpt, cancellationToken).ConfigureAwait(false)); + builder.AddExternalSemanticDiagnostics(document.Id, await ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync(document, documentAnalyzer, AnalysisKind.Semantic, compilationOpt, cancellationToken).ConfigureAwait(false)); } } } @@ -335,7 +335,7 @@ private IEnumerable ConvertToLocalDiagnosticsWithoutCompilation( var lineSpan = location.GetLineSpan(); var documentIds = project.Solution.GetDocumentIdsWithFilePath(lineSpan.Path); - if (documentIds.IsEmpty || documentIds.Any(id => id != targetDocument.Id)) + if (documentIds.IsEmpty || documentIds.All(id => id != targetDocument.Id)) { continue; } From eb3aa8dc4b9abd3707a3fcaf62e3a6801196ca36 Mon Sep 17 00:00:00 2001 From: Heejae Chang Date: Tue, 12 Apr 2016 13:02:23 -0700 Subject: [PATCH 12/24] fixed merge issue 2 --- .../Core/CodeAnalysisTest/project.lock.json | 935 ------------------ .../MSBuildTask/Desktop/project.lock.json | 668 ------------- 2 files changed, 1603 deletions(-) delete mode 100644 src/Compilers/Core/CodeAnalysisTest/project.lock.json delete mode 100644 src/Compilers/Core/MSBuildTask/Desktop/project.lock.json diff --git a/src/Compilers/Core/CodeAnalysisTest/project.lock.json b/src/Compilers/Core/CodeAnalysisTest/project.lock.json deleted file mode 100644 index 1582b743f6999..0000000000000 --- a/src/Compilers/Core/CodeAnalysisTest/project.lock.json +++ /dev/null @@ -1,935 +0,0 @@ -{ - "locked": false, - "version": 1, - "targets": { - ".NETFramework,Version=v4.5": { - "Microsoft.CodeAnalysis.Test.Resources.Proprietary/1.2.0-beta1-20160105-04": { - "compile": { - "lib/net45/Microsoft.CodeAnalysis.Test.Resources.Proprietary.dll": {} - }, - "runtime": { - "lib/net45/Microsoft.CodeAnalysis.Test.Resources.Proprietary.dll": {} - } - }, - "Microsoft.DiaSymReader/1.0.7": { - "compile": { - "lib/net20/Microsoft.DiaSymReader.dll": {} - }, - "runtime": { - "lib/net20/Microsoft.DiaSymReader.dll": {} - } - }, - "Microsoft.DiaSymReader.Native/1.3.3": { - "runtimeTargets": { - "runtimes/win-x64/native/Microsoft.DiaSymReader.Native.amd64.dll": { - "assetType": "native", - "rid": "win-x64" - }, - "runtimes/win-x86/native/Microsoft.DiaSymReader.Native.x86.dll": { - "assetType": "native", - "rid": "win-x86" - }, - "runtimes/win/native/Microsoft.DiaSymReader.Native.amd64.dll": { - "assetType": "native", - "rid": "win" - }, - "runtimes/win/native/Microsoft.DiaSymReader.Native.arm.dll": { - "assetType": "native", - "rid": "win" - }, - "runtimes/win/native/Microsoft.DiaSymReader.Native.x86.dll": { - "assetType": "native", - "rid": "win" - }, - "runtimes/win8-arm/native/Microsoft.DiaSymReader.Native.arm.dll": { - "assetType": "native", - "rid": "win8-arm" - } - } - }, - "Moq/4.2.1402.2112": { - "compile": { - "lib/net40/Moq.dll": {} - }, - "runtime": { - "lib/net40/Moq.dll": {} - } - }, - "System.Collections/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "System.Collections.Immutable/1.1.37": { - "dependencies": { - "System.Collections": "4.0.0", - "System.Diagnostics.Debug": "4.0.0", - "System.Globalization": "4.0.0", - "System.Linq": "4.0.0", - "System.Resources.ResourceManager": "4.0.0", - "System.Runtime": "4.0.0", - "System.Runtime.Extensions": "4.0.0", - "System.Threading": "4.0.0" - }, - "compile": { - "lib/dotnet/System.Collections.Immutable.dll": {} - }, - "runtime": { - "lib/dotnet/System.Collections.Immutable.dll": {} - } - }, - "System.Diagnostics.Debug/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "System.Globalization/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "System.Linq/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "System.Reflection.Metadata/1.2.0": { - "dependencies": { - "System.Collections.Immutable": "1.1.37" - }, - "compile": { - "lib/portable-net45+win8/System.Reflection.Metadata.dll": {} - }, - "runtime": { - "lib/portable-net45+win8/System.Reflection.Metadata.dll": {} - } - }, - "System.Resources.ResourceManager/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "System.Runtime/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "System.Runtime.Extensions/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "System.Threading/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "xunit/2.1.0": { - "dependencies": { - "xunit.assert": "[2.1.0]", - "xunit.core": "[2.1.0]" - } - }, - "xunit.abstractions/2.0.0": { - "compile": { - "lib/net35/xunit.abstractions.dll": {} - }, - "runtime": { - "lib/net35/xunit.abstractions.dll": {} - } - }, - "xunit.assert/2.1.0": { - "compile": { - "lib/dotnet/xunit.assert.dll": {} - }, - "runtime": { - "lib/dotnet/xunit.assert.dll": {} - } - }, - "xunit.core/2.1.0": { - "dependencies": { - "xunit.extensibility.core": "[2.1.0]", - "xunit.extensibility.execution": "[2.1.0]" - } - }, - "xunit.extensibility.core/2.1.0": { - "dependencies": { - "xunit.abstractions": "[2.0.0]" - }, - "compile": { - "lib/dotnet/xunit.core.dll": {} - }, - "runtime": { - "lib/dotnet/xunit.core.dll": {} - } - }, - "xunit.extensibility.execution/2.1.0": { - "dependencies": { - "xunit.extensibility.core": "[2.1.0]" - }, - "compile": { - "lib/net45/xunit.execution.desktop.dll": {} - }, - "runtime": { - "lib/net45/xunit.execution.desktop.dll": {} - } - }, - "xunit.runner.console/2.1.0": {} - }, - ".NETFramework,Version=v4.5/win7": { - "Microsoft.CodeAnalysis.Test.Resources.Proprietary/1.2.0-beta1-20160105-04": { - "compile": { - "lib/net45/Microsoft.CodeAnalysis.Test.Resources.Proprietary.dll": {} - }, - "runtime": { - "lib/net45/Microsoft.CodeAnalysis.Test.Resources.Proprietary.dll": {} - } - }, - "Microsoft.DiaSymReader/1.0.7": { - "compile": { - "lib/net20/Microsoft.DiaSymReader.dll": {} - }, - "runtime": { - "lib/net20/Microsoft.DiaSymReader.dll": {} - } - }, - "Microsoft.DiaSymReader.Native/1.3.3": {}, - "Moq/4.2.1402.2112": { - "compile": { - "lib/net40/Moq.dll": {} - }, - "runtime": { - "lib/net40/Moq.dll": {} - } - }, - "System.Collections/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "System.Collections.Immutable/1.1.37": { - "dependencies": { - "System.Collections": "4.0.0", - "System.Diagnostics.Debug": "4.0.0", - "System.Globalization": "4.0.0", - "System.Linq": "4.0.0", - "System.Resources.ResourceManager": "4.0.0", - "System.Runtime": "4.0.0", - "System.Runtime.Extensions": "4.0.0", - "System.Threading": "4.0.0" - }, - "compile": { - "lib/dotnet/System.Collections.Immutable.dll": {} - }, - "runtime": { - "lib/dotnet/System.Collections.Immutable.dll": {} - } - }, - "System.Diagnostics.Debug/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "System.Globalization/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "System.Linq/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "System.Reflection.Metadata/1.2.0": { - "dependencies": { - "System.Collections.Immutable": "1.1.37" - }, - "compile": { - "lib/portable-net45+win8/System.Reflection.Metadata.dll": {} - }, - "runtime": { - "lib/portable-net45+win8/System.Reflection.Metadata.dll": {} - } - }, - "System.Resources.ResourceManager/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "System.Runtime/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "System.Runtime.Extensions/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "System.Threading/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "xunit/2.1.0": { - "dependencies": { - "xunit.assert": "[2.1.0]", - "xunit.core": "[2.1.0]" - } - }, - "xunit.abstractions/2.0.0": { - "compile": { - "lib/net35/xunit.abstractions.dll": {} - }, - "runtime": { - "lib/net35/xunit.abstractions.dll": {} - } - }, - "xunit.assert/2.1.0": { - "compile": { - "lib/dotnet/xunit.assert.dll": {} - }, - "runtime": { - "lib/dotnet/xunit.assert.dll": {} - } - }, - "xunit.core/2.1.0": { - "dependencies": { - "xunit.extensibility.core": "[2.1.0]", - "xunit.extensibility.execution": "[2.1.0]" - } - }, - "xunit.extensibility.core/2.1.0": { - "dependencies": { - "xunit.abstractions": "[2.0.0]" - }, - "compile": { - "lib/dotnet/xunit.core.dll": {} - }, - "runtime": { - "lib/dotnet/xunit.core.dll": {} - } - }, - "xunit.extensibility.execution/2.1.0": { - "dependencies": { - "xunit.extensibility.core": "[2.1.0]" - }, - "compile": { - "lib/net45/xunit.execution.desktop.dll": {} - }, - "runtime": { - "lib/net45/xunit.execution.desktop.dll": {} - } - }, - "xunit.runner.console/2.1.0": {} - } - }, - "libraries": { - "Microsoft.CodeAnalysis.Test.Resources.Proprietary/1.2.0-beta1-20160105-04": { - "sha512": "QIkINpihvcij/pZhZdLmSTt4wMgjSwvRjXavvcR86lIRXi7pk4+mNgjFF5FXiR8B4WEmttdb5diyVZq7ZP4unw==", - "type": "package", - "files": [ - "Microsoft.CodeAnalysis.Test.Resources.Proprietary.1.2.0-beta1-20160105-04.nupkg.sha512", - "Microsoft.CodeAnalysis.Test.Resources.Proprietary.nuspec", - "lib/net45/Microsoft.CodeAnalysis.Test.Resources.Proprietary.dll" - ] - }, - "Microsoft.DiaSymReader/1.0.7": { - "sha512": "4tPrkKu02w87HEvoubBGm7Hqjps69DucsBWQvGezwvDV5RJt+eZqdmdC/jNH1qn6hIem9JpJnLBK0abBzhErOg==", - "type": "package", - "files": [ - "Microsoft.DiaSymReader.1.0.7.nupkg.sha512", - "Microsoft.DiaSymReader.nuspec", - "lib/net20/Microsoft.DiaSymReader.dll", - "lib/net20/Microsoft.DiaSymReader.xml", - "lib/portable-net45+win8/Microsoft.DiaSymReader.dll", - "lib/portable-net45+win8/Microsoft.DiaSymReader.xml" - ] - }, - "Microsoft.DiaSymReader.Native/1.3.3": { - "sha512": "mjATkm+L2UlP35gO/ExNutLDfgX4iiwz1l/8sYVoeGHp5WnkEDu0NfIEsC4Oy/pCYeRw0/6SGB+kArJVNNvENQ==", - "type": "package", - "files": [ - "Microsoft.DiaSymReader.Native.1.3.3.nupkg.sha512", - "Microsoft.DiaSymReader.Native.nuspec", - "build/Microsoft.DiaSymReader.Native.props", - "runtimes/win-x64/native/Microsoft.DiaSymReader.Native.amd64.dll", - "runtimes/win-x86/native/Microsoft.DiaSymReader.Native.x86.dll", - "runtimes/win/native/Microsoft.DiaSymReader.Native.amd64.dll", - "runtimes/win/native/Microsoft.DiaSymReader.Native.arm.dll", - "runtimes/win/native/Microsoft.DiaSymReader.Native.x86.dll", - "runtimes/win8-arm/native/Microsoft.DiaSymReader.Native.arm.dll" - ] - }, - "Moq/4.2.1402.2112": { - "sha512": "/TWoXE2OIjJjSvcxER7HMoZwpgETSGlKbLZiME7sVVoPMoqgLvDyjSISveTyHxNoDXd18cZlM8aHdS9ZOAbjMw==", - "type": "package", - "files": [ - "Moq.4.2.1402.2112.nupkg.sha512", - "Moq.nuspec", - "lib/net35/Moq.dll", - "lib/net35/Moq.xml", - "lib/net40/Moq.dll", - "lib/net40/Moq.xml", - "lib/sl4/Moq.Silverlight.dll", - "lib/sl4/Moq.Silverlight.xml" - ] - }, - "System.Collections/4.0.0": { - "sha512": "i2vsGDIEbWdHcUSNDPKZP/ZWod6o740el7mGTCy0dqbCxQh74W4QoC+klUwPEtGEFuvzJ7bJgvwJqscosVNyZQ==", - "type": "package", - "files": [ - "License.rtf", - "System.Collections.4.0.0.nupkg.sha512", - "System.Collections.nuspec", - "lib/MonoAndroid10/_._", - "lib/MonoTouch10/_._", - "lib/net45/_._", - "lib/win8/_._", - "lib/wp80/_._", - "lib/wpa81/_._", - "lib/xamarinios10/_._", - "lib/xamarinmac20/_._", - "ref/MonoAndroid10/_._", - "ref/MonoTouch10/_._", - "ref/dotnet/System.Collections.dll", - "ref/dotnet/System.Collections.xml", - "ref/dotnet/de/System.Collections.xml", - "ref/dotnet/es/System.Collections.xml", - "ref/dotnet/fr/System.Collections.xml", - "ref/dotnet/it/System.Collections.xml", - "ref/dotnet/ja/System.Collections.xml", - "ref/dotnet/ko/System.Collections.xml", - "ref/dotnet/ru/System.Collections.xml", - "ref/dotnet/zh-hans/System.Collections.xml", - "ref/dotnet/zh-hant/System.Collections.xml", - "ref/net45/_._", - "ref/netcore50/System.Collections.dll", - "ref/netcore50/System.Collections.xml", - "ref/netcore50/de/System.Collections.xml", - "ref/netcore50/es/System.Collections.xml", - "ref/netcore50/fr/System.Collections.xml", - "ref/netcore50/it/System.Collections.xml", - "ref/netcore50/ja/System.Collections.xml", - "ref/netcore50/ko/System.Collections.xml", - "ref/netcore50/ru/System.Collections.xml", - "ref/netcore50/zh-hans/System.Collections.xml", - "ref/netcore50/zh-hant/System.Collections.xml", - "ref/win8/_._", - "ref/wp80/_._", - "ref/wpa81/_._", - "ref/xamarinios10/_._", - "ref/xamarinmac20/_._" - ] - }, - "System.Collections.Immutable/1.1.37": { - "sha512": "fTpqwZYBzoklTT+XjTRK8KxvmrGkYHzBiylCcKyQcxiOM8k+QvhNBxRvFHDWzy4OEP5f8/9n+xQ9mEgEXY+muA==", - "type": "package", - "files": [ - "System.Collections.Immutable.1.1.37.nupkg.sha512", - "System.Collections.Immutable.nuspec", - "lib/dotnet/System.Collections.Immutable.dll", - "lib/dotnet/System.Collections.Immutable.xml", - "lib/portable-net45+win8+wp8+wpa81/System.Collections.Immutable.dll", - "lib/portable-net45+win8+wp8+wpa81/System.Collections.Immutable.xml" - ] - }, - "System.Diagnostics.Debug/4.0.0": { - "sha512": "AYJsLLGDVTC/nyURjgAo7Lpye0+HuSkcQujUf+NgQVdC/C/ky5NyamQHCforHJzgqspitMMtBe8B4UBdGXy1zQ==", - "type": "package", - "files": [ - "License.rtf", - "System.Diagnostics.Debug.4.0.0.nupkg.sha512", - "System.Diagnostics.Debug.nuspec", - "lib/MonoAndroid10/_._", - "lib/MonoTouch10/_._", - "lib/net45/_._", - "lib/win8/_._", - "lib/wp80/_._", - "lib/wpa81/_._", - "lib/xamarinios10/_._", - "lib/xamarinmac20/_._", - "ref/MonoAndroid10/_._", - "ref/MonoTouch10/_._", - "ref/dotnet/System.Diagnostics.Debug.dll", - "ref/dotnet/System.Diagnostics.Debug.xml", - "ref/dotnet/de/System.Diagnostics.Debug.xml", - "ref/dotnet/es/System.Diagnostics.Debug.xml", - "ref/dotnet/fr/System.Diagnostics.Debug.xml", - "ref/dotnet/it/System.Diagnostics.Debug.xml", - "ref/dotnet/ja/System.Diagnostics.Debug.xml", - "ref/dotnet/ko/System.Diagnostics.Debug.xml", - "ref/dotnet/ru/System.Diagnostics.Debug.xml", - "ref/dotnet/zh-hans/System.Diagnostics.Debug.xml", - "ref/dotnet/zh-hant/System.Diagnostics.Debug.xml", - "ref/net45/_._", - "ref/netcore50/System.Diagnostics.Debug.dll", - "ref/netcore50/System.Diagnostics.Debug.xml", - "ref/netcore50/de/System.Diagnostics.Debug.xml", - "ref/netcore50/es/System.Diagnostics.Debug.xml", - "ref/netcore50/fr/System.Diagnostics.Debug.xml", - "ref/netcore50/it/System.Diagnostics.Debug.xml", - "ref/netcore50/ja/System.Diagnostics.Debug.xml", - "ref/netcore50/ko/System.Diagnostics.Debug.xml", - "ref/netcore50/ru/System.Diagnostics.Debug.xml", - "ref/netcore50/zh-hans/System.Diagnostics.Debug.xml", - "ref/netcore50/zh-hant/System.Diagnostics.Debug.xml", - "ref/win8/_._", - "ref/wp80/_._", - "ref/wpa81/_._", - "ref/xamarinios10/_._", - "ref/xamarinmac20/_._" - ] - }, - "System.Globalization/4.0.0": { - "sha512": "IBJyTo1y7ZtzzoJUA60T1XPvNTyw/wfFmjFoBFtlYfkekIOtD/AzDDIg0YdUa7eNtFEfliED2R7HdppTdU4t5A==", - "type": "package", - "files": [ - "License.rtf", - "System.Globalization.4.0.0.nupkg.sha512", - "System.Globalization.nuspec", - "lib/MonoAndroid10/_._", - "lib/MonoTouch10/_._", - "lib/net45/_._", - "lib/win8/_._", - "lib/wp80/_._", - "lib/wpa81/_._", - "lib/xamarinios10/_._", - "lib/xamarinmac20/_._", - "ref/MonoAndroid10/_._", - "ref/MonoTouch10/_._", - "ref/dotnet/System.Globalization.dll", - "ref/dotnet/System.Globalization.xml", - "ref/dotnet/de/System.Globalization.xml", - "ref/dotnet/es/System.Globalization.xml", - "ref/dotnet/fr/System.Globalization.xml", - "ref/dotnet/it/System.Globalization.xml", - "ref/dotnet/ja/System.Globalization.xml", - "ref/dotnet/ko/System.Globalization.xml", - "ref/dotnet/ru/System.Globalization.xml", - "ref/dotnet/zh-hans/System.Globalization.xml", - "ref/dotnet/zh-hant/System.Globalization.xml", - "ref/net45/_._", - "ref/netcore50/System.Globalization.dll", - "ref/netcore50/System.Globalization.xml", - "ref/netcore50/de/System.Globalization.xml", - "ref/netcore50/es/System.Globalization.xml", - "ref/netcore50/fr/System.Globalization.xml", - "ref/netcore50/it/System.Globalization.xml", - "ref/netcore50/ja/System.Globalization.xml", - "ref/netcore50/ko/System.Globalization.xml", - "ref/netcore50/ru/System.Globalization.xml", - "ref/netcore50/zh-hans/System.Globalization.xml", - "ref/netcore50/zh-hant/System.Globalization.xml", - "ref/win8/_._", - "ref/wp80/_._", - "ref/wpa81/_._", - "ref/xamarinios10/_._", - "ref/xamarinmac20/_._" - ] - }, - "System.Linq/4.0.0": { - "sha512": "r6Hlc+ytE6m/9UBr+nNRRdoJEWjoeQiT3L3lXYFDHoXk3VYsRBCDNXrawcexw7KPLaH0zamQLiAb6avhZ50cGg==", - "type": "package", - "files": [ - "System.Linq.4.0.0.nupkg.sha512", - "System.Linq.nuspec", - "lib/dotnet/System.Linq.dll", - "lib/net45/_._", - "lib/netcore50/System.Linq.dll", - "lib/win8/_._", - "lib/wp80/_._", - "lib/wpa81/_._", - "ref/dotnet/System.Linq.dll", - "ref/dotnet/System.Linq.xml", - "ref/dotnet/de/System.Linq.xml", - "ref/dotnet/es/System.Linq.xml", - "ref/dotnet/fr/System.Linq.xml", - "ref/dotnet/it/System.Linq.xml", - "ref/dotnet/ja/System.Linq.xml", - "ref/dotnet/ko/System.Linq.xml", - "ref/dotnet/ru/System.Linq.xml", - "ref/dotnet/zh-hans/System.Linq.xml", - "ref/dotnet/zh-hant/System.Linq.xml", - "ref/net45/_._", - "ref/netcore50/System.Linq.dll", - "ref/netcore50/System.Linq.xml", - "ref/win8/_._", - "ref/wp80/_._", - "ref/wpa81/_._" - ] - }, - "System.Reflection.Metadata/1.2.0": { - "sha512": "ubQKFCNYPwhqPXPLjRKCvTDR2UvL5L5+Tm181D/5kl/df7264AuXDi2j2Bf5DxplBxevq8eUH9LRomcFCXTQKw==", - "type": "package", - "files": [ - "System.Reflection.Metadata.1.2.0.nupkg.sha512", - "System.Reflection.Metadata.nuspec", - "ThirdPartyNotices.txt", - "dotnet_library_license.txt", - "lib/netstandard1.1/System.Reflection.Metadata.dll", - "lib/netstandard1.1/System.Reflection.Metadata.xml", - "lib/portable-net45+win8/System.Reflection.Metadata.dll", - "lib/portable-net45+win8/System.Reflection.Metadata.xml" - ] - }, - "System.Resources.ResourceManager/4.0.0": { - "sha512": "qmqeZ4BJgjfU+G2JbrZt4Dk1LsMxO4t+f/9HarNY6w8pBgweO6jT+cknUH7c3qIrGvyUqraBhU45Eo6UtA0fAw==", - "type": "package", - "files": [ - "System.Resources.ResourceManager.4.0.0.nupkg.sha512", - "System.Resources.ResourceManager.nuspec", - "lib/DNXCore50/System.Resources.ResourceManager.dll", - "lib/net45/_._", - "lib/netcore50/System.Resources.ResourceManager.dll", - "lib/win8/_._", - "lib/wp80/_._", - "lib/wpa81/_._", - "ref/dotnet/System.Resources.ResourceManager.dll", - "ref/dotnet/System.Resources.ResourceManager.xml", - "ref/dotnet/de/System.Resources.ResourceManager.xml", - "ref/dotnet/es/System.Resources.ResourceManager.xml", - "ref/dotnet/fr/System.Resources.ResourceManager.xml", - "ref/dotnet/it/System.Resources.ResourceManager.xml", - "ref/dotnet/ja/System.Resources.ResourceManager.xml", - "ref/dotnet/ko/System.Resources.ResourceManager.xml", - "ref/dotnet/ru/System.Resources.ResourceManager.xml", - "ref/dotnet/zh-hans/System.Resources.ResourceManager.xml", - "ref/dotnet/zh-hant/System.Resources.ResourceManager.xml", - "ref/net45/_._", - "ref/netcore50/System.Resources.ResourceManager.dll", - "ref/netcore50/System.Resources.ResourceManager.xml", - "ref/win8/_._", - "ref/wp80/_._", - "ref/wpa81/_._", - "runtimes/win8-aot/lib/netcore50/System.Resources.ResourceManager.dll" - ] - }, - "System.Runtime/4.0.0": { - "sha512": "Uq9epame8hEqJlj4KaWb67dDJvj4IM37jRFGVeFbugRdPz48bR0voyBhrbf3iSa2tAmlkg4lsa6BUOL9iwlMew==", - "type": "package", - "files": [ - "License.rtf", - "System.Runtime.4.0.0.nupkg.sha512", - "System.Runtime.nuspec", - "lib/MonoAndroid10/_._", - "lib/MonoTouch10/_._", - "lib/net45/_._", - "lib/win8/_._", - "lib/wp80/_._", - "lib/wpa81/_._", - "lib/xamarinios10/_._", - "lib/xamarinmac20/_._", - "ref/MonoAndroid10/_._", - "ref/MonoTouch10/_._", - "ref/dotnet/System.Runtime.dll", - "ref/dotnet/System.Runtime.xml", - "ref/dotnet/de/System.Runtime.xml", - "ref/dotnet/es/System.Runtime.xml", - "ref/dotnet/fr/System.Runtime.xml", - "ref/dotnet/it/System.Runtime.xml", - "ref/dotnet/ja/System.Runtime.xml", - "ref/dotnet/ko/System.Runtime.xml", - "ref/dotnet/ru/System.Runtime.xml", - "ref/dotnet/zh-hans/System.Runtime.xml", - "ref/dotnet/zh-hant/System.Runtime.xml", - "ref/net45/_._", - "ref/netcore50/System.Runtime.dll", - "ref/netcore50/System.Runtime.xml", - "ref/netcore50/de/System.Runtime.xml", - "ref/netcore50/es/System.Runtime.xml", - "ref/netcore50/fr/System.Runtime.xml", - "ref/netcore50/it/System.Runtime.xml", - "ref/netcore50/ja/System.Runtime.xml", - "ref/netcore50/ko/System.Runtime.xml", - "ref/netcore50/ru/System.Runtime.xml", - "ref/netcore50/zh-hans/System.Runtime.xml", - "ref/netcore50/zh-hant/System.Runtime.xml", - "ref/win8/_._", - "ref/wp80/_._", - "ref/wpa81/_._", - "ref/xamarinios10/_._", - "ref/xamarinmac20/_._" - ] - }, - "System.Runtime.Extensions/4.0.0": { - "sha512": "zPzwoJcA7qar/b5Ihhzfcdr3vBOR8FIg7u//Qc5mqyAriasXuMFVraBZ5vOQq5asfun9ryNEL8Z2BOlUK5QRqA==", - "type": "package", - "files": [ - "License.rtf", - "System.Runtime.Extensions.4.0.0.nupkg.sha512", - "System.Runtime.Extensions.nuspec", - "lib/MonoAndroid10/_._", - "lib/MonoTouch10/_._", - "lib/net45/_._", - "lib/win8/_._", - "lib/wp80/_._", - "lib/wpa81/_._", - "lib/xamarinios10/_._", - "lib/xamarinmac20/_._", - "ref/MonoAndroid10/_._", - "ref/MonoTouch10/_._", - "ref/dotnet/System.Runtime.Extensions.dll", - "ref/dotnet/System.Runtime.Extensions.xml", - "ref/dotnet/de/System.Runtime.Extensions.xml", - "ref/dotnet/es/System.Runtime.Extensions.xml", - "ref/dotnet/fr/System.Runtime.Extensions.xml", - "ref/dotnet/it/System.Runtime.Extensions.xml", - "ref/dotnet/ja/System.Runtime.Extensions.xml", - "ref/dotnet/ko/System.Runtime.Extensions.xml", - "ref/dotnet/ru/System.Runtime.Extensions.xml", - "ref/dotnet/zh-hans/System.Runtime.Extensions.xml", - "ref/dotnet/zh-hant/System.Runtime.Extensions.xml", - "ref/net45/_._", - "ref/netcore50/System.Runtime.Extensions.dll", - "ref/netcore50/System.Runtime.Extensions.xml", - "ref/netcore50/de/System.Runtime.Extensions.xml", - "ref/netcore50/es/System.Runtime.Extensions.xml", - "ref/netcore50/fr/System.Runtime.Extensions.xml", - "ref/netcore50/it/System.Runtime.Extensions.xml", - "ref/netcore50/ja/System.Runtime.Extensions.xml", - "ref/netcore50/ko/System.Runtime.Extensions.xml", - "ref/netcore50/ru/System.Runtime.Extensions.xml", - "ref/netcore50/zh-hans/System.Runtime.Extensions.xml", - "ref/netcore50/zh-hant/System.Runtime.Extensions.xml", - "ref/win8/_._", - "ref/wp80/_._", - "ref/wpa81/_._", - "ref/xamarinios10/_._", - "ref/xamarinmac20/_._" - ] - }, - "System.Threading/4.0.0": { - "sha512": "H6O/9gUrjPDNYanh/7OFGAZHjVXvEuITD0RcnjfvIV04HOGrOPqUBU0kmz9RIX/7YGgCQn1o1S2DX6Cuv8kVGQ==", - "type": "package", - "files": [ - "License.rtf", - "System.Threading.4.0.0.nupkg.sha512", - "System.Threading.nuspec", - "lib/MonoAndroid10/_._", - "lib/MonoTouch10/_._", - "lib/net45/_._", - "lib/win8/_._", - "lib/wp80/_._", - "lib/wpa81/_._", - "lib/xamarinios10/_._", - "lib/xamarinmac20/_._", - "ref/MonoAndroid10/_._", - "ref/MonoTouch10/_._", - "ref/dotnet/System.Threading.dll", - "ref/dotnet/System.Threading.xml", - "ref/dotnet/de/System.Threading.xml", - "ref/dotnet/es/System.Threading.xml", - "ref/dotnet/fr/System.Threading.xml", - "ref/dotnet/it/System.Threading.xml", - "ref/dotnet/ja/System.Threading.xml", - "ref/dotnet/ko/System.Threading.xml", - "ref/dotnet/ru/System.Threading.xml", - "ref/dotnet/zh-hans/System.Threading.xml", - "ref/dotnet/zh-hant/System.Threading.xml", - "ref/net45/_._", - "ref/netcore50/System.Threading.dll", - "ref/netcore50/System.Threading.xml", - "ref/netcore50/de/System.Threading.xml", - "ref/netcore50/es/System.Threading.xml", - "ref/netcore50/fr/System.Threading.xml", - "ref/netcore50/it/System.Threading.xml", - "ref/netcore50/ja/System.Threading.xml", - "ref/netcore50/ko/System.Threading.xml", - "ref/netcore50/ru/System.Threading.xml", - "ref/netcore50/zh-hans/System.Threading.xml", - "ref/netcore50/zh-hant/System.Threading.xml", - "ref/win8/_._", - "ref/wp80/_._", - "ref/wpa81/_._", - "ref/xamarinios10/_._", - "ref/xamarinmac20/_._" - ] - }, - "xunit/2.1.0": { - "sha512": "u/7VQSOSXa7kSG4iK6Lcn7RqKZQ3hk7cnyMNVMpXHSP0RI5VQEtc44hvkG3LyWOVsx1dhUDD3rPAHAxyOUDQJw==", - "type": "package", - "files": [ - "xunit.2.1.0.nupkg.sha512", - "xunit.nuspec" - ] - }, - "xunit.abstractions/2.0.0": { - "sha512": "NAdxKQRzuLnCZ0g++x6i87/8rMBpQoRiRlRNLAqfODm2zJPbteHRoSER3DXfxnqrHXyBJT8rFaZ8uveBeQyaMA==", - "type": "package", - "files": [ - "lib/net35/xunit.abstractions.dll", - "lib/net35/xunit.abstractions.xml", - "lib/portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS/xunit.abstractions.dll", - "lib/portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS/xunit.abstractions.xml", - "xunit.abstractions.2.0.0.nupkg.sha512", - "xunit.abstractions.nuspec" - ] - }, - "xunit.assert/2.1.0": { - "sha512": "Hhhw+YaTe+BGhbr57dxVE+6VJk8BfThqFFii1XIsSZ4qx+SSCixprJC10JkiLRVSTfWyT8W/4nAf6NQgIrmBxA==", - "type": "package", - "files": [ - "lib/dotnet/xunit.assert.dll", - "lib/dotnet/xunit.assert.pdb", - "lib/dotnet/xunit.assert.xml", - "lib/portable-net45+win8+wp8+wpa81/xunit.assert.dll", - "lib/portable-net45+win8+wp8+wpa81/xunit.assert.pdb", - "lib/portable-net45+win8+wp8+wpa81/xunit.assert.xml", - "xunit.assert.2.1.0.nupkg.sha512", - "xunit.assert.nuspec" - ] - }, - "xunit.core/2.1.0": { - "sha512": "jlbYdPbnkPIRwJllcT/tQZCNsSElVDEymdpJfH79uTUrPARkELVYw9o/zhAjKZXmeikGqGK5C2Yny4gTNoEu0Q==", - "type": "package", - "files": [ - "build/_desktop/xunit.execution.desktop.dll", - "build/dnx451/_._", - "build/monoandroid/_._", - "build/monotouch/_._", - "build/net45/_._", - "build/portable-net45+win8+wp8+wpa81/xunit.core.props", - "build/win8/_._", - "build/win81/xunit.core.props", - "build/wp8/_._", - "build/wpa81/xunit.core.props", - "build/xamarinios/_._", - "xunit.core.2.1.0.nupkg.sha512", - "xunit.core.nuspec" - ] - }, - "xunit.extensibility.core/2.1.0": { - "sha512": "ANWM3WxeaeHjACLRlmrv+xOc0WAcr3cvIiJE+gqbdzTv1NCH4p1VDyT+8WmmdCc9db0WFiJLaDy4YTYsL1wWXw==", - "type": "package", - "files": [ - "lib/dotnet/xunit.core.dll", - "lib/dotnet/xunit.core.dll.tdnet", - "lib/dotnet/xunit.core.pdb", - "lib/dotnet/xunit.core.xml", - "lib/dotnet/xunit.runner.tdnet.dll", - "lib/dotnet/xunit.runner.utility.desktop.dll", - "lib/portable-net45+win8+wp8+wpa81/xunit.core.dll", - "lib/portable-net45+win8+wp8+wpa81/xunit.core.dll.tdnet", - "lib/portable-net45+win8+wp8+wpa81/xunit.core.pdb", - "lib/portable-net45+win8+wp8+wpa81/xunit.core.xml", - "lib/portable-net45+win8+wp8+wpa81/xunit.runner.tdnet.dll", - "lib/portable-net45+win8+wp8+wpa81/xunit.runner.utility.desktop.dll", - "xunit.extensibility.core.2.1.0.nupkg.sha512", - "xunit.extensibility.core.nuspec" - ] - }, - "xunit.extensibility.execution/2.1.0": { - "sha512": "tAoNafoVknKa3sZJPMvtZRnhOSk3gasEGeceSm7w/gyGwsR/OXFxndWJB1xSHeoy33d3Z6jFqn4A3j+pWCF0Ew==", - "type": "package", - "files": [ - "lib/dnx451/xunit.execution.dotnet.dll", - "lib/dnx451/xunit.execution.dotnet.pdb", - "lib/dnx451/xunit.execution.dotnet.xml", - "lib/dotnet/xunit.execution.dotnet.dll", - "lib/dotnet/xunit.execution.dotnet.pdb", - "lib/dotnet/xunit.execution.dotnet.xml", - "lib/monoandroid/xunit.execution.dotnet.dll", - "lib/monoandroid/xunit.execution.dotnet.pdb", - "lib/monoandroid/xunit.execution.dotnet.xml", - "lib/monotouch/xunit.execution.dotnet.dll", - "lib/monotouch/xunit.execution.dotnet.pdb", - "lib/monotouch/xunit.execution.dotnet.xml", - "lib/net45/xunit.execution.desktop.dll", - "lib/net45/xunit.execution.desktop.pdb", - "lib/net45/xunit.execution.desktop.xml", - "lib/portable-net45+win8+wp8+wpa81/xunit.execution.dotnet.dll", - "lib/portable-net45+win8+wp8+wpa81/xunit.execution.dotnet.pdb", - "lib/portable-net45+win8+wp8+wpa81/xunit.execution.dotnet.xml", - "lib/win8/xunit.execution.dotnet.dll", - "lib/win8/xunit.execution.dotnet.pdb", - "lib/win8/xunit.execution.dotnet.xml", - "lib/wp8/xunit.execution.dotnet.dll", - "lib/wp8/xunit.execution.dotnet.pdb", - "lib/wp8/xunit.execution.dotnet.xml", - "lib/wpa81/xunit.execution.dotnet.dll", - "lib/wpa81/xunit.execution.dotnet.pdb", - "lib/wpa81/xunit.execution.dotnet.xml", - "lib/xamarinios/xunit.execution.dotnet.dll", - "lib/xamarinios/xunit.execution.dotnet.pdb", - "lib/xamarinios/xunit.execution.dotnet.xml", - "xunit.extensibility.execution.2.1.0.nupkg.sha512", - "xunit.extensibility.execution.nuspec" - ] - }, - "xunit.runner.console/2.1.0": { - "sha512": "dryP6nBDWD2aH6OUidwoPQLebelnfA6DDKUs9PxJ6iPDbT9t5eSKyjqLSas/nQFEJZwXXWVnbImrgK/IgiyOdQ==", - "type": "package", - "files": [ - "tools/HTML.xslt", - "tools/NUnitXml.xslt", - "tools/xUnit1.xslt", - "tools/xunit.abstractions.dll", - "tools/xunit.console.exe", - "tools/xunit.console.exe.config", - "tools/xunit.console.x86.exe", - "tools/xunit.console.x86.exe.config", - "tools/xunit.runner.reporters.desktop.dll", - "tools/xunit.runner.utility.desktop.dll", - "xunit.runner.console.2.1.0.nupkg.sha512", - "xunit.runner.console.nuspec" - ] - } - }, - "projectFileDependencyGroups": { - "": [ - "Moq >= 4.2.1402.2112", - "xunit >= 2.1.0", - "xunit.runner.console >= 2.1.0" - ], - ".NETFramework,Version=v4.5": [] - } -} \ No newline at end of file diff --git a/src/Compilers/Core/MSBuildTask/Desktop/project.lock.json b/src/Compilers/Core/MSBuildTask/Desktop/project.lock.json deleted file mode 100644 index 82dd73f69bc79..0000000000000 --- a/src/Compilers/Core/MSBuildTask/Desktop/project.lock.json +++ /dev/null @@ -1,668 +0,0 @@ -{ - "locked": false, - "version": 1, - "targets": { - ".NETFramework,Version=v4.5": { - "System.Collections/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "System.Collections.Immutable/1.1.37": { - "dependencies": { - "System.Collections": "4.0.0", - "System.Diagnostics.Debug": "4.0.0", - "System.Globalization": "4.0.0", - "System.Linq": "4.0.0", - "System.Resources.ResourceManager": "4.0.0", - "System.Runtime": "4.0.0", - "System.Runtime.Extensions": "4.0.0", - "System.Threading": "4.0.0" - }, - "compile": { - "lib/dotnet/System.Collections.Immutable.dll": {} - }, - "runtime": { - "lib/dotnet/System.Collections.Immutable.dll": {} - } - }, - "System.Diagnostics.Debug/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "System.Globalization/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "System.Linq/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "System.Reflection.Metadata/1.2.0": { - "dependencies": { - "System.Collections.Immutable": "1.1.37" - }, - "compile": { - "lib/portable-net45+win8/System.Reflection.Metadata.dll": {} - }, - "runtime": { - "lib/portable-net45+win8/System.Reflection.Metadata.dll": {} - } - }, - "System.Resources.ResourceManager/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "System.Runtime/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "System.Runtime.Extensions/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "System.Threading/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - } - }, - ".NETFramework,Version=v4.5/win7": { - "System.Collections/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "System.Collections.Immutable/1.1.37": { - "dependencies": { - "System.Collections": "4.0.0", - "System.Diagnostics.Debug": "4.0.0", - "System.Globalization": "4.0.0", - "System.Linq": "4.0.0", - "System.Resources.ResourceManager": "4.0.0", - "System.Runtime": "4.0.0", - "System.Runtime.Extensions": "4.0.0", - "System.Threading": "4.0.0" - }, - "compile": { - "lib/dotnet/System.Collections.Immutable.dll": {} - }, - "runtime": { - "lib/dotnet/System.Collections.Immutable.dll": {} - } - }, - "System.Diagnostics.Debug/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "System.Globalization/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "System.Linq/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "System.Reflection.Metadata/1.2.0": { - "dependencies": { - "System.Collections.Immutable": "1.1.37" - }, - "compile": { - "lib/portable-net45+win8/System.Reflection.Metadata.dll": {} - }, - "runtime": { - "lib/portable-net45+win8/System.Reflection.Metadata.dll": {} - } - }, - "System.Resources.ResourceManager/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "System.Runtime/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "System.Runtime.Extensions/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "System.Threading/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - } - }, - ".NETFramework,Version=v4.5/win7-anycpu": { - "System.Collections/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "System.Collections.Immutable/1.1.37": { - "dependencies": { - "System.Collections": "4.0.0", - "System.Diagnostics.Debug": "4.0.0", - "System.Globalization": "4.0.0", - "System.Linq": "4.0.0", - "System.Resources.ResourceManager": "4.0.0", - "System.Runtime": "4.0.0", - "System.Runtime.Extensions": "4.0.0", - "System.Threading": "4.0.0" - }, - "compile": { - "lib/dotnet/System.Collections.Immutable.dll": {} - }, - "runtime": { - "lib/dotnet/System.Collections.Immutable.dll": {} - } - }, - "System.Diagnostics.Debug/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "System.Globalization/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "System.Linq/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "System.Reflection.Metadata/1.2.0": { - "dependencies": { - "System.Collections.Immutable": "1.1.37" - }, - "compile": { - "lib/portable-net45+win8/System.Reflection.Metadata.dll": {} - }, - "runtime": { - "lib/portable-net45+win8/System.Reflection.Metadata.dll": {} - } - }, - "System.Resources.ResourceManager/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "System.Runtime/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "System.Runtime.Extensions/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - }, - "System.Threading/4.0.0": { - "compile": { - "ref/net45/_._": {} - }, - "runtime": { - "lib/net45/_._": {} - } - } - } - }, - "libraries": { - "System.Collections/4.0.0": { - "sha512": "i2vsGDIEbWdHcUSNDPKZP/ZWod6o740el7mGTCy0dqbCxQh74W4QoC+klUwPEtGEFuvzJ7bJgvwJqscosVNyZQ==", - "type": "package", - "files": [ - "License.rtf", - "System.Collections.4.0.0.nupkg.sha512", - "System.Collections.nuspec", - "lib/MonoAndroid10/_._", - "lib/MonoTouch10/_._", - "lib/net45/_._", - "lib/win8/_._", - "lib/wp80/_._", - "lib/wpa81/_._", - "lib/xamarinios10/_._", - "lib/xamarinmac20/_._", - "ref/MonoAndroid10/_._", - "ref/MonoTouch10/_._", - "ref/dotnet/System.Collections.dll", - "ref/dotnet/System.Collections.xml", - "ref/dotnet/de/System.Collections.xml", - "ref/dotnet/es/System.Collections.xml", - "ref/dotnet/fr/System.Collections.xml", - "ref/dotnet/it/System.Collections.xml", - "ref/dotnet/ja/System.Collections.xml", - "ref/dotnet/ko/System.Collections.xml", - "ref/dotnet/ru/System.Collections.xml", - "ref/dotnet/zh-hans/System.Collections.xml", - "ref/dotnet/zh-hant/System.Collections.xml", - "ref/net45/_._", - "ref/netcore50/System.Collections.dll", - "ref/netcore50/System.Collections.xml", - "ref/netcore50/de/System.Collections.xml", - "ref/netcore50/es/System.Collections.xml", - "ref/netcore50/fr/System.Collections.xml", - "ref/netcore50/it/System.Collections.xml", - "ref/netcore50/ja/System.Collections.xml", - "ref/netcore50/ko/System.Collections.xml", - "ref/netcore50/ru/System.Collections.xml", - "ref/netcore50/zh-hans/System.Collections.xml", - "ref/netcore50/zh-hant/System.Collections.xml", - "ref/win8/_._", - "ref/wp80/_._", - "ref/wpa81/_._", - "ref/xamarinios10/_._", - "ref/xamarinmac20/_._" - ] - }, - "System.Collections.Immutable/1.1.37": { - "sha512": "fTpqwZYBzoklTT+XjTRK8KxvmrGkYHzBiylCcKyQcxiOM8k+QvhNBxRvFHDWzy4OEP5f8/9n+xQ9mEgEXY+muA==", - "type": "package", - "files": [ - "System.Collections.Immutable.1.1.37.nupkg.sha512", - "System.Collections.Immutable.nuspec", - "lib/dotnet/System.Collections.Immutable.dll", - "lib/dotnet/System.Collections.Immutable.xml", - "lib/portable-net45+win8+wp8+wpa81/System.Collections.Immutable.dll", - "lib/portable-net45+win8+wp8+wpa81/System.Collections.Immutable.xml" - ] - }, - "System.Diagnostics.Debug/4.0.0": { - "sha512": "AYJsLLGDVTC/nyURjgAo7Lpye0+HuSkcQujUf+NgQVdC/C/ky5NyamQHCforHJzgqspitMMtBe8B4UBdGXy1zQ==", - "type": "package", - "files": [ - "License.rtf", - "System.Diagnostics.Debug.4.0.0.nupkg.sha512", - "System.Diagnostics.Debug.nuspec", - "lib/MonoAndroid10/_._", - "lib/MonoTouch10/_._", - "lib/net45/_._", - "lib/win8/_._", - "lib/wp80/_._", - "lib/wpa81/_._", - "lib/xamarinios10/_._", - "lib/xamarinmac20/_._", - "ref/MonoAndroid10/_._", - "ref/MonoTouch10/_._", - "ref/dotnet/System.Diagnostics.Debug.dll", - "ref/dotnet/System.Diagnostics.Debug.xml", - "ref/dotnet/de/System.Diagnostics.Debug.xml", - "ref/dotnet/es/System.Diagnostics.Debug.xml", - "ref/dotnet/fr/System.Diagnostics.Debug.xml", - "ref/dotnet/it/System.Diagnostics.Debug.xml", - "ref/dotnet/ja/System.Diagnostics.Debug.xml", - "ref/dotnet/ko/System.Diagnostics.Debug.xml", - "ref/dotnet/ru/System.Diagnostics.Debug.xml", - "ref/dotnet/zh-hans/System.Diagnostics.Debug.xml", - "ref/dotnet/zh-hant/System.Diagnostics.Debug.xml", - "ref/net45/_._", - "ref/netcore50/System.Diagnostics.Debug.dll", - "ref/netcore50/System.Diagnostics.Debug.xml", - "ref/netcore50/de/System.Diagnostics.Debug.xml", - "ref/netcore50/es/System.Diagnostics.Debug.xml", - "ref/netcore50/fr/System.Diagnostics.Debug.xml", - "ref/netcore50/it/System.Diagnostics.Debug.xml", - "ref/netcore50/ja/System.Diagnostics.Debug.xml", - "ref/netcore50/ko/System.Diagnostics.Debug.xml", - "ref/netcore50/ru/System.Diagnostics.Debug.xml", - "ref/netcore50/zh-hans/System.Diagnostics.Debug.xml", - "ref/netcore50/zh-hant/System.Diagnostics.Debug.xml", - "ref/win8/_._", - "ref/wp80/_._", - "ref/wpa81/_._", - "ref/xamarinios10/_._", - "ref/xamarinmac20/_._" - ] - }, - "System.Globalization/4.0.0": { - "sha512": "IBJyTo1y7ZtzzoJUA60T1XPvNTyw/wfFmjFoBFtlYfkekIOtD/AzDDIg0YdUa7eNtFEfliED2R7HdppTdU4t5A==", - "type": "package", - "files": [ - "License.rtf", - "System.Globalization.4.0.0.nupkg.sha512", - "System.Globalization.nuspec", - "lib/MonoAndroid10/_._", - "lib/MonoTouch10/_._", - "lib/net45/_._", - "lib/win8/_._", - "lib/wp80/_._", - "lib/wpa81/_._", - "lib/xamarinios10/_._", - "lib/xamarinmac20/_._", - "ref/MonoAndroid10/_._", - "ref/MonoTouch10/_._", - "ref/dotnet/System.Globalization.dll", - "ref/dotnet/System.Globalization.xml", - "ref/dotnet/de/System.Globalization.xml", - "ref/dotnet/es/System.Globalization.xml", - "ref/dotnet/fr/System.Globalization.xml", - "ref/dotnet/it/System.Globalization.xml", - "ref/dotnet/ja/System.Globalization.xml", - "ref/dotnet/ko/System.Globalization.xml", - "ref/dotnet/ru/System.Globalization.xml", - "ref/dotnet/zh-hans/System.Globalization.xml", - "ref/dotnet/zh-hant/System.Globalization.xml", - "ref/net45/_._", - "ref/netcore50/System.Globalization.dll", - "ref/netcore50/System.Globalization.xml", - "ref/netcore50/de/System.Globalization.xml", - "ref/netcore50/es/System.Globalization.xml", - "ref/netcore50/fr/System.Globalization.xml", - "ref/netcore50/it/System.Globalization.xml", - "ref/netcore50/ja/System.Globalization.xml", - "ref/netcore50/ko/System.Globalization.xml", - "ref/netcore50/ru/System.Globalization.xml", - "ref/netcore50/zh-hans/System.Globalization.xml", - "ref/netcore50/zh-hant/System.Globalization.xml", - "ref/win8/_._", - "ref/wp80/_._", - "ref/wpa81/_._", - "ref/xamarinios10/_._", - "ref/xamarinmac20/_._" - ] - }, - "System.Linq/4.0.0": { - "sha512": "r6Hlc+ytE6m/9UBr+nNRRdoJEWjoeQiT3L3lXYFDHoXk3VYsRBCDNXrawcexw7KPLaH0zamQLiAb6avhZ50cGg==", - "type": "package", - "files": [ - "System.Linq.4.0.0.nupkg.sha512", - "System.Linq.nuspec", - "lib/dotnet/System.Linq.dll", - "lib/net45/_._", - "lib/netcore50/System.Linq.dll", - "lib/win8/_._", - "lib/wp80/_._", - "lib/wpa81/_._", - "ref/dotnet/System.Linq.dll", - "ref/dotnet/System.Linq.xml", - "ref/dotnet/de/System.Linq.xml", - "ref/dotnet/es/System.Linq.xml", - "ref/dotnet/fr/System.Linq.xml", - "ref/dotnet/it/System.Linq.xml", - "ref/dotnet/ja/System.Linq.xml", - "ref/dotnet/ko/System.Linq.xml", - "ref/dotnet/ru/System.Linq.xml", - "ref/dotnet/zh-hans/System.Linq.xml", - "ref/dotnet/zh-hant/System.Linq.xml", - "ref/net45/_._", - "ref/netcore50/System.Linq.dll", - "ref/netcore50/System.Linq.xml", - "ref/win8/_._", - "ref/wp80/_._", - "ref/wpa81/_._" - ] - }, - "System.Reflection.Metadata/1.2.0": { - "sha512": "ubQKFCNYPwhqPXPLjRKCvTDR2UvL5L5+Tm181D/5kl/df7264AuXDi2j2Bf5DxplBxevq8eUH9LRomcFCXTQKw==", - "type": "package", - "files": [ - "System.Reflection.Metadata.1.2.0.nupkg.sha512", - "System.Reflection.Metadata.nuspec", - "ThirdPartyNotices.txt", - "dotnet_library_license.txt", - "lib/netstandard1.1/System.Reflection.Metadata.dll", - "lib/netstandard1.1/System.Reflection.Metadata.xml", - "lib/portable-net45+win8/System.Reflection.Metadata.dll", - "lib/portable-net45+win8/System.Reflection.Metadata.xml" - ] - }, - "System.Resources.ResourceManager/4.0.0": { - "sha512": "qmqeZ4BJgjfU+G2JbrZt4Dk1LsMxO4t+f/9HarNY6w8pBgweO6jT+cknUH7c3qIrGvyUqraBhU45Eo6UtA0fAw==", - "type": "package", - "files": [ - "System.Resources.ResourceManager.4.0.0.nupkg.sha512", - "System.Resources.ResourceManager.nuspec", - "lib/DNXCore50/System.Resources.ResourceManager.dll", - "lib/net45/_._", - "lib/netcore50/System.Resources.ResourceManager.dll", - "lib/win8/_._", - "lib/wp80/_._", - "lib/wpa81/_._", - "ref/dotnet/System.Resources.ResourceManager.dll", - "ref/dotnet/System.Resources.ResourceManager.xml", - "ref/dotnet/de/System.Resources.ResourceManager.xml", - "ref/dotnet/es/System.Resources.ResourceManager.xml", - "ref/dotnet/fr/System.Resources.ResourceManager.xml", - "ref/dotnet/it/System.Resources.ResourceManager.xml", - "ref/dotnet/ja/System.Resources.ResourceManager.xml", - "ref/dotnet/ko/System.Resources.ResourceManager.xml", - "ref/dotnet/ru/System.Resources.ResourceManager.xml", - "ref/dotnet/zh-hans/System.Resources.ResourceManager.xml", - "ref/dotnet/zh-hant/System.Resources.ResourceManager.xml", - "ref/net45/_._", - "ref/netcore50/System.Resources.ResourceManager.dll", - "ref/netcore50/System.Resources.ResourceManager.xml", - "ref/win8/_._", - "ref/wp80/_._", - "ref/wpa81/_._", - "runtimes/win8-aot/lib/netcore50/System.Resources.ResourceManager.dll" - ] - }, - "System.Runtime/4.0.0": { - "sha512": "Uq9epame8hEqJlj4KaWb67dDJvj4IM37jRFGVeFbugRdPz48bR0voyBhrbf3iSa2tAmlkg4lsa6BUOL9iwlMew==", - "type": "package", - "files": [ - "License.rtf", - "System.Runtime.4.0.0.nupkg.sha512", - "System.Runtime.nuspec", - "lib/MonoAndroid10/_._", - "lib/MonoTouch10/_._", - "lib/net45/_._", - "lib/win8/_._", - "lib/wp80/_._", - "lib/wpa81/_._", - "lib/xamarinios10/_._", - "lib/xamarinmac20/_._", - "ref/MonoAndroid10/_._", - "ref/MonoTouch10/_._", - "ref/dotnet/System.Runtime.dll", - "ref/dotnet/System.Runtime.xml", - "ref/dotnet/de/System.Runtime.xml", - "ref/dotnet/es/System.Runtime.xml", - "ref/dotnet/fr/System.Runtime.xml", - "ref/dotnet/it/System.Runtime.xml", - "ref/dotnet/ja/System.Runtime.xml", - "ref/dotnet/ko/System.Runtime.xml", - "ref/dotnet/ru/System.Runtime.xml", - "ref/dotnet/zh-hans/System.Runtime.xml", - "ref/dotnet/zh-hant/System.Runtime.xml", - "ref/net45/_._", - "ref/netcore50/System.Runtime.dll", - "ref/netcore50/System.Runtime.xml", - "ref/netcore50/de/System.Runtime.xml", - "ref/netcore50/es/System.Runtime.xml", - "ref/netcore50/fr/System.Runtime.xml", - "ref/netcore50/it/System.Runtime.xml", - "ref/netcore50/ja/System.Runtime.xml", - "ref/netcore50/ko/System.Runtime.xml", - "ref/netcore50/ru/System.Runtime.xml", - "ref/netcore50/zh-hans/System.Runtime.xml", - "ref/netcore50/zh-hant/System.Runtime.xml", - "ref/win8/_._", - "ref/wp80/_._", - "ref/wpa81/_._", - "ref/xamarinios10/_._", - "ref/xamarinmac20/_._" - ] - }, - "System.Runtime.Extensions/4.0.0": { - "sha512": "zPzwoJcA7qar/b5Ihhzfcdr3vBOR8FIg7u//Qc5mqyAriasXuMFVraBZ5vOQq5asfun9ryNEL8Z2BOlUK5QRqA==", - "type": "package", - "files": [ - "License.rtf", - "System.Runtime.Extensions.4.0.0.nupkg.sha512", - "System.Runtime.Extensions.nuspec", - "lib/MonoAndroid10/_._", - "lib/MonoTouch10/_._", - "lib/net45/_._", - "lib/win8/_._", - "lib/wp80/_._", - "lib/wpa81/_._", - "lib/xamarinios10/_._", - "lib/xamarinmac20/_._", - "ref/MonoAndroid10/_._", - "ref/MonoTouch10/_._", - "ref/dotnet/System.Runtime.Extensions.dll", - "ref/dotnet/System.Runtime.Extensions.xml", - "ref/dotnet/de/System.Runtime.Extensions.xml", - "ref/dotnet/es/System.Runtime.Extensions.xml", - "ref/dotnet/fr/System.Runtime.Extensions.xml", - "ref/dotnet/it/System.Runtime.Extensions.xml", - "ref/dotnet/ja/System.Runtime.Extensions.xml", - "ref/dotnet/ko/System.Runtime.Extensions.xml", - "ref/dotnet/ru/System.Runtime.Extensions.xml", - "ref/dotnet/zh-hans/System.Runtime.Extensions.xml", - "ref/dotnet/zh-hant/System.Runtime.Extensions.xml", - "ref/net45/_._", - "ref/netcore50/System.Runtime.Extensions.dll", - "ref/netcore50/System.Runtime.Extensions.xml", - "ref/netcore50/de/System.Runtime.Extensions.xml", - "ref/netcore50/es/System.Runtime.Extensions.xml", - "ref/netcore50/fr/System.Runtime.Extensions.xml", - "ref/netcore50/it/System.Runtime.Extensions.xml", - "ref/netcore50/ja/System.Runtime.Extensions.xml", - "ref/netcore50/ko/System.Runtime.Extensions.xml", - "ref/netcore50/ru/System.Runtime.Extensions.xml", - "ref/netcore50/zh-hans/System.Runtime.Extensions.xml", - "ref/netcore50/zh-hant/System.Runtime.Extensions.xml", - "ref/win8/_._", - "ref/wp80/_._", - "ref/wpa81/_._", - "ref/xamarinios10/_._", - "ref/xamarinmac20/_._" - ] - }, - "System.Threading/4.0.0": { - "sha512": "H6O/9gUrjPDNYanh/7OFGAZHjVXvEuITD0RcnjfvIV04HOGrOPqUBU0kmz9RIX/7YGgCQn1o1S2DX6Cuv8kVGQ==", - "type": "package", - "files": [ - "License.rtf", - "System.Threading.4.0.0.nupkg.sha512", - "System.Threading.nuspec", - "lib/MonoAndroid10/_._", - "lib/MonoTouch10/_._", - "lib/net45/_._", - "lib/win8/_._", - "lib/wp80/_._", - "lib/wpa81/_._", - "lib/xamarinios10/_._", - "lib/xamarinmac20/_._", - "ref/MonoAndroid10/_._", - "ref/MonoTouch10/_._", - "ref/dotnet/System.Threading.dll", - "ref/dotnet/System.Threading.xml", - "ref/dotnet/de/System.Threading.xml", - "ref/dotnet/es/System.Threading.xml", - "ref/dotnet/fr/System.Threading.xml", - "ref/dotnet/it/System.Threading.xml", - "ref/dotnet/ja/System.Threading.xml", - "ref/dotnet/ko/System.Threading.xml", - "ref/dotnet/ru/System.Threading.xml", - "ref/dotnet/zh-hans/System.Threading.xml", - "ref/dotnet/zh-hant/System.Threading.xml", - "ref/net45/_._", - "ref/netcore50/System.Threading.dll", - "ref/netcore50/System.Threading.xml", - "ref/netcore50/de/System.Threading.xml", - "ref/netcore50/es/System.Threading.xml", - "ref/netcore50/fr/System.Threading.xml", - "ref/netcore50/it/System.Threading.xml", - "ref/netcore50/ja/System.Threading.xml", - "ref/netcore50/ko/System.Threading.xml", - "ref/netcore50/ru/System.Threading.xml", - "ref/netcore50/zh-hans/System.Threading.xml", - "ref/netcore50/zh-hant/System.Threading.xml", - "ref/win8/_._", - "ref/wp80/_._", - "ref/wpa81/_._", - "ref/xamarinios10/_._", - "ref/xamarinmac20/_._" - ] - } - }, - "projectFileDependencyGroups": { - "": [], - ".NETFramework,Version=v4.5": [] - } -} \ No newline at end of file From 96a13f43740cac527ce561cad5432e0e170ce70b Mon Sep 17 00:00:00 2001 From: Heejae Chang Date: Tue, 12 Apr 2016 13:40:46 -0700 Subject: [PATCH 13/24] PR feedback changed test to use GetDiagnosticsAsync rather than GetProjectDiagnosticsAsync --- .../Diagnostics/DiagnosticServiceTests.vb | 52 ++++++++----------- 1 file changed, 21 insertions(+), 31 deletions(-) diff --git a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb index 05dc8a9be2512..4430d7c6a12bc 100644 --- a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb @@ -730,37 +730,27 @@ class AnonymousFunctions Assert.Equal(1, diagnostics.Count()) Assert.Equal(document.Id, diagnostics.First().DocumentId) - If options.GetOption(InternalDiagnosticsOptions.UseDiagnosticEngineV2) Then - ' Verify compilation diagnostics are reported with correct location info when asked for project diagnostics. - Dim projectDiagnostics = diagnosticService.GetProjectDiagnosticsForIdsAsync(project.Solution, project.Id).WaitAndGetResult(CancellationToken.None) - Assert.Equal(1, projectDiagnostics.Count()) - - Dim noLocationDiagnostic = projectDiagnostics.First() - Assert.Equal(CompilationEndedAnalyzer.Descriptor.Id, noLocationDiagnostic.Id) - Assert.Equal(False, noLocationDiagnostic.HasTextSpan) - Else - ' REVIEW: GetProjectDiagnosticsForIdsAsync is for project diagnostics with no location. not sure why in v1, this API returns - ' diagnostic with location? - - ' Verify compilation diagnostics are reported with correct location info when asked for project diagnostics. - Dim projectDiagnostics = diagnosticService.GetProjectDiagnosticsForIdsAsync(project.Solution, project.Id).WaitAndGetResult(CancellationToken.None) - Assert.Equal(2, projectDiagnostics.Count()) - - Dim noLocationDiagnostic = projectDiagnostics.First() - Assert.Equal(CompilationEndedAnalyzer.Descriptor.Id, noLocationDiagnostic.Id) - Assert.Equal(False, noLocationDiagnostic.HasTextSpan) - - Dim withDocumentLocationDiagnostic = projectDiagnostics.Last() - Assert.Equal(CompilationEndedAnalyzer.Descriptor.Id, withDocumentLocationDiagnostic.Id) - Assert.Equal(True, withDocumentLocationDiagnostic.HasTextSpan) - Assert.NotNull(withDocumentLocationDiagnostic.DocumentId) - - Dim diagnosticDocument = project.GetDocument(withDocumentLocationDiagnostic.DocumentId) - Dim tree = diagnosticDocument.GetSyntaxTreeAsync().Result - Dim actualLocation = withDocumentLocationDiagnostic.ToDiagnosticAsync(project, CancellationToken.None).Result.Location - Dim expectedLocation = document.GetSyntaxRootAsync().Result.GetLocation - Assert.Equal(expectedLocation, actualLocation) - End If + ' REVIEW: GetProjectDiagnosticsForIdsAsync is for project diagnostics with no location. not sure why in v1, this API returns + ' diagnostic with location? + + ' Verify compilation diagnostics are reported with correct location info when asked for project diagnostics. + Dim projectDiagnostics = diagnosticService.GetDiagnosticsForIdsAsync(project.Solution, project.Id).WaitAndGetResult(CancellationToken.None) + Assert.Equal(2, projectDiagnostics.Count()) + + Dim noLocationDiagnostic = projectDiagnostics.First(Function(d) Not d.HasTextSpan) + Assert.Equal(CompilationEndedAnalyzer.Descriptor.Id, noLocationDiagnostic.Id) + Assert.Equal(False, noLocationDiagnostic.HasTextSpan) + + Dim withDocumentLocationDiagnostic = projectDiagnostics.First(Function(d) d.HasTextSpan) + Assert.Equal(CompilationEndedAnalyzer.Descriptor.Id, withDocumentLocationDiagnostic.Id) + Assert.Equal(True, withDocumentLocationDiagnostic.HasTextSpan) + Assert.NotNull(withDocumentLocationDiagnostic.DocumentId) + + Dim diagnosticDocument = project.GetDocument(withDocumentLocationDiagnostic.DocumentId) + Dim tree = diagnosticDocument.GetSyntaxTreeAsync().Result + Dim actualLocation = withDocumentLocationDiagnostic.ToDiagnosticAsync(project, CancellationToken.None).Result.Location + Dim expectedLocation = document.GetSyntaxRootAsync().Result.GetLocation + Assert.Equal(expectedLocation, actualLocation) End Using End Sub From 96ace4c42309f397c5f279497e3525ada15853f3 Mon Sep 17 00:00:00 2001 From: Heejae Chang Date: Tue, 12 Apr 2016 14:23:12 -0700 Subject: [PATCH 14/24] GetEffectiveDiagnostics is not needed for CompilationWithAnalyzer API --- .../Diagnostics/EngineV2/CompilerDiagnosticExecutor.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/CompilerDiagnosticExecutor.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/CompilerDiagnosticExecutor.cs index 50a0e63fdc0ec..f37ecc4f9657f 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/CompilerDiagnosticExecutor.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/CompilerDiagnosticExecutor.cs @@ -43,14 +43,14 @@ public static async Task var model = compilation.GetSemanticModel(tree); var syntax = await analyzerDriver.GetAnalyzerSyntaxDiagnosticsAsync(tree, oneAnalyzers, cancellationToken).ConfigureAwait(false); - result.AddSyntaxDiagnostics(tree, CompilationWithAnalyzers.GetEffectiveDiagnostics(syntax, compilation)); + result.AddSyntaxDiagnostics(tree, syntax); var semantic = await analyzerDriver.GetAnalyzerSemanticDiagnosticsAsync(model, noSpanFilter, oneAnalyzers, cancellationToken).ConfigureAwait(false); - result.AddSemanticDiagnostics(tree, CompilationWithAnalyzers.GetEffectiveDiagnostics(semantic, compilation)); + result.AddSemanticDiagnostics(tree, semantic); } var rest = await analyzerDriver.GetAnalyzerCompilationDiagnosticsAsync(oneAnalyzers, cancellationToken).ConfigureAwait(false); - result.AddCompilationDiagnostics(CompilationWithAnalyzers.GetEffectiveDiagnostics(rest, compilation)); + result.AddCompilationDiagnostics(rest); builder.Add(analyzer, result.ToResult()); } From c18332fc26dc8df99129a1a45432d92645f0bd2d Mon Sep 17 00:00:00 2001 From: Heejae Chang Date: Tue, 12 Apr 2016 14:26:01 -0700 Subject: [PATCH 15/24] added comment for AddExternalDiagnostics --- .../Portable/Diagnostics/EngineV2/CompilerDiagnosticExecutor.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/CompilerDiagnosticExecutor.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/CompilerDiagnosticExecutor.cs index f37ecc4f9657f..3431b8656e6b1 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/CompilerDiagnosticExecutor.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/CompilerDiagnosticExecutor.cs @@ -105,11 +105,13 @@ private ImmutableDictionary> Convert( public void AddExternalSyntaxDiagnostics(DocumentId documentId, IEnumerable diagnostics) { + // this is for diagnostic producer that doesnt use compiler based DiagnosticAnalyzer such as TypeScript. AddExternalDiagnostics(ref _lazySyntaxLocals, documentId, diagnostics); } public void AddExternalSemanticDiagnostics(DocumentId documentId, IEnumerable diagnostics) { + // this is for diagnostic producer that doesnt use compiler based DiagnosticAnalyzer such as TypeScript. AddExternalDiagnostics(ref _lazySemanticLocals, documentId, diagnostics); } From 2a436df5f3743ce6f6cfa7cd9d7fa030916682f8 Mon Sep 17 00:00:00 2001 From: Heejae Chang Date: Tue, 12 Apr 2016 14:47:51 -0700 Subject: [PATCH 16/24] PR feedback. added some comments, code clean up and rename. --- .../EngineV2/CompilerDiagnosticExecutor.cs | 10 +++--- ...gnosticIncrementalAnalyzer.AnalysisData.cs | 35 +++++++++++++++++-- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/CompilerDiagnosticExecutor.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/CompilerDiagnosticExecutor.cs index 3431b8656e6b1..bed38cc7543aa 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/CompilerDiagnosticExecutor.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/CompilerDiagnosticExecutor.cs @@ -137,7 +137,7 @@ private void AddExternalDiagnostics( lazyLocals = lazyLocals ?? new Dictionary>(); lazyLocals.GetOrAdd(document.Id, _ => new List()).Add(DiagnosticData.Create(document, diagnostic)); - SetDocument(document); + AddDocumentToSet(document); } } else if (diagnosticDocumentId != null) @@ -149,7 +149,7 @@ private void AddExternalDiagnostics( _lazyNonLocals = _lazyNonLocals ?? new Dictionary>(); _lazyNonLocals.GetOrAdd(document.Id, _ => new List()).Add(DiagnosticData.Create(document, diagnostic)); - SetDocument(document); + AddDocumentToSet(document); } } else @@ -232,7 +232,7 @@ private void AddDiagnostics( lazyLocals = lazyLocals ?? new Dictionary>(); lazyLocals.GetOrAdd(document.Id, _ => new List()).Add(DiagnosticData.Create(document, diagnostic)); - SetDocument(document); + AddDocumentToSet(document); } } else if (diagnostic.Location.SourceTree != null) @@ -244,7 +244,7 @@ private void AddDiagnostics( _lazyNonLocals = _lazyNonLocals ?? new Dictionary>(); _lazyNonLocals.GetOrAdd(document.Id, _ => new List()).Add(DiagnosticData.Create(document, diagnostic)); - SetDocument(document); + AddDocumentToSet(document); } } else @@ -271,7 +271,7 @@ private void AddDiagnostics( } } - private void SetDocument(Document document) + private void AddDocumentToSet(Document document) { _lazySet = _lazySet ?? new HashSet(); _lazySet.Add(document.Id); diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.AnalysisData.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.AnalysisData.cs index 5dab0b020e873..39fc6dad93646 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.AnalysisData.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.AnalysisData.cs @@ -11,16 +11,27 @@ namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 internal partial class DiagnosticIncrementalAnalyzer { /// - /// + /// Simple data holder for local diagnostics for an analyzer /// private struct DocumentAnalysisData { public static readonly DocumentAnalysisData Empty = new DocumentAnalysisData(VersionStamp.Default, ImmutableArray.Empty); + /// + /// Version of the Items + /// public readonly VersionStamp Version; - public readonly ImmutableArray OldItems; + + /// + /// Current data that matches the version + /// public readonly ImmutableArray Items; + /// + /// When present, This hold onto last data we broadcast to outer world + /// + public readonly ImmutableArray OldItems; + public DocumentAnalysisData(VersionStamp version, ImmutableArray items) { this.Version = version; @@ -44,13 +55,31 @@ public bool FromCache } } + /// + /// Data holder for all diagnostics for a project for an analyzer + /// private struct ProjectAnalysisData { + /// + /// ProjectId of this data + /// public readonly ProjectId ProjectId; + + /// + /// Version of the Items + /// public readonly VersionStamp Version; - public readonly ImmutableDictionary OldResult; + + /// + /// Current data that matches the version + /// public readonly ImmutableDictionary Result; + /// + /// When present, This hold onto last data we broadcast to outer world + /// + public readonly ImmutableDictionary OldResult; + public ProjectAnalysisData(ProjectId projectId, VersionStamp version, ImmutableDictionary result) { ProjectId = projectId; From 060de6a974df3ffaf1b239138c8425f3b9ed71ad Mon Sep 17 00:00:00 2001 From: Heejae Chang Date: Tue, 12 Apr 2016 14:53:43 -0700 Subject: [PATCH 17/24] use semantic based IsCompilerAnalyzer rather than the hacky string based one. --- src/Features/Core/Portable/Diagnostics/HostAnalyzerManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Features/Core/Portable/Diagnostics/HostAnalyzerManager.cs b/src/Features/Core/Portable/Diagnostics/HostAnalyzerManager.cs index ab38c6646ec6f..96f3e24d33cd0 100644 --- a/src/Features/Core/Portable/Diagnostics/HostAnalyzerManager.cs +++ b/src/Features/Core/Portable/Diagnostics/HostAnalyzerManager.cs @@ -161,7 +161,7 @@ private ImmutableArray GetDiagnosticDescriptorsCore(Diagno public bool IsAnalyzerSuppressed(DiagnosticAnalyzer analyzer, Project project) { var options = project.CompilationOptions; - if (options == null || analyzer.IsCompilerAnalyzer()) + if (options == null || IsCompilerDiagnosticAnalyzer(project.Language, analyzer)) { return false; } From 7fb3292abc5d915d958d4bce3789184afca43b9d Mon Sep 17 00:00:00 2001 From: Heejae Chang Date: Tue, 12 Apr 2016 15:09:59 -0700 Subject: [PATCH 18/24] CR feedback. removed unnecessary GetEffectiveDiagnostics call for diagnostics from CompilationWithAnalyzers and other code clean up. --- .../EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs index 058820aad211a..5cab12f0f9473 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs @@ -159,7 +159,7 @@ private async Task> Merg { foreach (var document in project.Documents) { - if (project.SupportsCompilation) + if (document.SupportsSyntaxTree) { var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); builder.AddSyntaxDiagnostics(tree, await ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync(document, documentAnalyzer, AnalysisKind.Syntax, compilationOpt, cancellationToken).ConfigureAwait(false)); @@ -256,11 +256,11 @@ private async Task> ComputeDiagnosticAnalyzerDiagnostics case AnalysisKind.Syntax: var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); var diagnostics = await analyzerDriverOpt.GetAnalyzerSyntaxDiagnosticsAsync(tree, oneAnalyzers, cancellationToken).ConfigureAwait(false); - return CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, analyzerDriverOpt.Compilation); + return diagnostics.ToImmutableArrayOrEmpty(); case AnalysisKind.Semantic: var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); diagnostics = await analyzerDriverOpt.GetAnalyzerSemanticDiagnosticsAsync(model, spanOpt, oneAnalyzers, cancellationToken).ConfigureAwait(false); - return CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, analyzerDriverOpt.Compilation); + return diagnostics.ToImmutableArrayOrEmpty(); default: return Contract.FailWithReturn>("shouldn't reach here"); } From 2920c1550025c04edae6e2bb7b8384d30071dd8b Mon Sep 17 00:00:00 2001 From: Heejae Chang Date: Tue, 12 Apr 2016 15:31:24 -0700 Subject: [PATCH 19/24] CR feedback. added assert for GetEffectiveDiagnostics --- .../Diagnostics/EngineV2/CompilerDiagnosticExecutor.cs | 3 +++ .../EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/CompilerDiagnosticExecutor.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/CompilerDiagnosticExecutor.cs index bed38cc7543aa..a6889e9df2d64 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/CompilerDiagnosticExecutor.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/CompilerDiagnosticExecutor.cs @@ -43,13 +43,16 @@ public static async Task var model = compilation.GetSemanticModel(tree); var syntax = await analyzerDriver.GetAnalyzerSyntaxDiagnosticsAsync(tree, oneAnalyzers, cancellationToken).ConfigureAwait(false); + Contract.Requires(syntax.Count() == CompilationWithAnalyzers.GetEffectiveDiagnostics(syntax, analyzerDriver.Compilation).Count()); result.AddSyntaxDiagnostics(tree, syntax); var semantic = await analyzerDriver.GetAnalyzerSemanticDiagnosticsAsync(model, noSpanFilter, oneAnalyzers, cancellationToken).ConfigureAwait(false); + Contract.Requires(semantic.Count() == CompilationWithAnalyzers.GetEffectiveDiagnostics(semantic, analyzerDriver.Compilation).Count()); result.AddSemanticDiagnostics(tree, semantic); } var rest = await analyzerDriver.GetAnalyzerCompilationDiagnosticsAsync(oneAnalyzers, cancellationToken).ConfigureAwait(false); + Contract.Requires(rest.Count() == CompilationWithAnalyzers.GetEffectiveDiagnostics(rest, analyzerDriver.Compilation).Count()); result.AddCompilationDiagnostics(rest); builder.Add(analyzer, result.ToResult()); diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs index 5cab12f0f9473..7bc80369ed7c0 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs @@ -256,10 +256,14 @@ private async Task> ComputeDiagnosticAnalyzerDiagnostics case AnalysisKind.Syntax: var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); var diagnostics = await analyzerDriverOpt.GetAnalyzerSyntaxDiagnosticsAsync(tree, oneAnalyzers, cancellationToken).ConfigureAwait(false); + + Contract.Requires(diagnostics.Count() == CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, analyzerDriverOpt.Compilation).Count()); return diagnostics.ToImmutableArrayOrEmpty(); case AnalysisKind.Semantic: var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); diagnostics = await analyzerDriverOpt.GetAnalyzerSemanticDiagnosticsAsync(model, spanOpt, oneAnalyzers, cancellationToken).ConfigureAwait(false); + + Contract.Requires(diagnostics.Count() == CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, analyzerDriverOpt.Compilation).Count()); return diagnostics.ToImmutableArrayOrEmpty(); default: return Contract.FailWithReturn>("shouldn't reach here"); From 60e41b214fc5d4e64da375162584a9336b35958f Mon Sep 17 00:00:00 2001 From: Heejae Chang Date: Tue, 12 Apr 2016 16:04:15 -0700 Subject: [PATCH 20/24] added comment based on PR. --- .../DiagnosticIncrementalAnalyzer.Executor.cs | 12 ++++++++++++ ...iagnosticIncrementalAnalyzer.ProjectState.cs | 17 +++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs index 7bc80369ed7c0..f7fdf0cb80cd5 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs @@ -41,6 +41,9 @@ public IEnumerable ConvertToLocalDiagnostics(Document targetDocu return ConvertToLocalDiagnosticsWithoutCompilation(targetDocument, diagnostics, span); } + /// + /// Return all local diagnostics (syntax, semantic) that belong to given document for the given StateSet (analyzer) either from cache or by calculating them + /// public async Task GetDocumentAnalysisDataAsync( CompilationWithAnalyzers analyzerDriverOpt, Document document, StateSet stateSet, AnalysisKind kind, CancellationToken cancellationToken) { @@ -74,6 +77,9 @@ public async Task GetDocumentAnalysisDataAsync( } } + /// + /// Return all diagnostics that belong to given project for the given StateSets (analyzers) either from cache or by calculating them + /// public async Task GetProjectAnalysisDataAsync( CompilationWithAnalyzers analyzerDriverOpt, Project project, IEnumerable stateSets, CancellationToken cancellationToken) { @@ -105,6 +111,9 @@ public async Task GetProjectAnalysisDataAsync( } } + /// + /// Return all local diagnostics (syntax, semantic) that belong to given document for the given StateSet (analyzer) by calculating them + /// public async Task> ComputeDiagnosticsAsync( CompilationWithAnalyzers analyzerDriverOpt, Document document, DiagnosticAnalyzer analyzer, AnalysisKind kind, TextSpan? spanOpt, CancellationToken cancellationToken) { @@ -119,6 +128,9 @@ public async Task> ComputeDiagnosticsAsync( return ConvertToLocalDiagnostics(document, documentDiagnostics); } + /// + /// Return all diagnostics that belong to given project for the given StateSets (analyzers) by calculating them + /// public async Task> ComputeDiagnosticsAsync( CompilationWithAnalyzers analyzerDriverOpt, Project project, IEnumerable stateSets, CancellationToken cancellationToken) { diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs index 956bf342b04da..aadb80393b965 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs @@ -42,6 +42,9 @@ public bool IsEmpty(DocumentId documentId) return IsEmpty(_lastResult, documentId); } + /// + /// Return all diagnostics for the given project stored in this state + /// public async Task GetAnalysisDataAsync(Project project, bool avoidLoadingData, CancellationToken cancellationToken) { // make a copy of last result. @@ -54,14 +57,14 @@ public async Task GetAnalysisDataAsync(Project project, bool avo } // PERF: avoid loading data if version is not right one. - // avoid loading data flag is there as a strickly perf optimization. + // avoid loading data flag is there as a strictly perf optimization. var version = await GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false); if (avoidLoadingData && lastResult.Version != version) { return lastResult; } - // if given document doesnt have any diagnostics, return empty. + // if given project doesnt have any diagnostics, return empty. if (lastResult.IsEmpty) { return new AnalysisResult(lastResult.ProjectId, lastResult.Version); @@ -73,6 +76,8 @@ public async Task GetAnalysisDataAsync(Project project, bool avo foreach (var documentId in lastResult.DocumentIds) { + cancellationToken.ThrowIfCancellationRequested(); + var document = project.GetDocument(documentId); if (document == null) { @@ -94,6 +99,9 @@ public async Task GetAnalysisDataAsync(Project project, bool avo return builder.ToResult(); } + /// + /// Return all diagnostics for the given document stored in this state including non local diagnostics for this document + /// public async Task GetAnalysisDataAsync(Document document, bool avoidLoadingData, CancellationToken cancellationToken) { // make a copy of last result. @@ -129,6 +137,9 @@ public async Task GetAnalysisDataAsync(Document document, bool a return builder.ToResult(); } + /// + /// Return all no location diagnostics for the given project stored in this state + /// public async Task GetProjectAnalysisDataAsync(Project project, bool avoidLoadingData, CancellationToken cancellationToken) { // make a copy of last result. @@ -209,6 +220,8 @@ private async Task LoadInitialAnalysisDataAsync(Project project, foreach (var document in project.Documents) { + cancellationToken.ThrowIfCancellationRequested(); + if (!await TryDeserializeDocumentAsync(serializer, document, builder, cancellationToken).ConfigureAwait(false)) { continue; From 5122239b2fe6d6baee23128b8ab725e643065d2f Mon Sep 17 00:00:00 2001 From: Heejae Chang Date: Tue, 12 Apr 2016 16:52:17 -0700 Subject: [PATCH 21/24] added some comment --- .../EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs index f9a6b547254a7..f805527c58249 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs @@ -158,7 +158,8 @@ public async Task> GetDiagnosticsAsync(Cancellati protected async Task AppendDiagnosticsAsync(Solution solution, CancellationToken cancellationToken) { - // PERF: should run this concurrently? analyzer driver itself is already running concurrently. + // PERF; run projects parallely rather than running CompilationWithAnalyzer with concurrency == true. + // we doing this to be safe (not get into thread starvation causing hundreds of threads to be spawn up). DocumentId nullTargetDocumentId = null; var tasks = new Task[solution.ProjectIds.Count]; From 1410c34690f4c448178d7b54cd5381e93962281c Mon Sep 17 00:00:00 2001 From: Heejae Chang Date: Wed, 13 Apr 2016 18:11:04 -0700 Subject: [PATCH 22/24] make analyzer driver won't get into action execution when there is actually no action for the analyzer --- .../DiagnosticAnalyzer/AnalyzerDriver.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs index 2b2db793324ef..d42191d2ac5d5 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs @@ -1557,6 +1557,11 @@ private void ExecuteDeclaringReferenceActions( ImmutableDictionary>> nodeActionsByKind; if (this.NodeActionsByAnalyzerAndKind.TryGetValue(analyzer, out nodeActionsByKind)) { + if (nodeActionsByKind.IsEmpty) + { + continue; + } + analyzerExecutor.ExecuteSyntaxNodeActions(nodesToAnalyze, nodeActionsByKind, analyzer, semanticModel, _getKind, declarationAnalysisData.TopmostNodeForAnalysis.FullSpan, decl, declarationIndex, symbol, analysisScope, analysisStateOpt, isInGeneratedCode); @@ -1610,6 +1615,11 @@ private void ExecuteDeclaringReferenceActions( ImmutableDictionary> operationActionsByKind; if (this.OperationActionsByAnalyzerAndKind.TryGetValue(analyzer, out operationActionsByKind)) { + if (operationActionsByKind.IsEmpty) + { + continue; + } + analyzerExecutor.ExecuteOperationActions(operationsToAnalyze, operationActionsByKind, analyzer, semanticModel, declarationAnalysisData.TopmostNodeForAnalysis.FullSpan, decl, declarationIndex, symbol, analysisScope, analysisStateOpt, isInGeneratedCode); @@ -1621,6 +1631,13 @@ private void ExecuteDeclaringReferenceActions( { foreach (var analyzerActions in codeBlockActions) { + if (analyzerActions.OperationBlockStartActions.IsEmpty && + analyzerActions.OperationBlockActions.IsEmpty && + analyzerActions.OpererationBlockEndActions.IsEmpty) + { + continue; + } + analyzerExecutor.ExecuteOperationBlockActions( analyzerActions.OperationBlockStartActions, analyzerActions.OperationBlockActions, analyzerActions.OpererationBlockEndActions, analyzerActions.Analyzer, declarationAnalysisData.TopmostNodeForAnalysis, symbol, @@ -1639,6 +1656,13 @@ private void ExecuteDeclaringReferenceActions( { foreach (var analyzerActions in codeBlockActions) { + if (analyzerActions.CodeBlockStartActions.IsEmpty && + analyzerActions.CodeBlockActions.IsEmpty && + analyzerActions.CodeBlockEndActions.IsEmpty) + { + continue; + } + analyzerExecutor.ExecuteCodeBlockActions( analyzerActions.CodeBlockStartActions, analyzerActions.CodeBlockActions, analyzerActions.CodeBlockEndActions, analyzerActions.Analyzer, declarationAnalysisData.TopmostNodeForAnalysis, symbol, From ef2603016dff3577a006d0f63bc7b916f41a90d1 Mon Sep 17 00:00:00 2001 From: Heejae Chang Date: Wed, 13 Apr 2016 18:28:56 -0700 Subject: [PATCH 23/24] CR feedback. added flag for including project non local result. --- ...osticIncrementalAnalyzer_GetDiagnostics.cs | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs index f805527c58249..00d4634a1d1a9 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs @@ -94,7 +94,7 @@ protected ImmutableArray GetDiagnosticData() } protected abstract Task?> GetDiagnosticsAsync(StateSet stateSet, Project project, DocumentId documentId, AnalysisKind kind, CancellationToken cancellationToken); - protected abstract Task AppendDiagnosticsAsync(Project project, DocumentId targetDocumentId, IEnumerable documentIds, CancellationToken cancellationToken); + protected abstract Task AppendDiagnosticsAsync(Project project, IEnumerable documentIds, bool includeProjectNonLocalResult, CancellationToken cancellationToken); public async Task> GetSpecificDiagnosticsAsync(CancellationToken cancellationToken) { @@ -148,7 +148,8 @@ public async Task> GetDiagnosticsAsync(Cancellati var documentIds = CurrentDocumentId != null ? SpecializedCollections.SingletonEnumerable(CurrentDocumentId) : CurrentProject.DocumentIds; // return diagnostics specific to one project or document - await AppendDiagnosticsAsync(CurrentProject, CurrentDocumentId, documentIds, cancellationToken).ConfigureAwait(false); + var includeProjectNonLocalResult = CurrentDocumentId == null; + await AppendDiagnosticsAsync(CurrentProject, documentIds, includeProjectNonLocalResult, cancellationToken).ConfigureAwait(false); return GetDiagnosticData(); } @@ -160,7 +161,7 @@ protected async Task AppendDiagnosticsAsync(Solution solution, CancellationToken { // PERF; run projects parallely rather than running CompilationWithAnalyzer with concurrency == true. // we doing this to be safe (not get into thread starvation causing hundreds of threads to be spawn up). - DocumentId nullTargetDocumentId = null; + var includeProjectNonLocalResult = true; var tasks = new Task[solution.ProjectIds.Count]; var index = 0; @@ -169,7 +170,7 @@ protected async Task AppendDiagnosticsAsync(Solution solution, CancellationToken var localProject = project; tasks[index++] = Task.Run( () => AppendDiagnosticsAsync( - localProject, nullTargetDocumentId, localProject.DocumentIds, cancellationToken: cancellationToken), cancellationToken); + localProject, localProject.DocumentIds, includeProjectNonLocalResult, cancellationToken), cancellationToken); } await Task.WhenAll(tasks).ConfigureAwait(false); @@ -243,7 +244,7 @@ public IDECachedDiagnosticGetter(DiagnosticIncrementalAnalyzer owner, Solution s { } - protected override async Task AppendDiagnosticsAsync(Project project, DocumentId targetDocumentId, IEnumerable documentIds, CancellationToken cancellationToken) + protected override async Task AppendDiagnosticsAsync(Project project, IEnumerable documentIds, bool includeProjectNonLocalResult, CancellationToken cancellationToken) { // when we return cached result, make sure we at least return something that exist in current solution if (project == null) @@ -260,10 +261,11 @@ protected override async Task AppendDiagnosticsAsync(Project project, DocumentId AppendDiagnostics(await GetDiagnosticsAsync(stateSet, project, documentId, AnalysisKind.NonLocal, cancellationToken).ConfigureAwait(false)); } - if (targetDocumentId == null) + if (includeProjectNonLocalResult) { // include project diagnostics if there is no target document - AppendDiagnostics(await GetProjectDiagnosticsAsync(stateSet, project, targetDocumentId, AnalysisKind.NonLocal, cancellationToken).ConfigureAwait(false)); + DocumentId targetDocumentId = null; + AppendDiagnostics(await GetProjectStateDiagnosticsAsync(stateSet, project, targetDocumentId, AnalysisKind.NonLocal, cancellationToken).ConfigureAwait(false)); } } } @@ -278,7 +280,7 @@ protected override async Task AppendDiagnosticsAsync(Project project, DocumentId return activeFileDiagnostics.Value; } - var projectDiagnostics = await GetProjectDiagnosticsAsync(stateSet, project, documentId, kind, cancellationToken).ConfigureAwait(false); + var projectDiagnostics = await GetProjectStateDiagnosticsAsync(stateSet, project, documentId, kind, cancellationToken).ConfigureAwait(false); if (projectDiagnostics.HasValue) { return projectDiagnostics.Value; @@ -303,7 +305,7 @@ protected override async Task AppendDiagnosticsAsync(Project project, DocumentId return state.GetAnalysisData(kind).Items; } - private async Task?> GetProjectDiagnosticsAsync( + private async Task?> GetProjectStateDiagnosticsAsync( StateSet stateSet, Project project, DocumentId documentId, AnalysisKind kind, CancellationToken cancellationToken) { ProjectState state; @@ -366,11 +368,10 @@ public async Task> GetProjectDiagnosticsAsync(Can return GetDiagnosticData(); } - DocumentId nullTargetDocumentId = null; - if (CurrentProjectId != null) { - await AppendDiagnosticsAsync(CurrentProject, nullTargetDocumentId, SpecializedCollections.EmptyEnumerable(), cancellationToken).ConfigureAwait(false); + var includeProjectNonLocalResult = true; + await AppendDiagnosticsAsync(CurrentProject, SpecializedCollections.EmptyEnumerable(), includeProjectNonLocalResult, cancellationToken).ConfigureAwait(false); return GetDiagnosticData(); } @@ -383,7 +384,7 @@ protected override bool ShouldIncludeDiagnostic(DiagnosticData diagnostic) return _diagnosticIds == null || _diagnosticIds.Contains(diagnostic.Id); } - protected override async Task AppendDiagnosticsAsync(Project project, DocumentId targetDocumentId, IEnumerable documentIds, CancellationToken cancellationToken) + protected override async Task AppendDiagnosticsAsync(Project project, IEnumerable documentIds, bool includeProjectNonLocalResult, CancellationToken cancellationToken) { // when we return cached result, make sure we at least return something that exist in current solution if (project == null) @@ -410,7 +411,7 @@ protected override async Task AppendDiagnosticsAsync(Project project, DocumentId AppendDiagnostics(GetResult(analysisResult, AnalysisKind.NonLocal, documentId)); } - if (targetDocumentId == null) + if (includeProjectNonLocalResult) { // include project diagnostics if there is no target document AppendDiagnostics(analysisResult.Others); From ba9daa6dd81505ce53ce7aacc24e3ab95767e03f Mon Sep 17 00:00:00 2001 From: Heejae Chang Date: Wed, 13 Apr 2016 18:43:10 -0700 Subject: [PATCH 24/24] restore +x --- build/scripts/restore.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 build/scripts/restore.sh diff --git a/build/scripts/restore.sh b/build/scripts/restore.sh old mode 100644 new mode 100755