提交 e31195da 编写于 作者: H Heejae Chang

Merge pull request #9032 from heejaechang/builddedup

made us to clean up all live errors for projects that built cleanly
......@@ -194,7 +194,7 @@ protected BaseDiagnosticIncrementalAnalyzer(DiagnosticAnalyzerService owner, Wor
///
/// given diagnostics are project wide diagnostics that doesn't contain a source location.
/// </summary>
public abstract Task SynchronizeWithBuildAsync(Project project, ImmutableArray<DiagnosticData> diagnostics);
public abstract Task SynchronizeWithBuildAsync(DiagnosticAnalyzerService.BatchUpdateToken token, Project project, ImmutableArray<DiagnosticData> diagnostics);
/// <summary>
/// Callback from build listener.
......@@ -206,7 +206,7 @@ protected BaseDiagnosticIncrementalAnalyzer(DiagnosticAnalyzerService owner, Wor
///
/// given diagnostics are ones that has a source location.
/// </summary>
public abstract Task SynchronizeWithBuildAsync(Document document, ImmutableArray<DiagnosticData> diagnostics);
public abstract Task SynchronizeWithBuildAsync(DiagnosticAnalyzerService.BatchUpdateToken token, Document document, ImmutableArray<DiagnosticData> diagnostics);
#endregion
internal DiagnosticAnalyzerService Owner { get; }
......
// 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;
......@@ -8,17 +10,28 @@ namespace Microsoft.CodeAnalysis.Diagnostics
{
internal partial class DiagnosticAnalyzerService
{
/// <summary>
/// Start new Batch build diagnostics update token.
/// </summary>
public IDisposable BeginBatchBuildDiagnosticsUpdate(Solution solution)
{
return new BatchUpdateToken(solution);
}
/// <summary>
/// Synchronize build errors with live error.
///
/// no cancellationToken since this can't be cancelled
/// </summary>
public Task SynchronizeWithBuildAsync(Project project, ImmutableArray<DiagnosticData> diagnostics)
public Task SynchronizeWithBuildAsync(IDisposable batchUpdateToken, Project project, ImmutableArray<DiagnosticData> diagnostics)
{
var token = (BatchUpdateToken)batchUpdateToken;
token.CheckProjectInSnapshot(project);
BaseDiagnosticIncrementalAnalyzer analyzer;
if (_map.TryGetValue(project.Solution.Workspace, out analyzer))
{
return analyzer.SynchronizeWithBuildAsync(project, diagnostics);
return analyzer.SynchronizeWithBuildAsync(token, project, diagnostics);
}
return SpecializedTasks.EmptyTask;
......@@ -29,15 +42,49 @@ public Task SynchronizeWithBuildAsync(Project project, ImmutableArray<Diagnostic
///
/// no cancellationToken since this can't be cancelled
/// </summary>
public Task SynchronizeWithBuildAsync(Document document, ImmutableArray<DiagnosticData> diagnostics)
public Task SynchronizeWithBuildAsync(IDisposable batchUpdateToken, Document document, ImmutableArray<DiagnosticData> diagnostics)
{
var token = (BatchUpdateToken)batchUpdateToken;
token.CheckDocumentInSnapshot(document);
BaseDiagnosticIncrementalAnalyzer analyzer;
if (_map.TryGetValue(document.Project.Solution.Workspace, out analyzer))
{
return analyzer.SynchronizeWithBuildAsync(document, diagnostics);
return analyzer.SynchronizeWithBuildAsync(token, document, diagnostics);
}
return SpecializedTasks.EmptyTask;
}
public class BatchUpdateToken : IDisposable
{
public readonly ConcurrentDictionary<object, object> _cache = new ConcurrentDictionary<object, object>(concurrencyLevel: 2, capacity: 1);
private readonly Solution _solution;
public BatchUpdateToken(Solution solution)
{
_solution = solution;
}
public object GetCache(object key, Func<object, object> 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();
}
}
}
}
......@@ -175,14 +175,14 @@ public override Task<IEnumerable<DiagnosticData>> GetDiagnosticsForSpanAsync(Doc
#endregion
#region build synchronization
public override Task SynchronizeWithBuildAsync(Project project, ImmutableArray<DiagnosticData> diagnostics)
public override Task SynchronizeWithBuildAsync(DiagnosticAnalyzerService.BatchUpdateToken token, Project project, ImmutableArray<DiagnosticData> diagnostics)
{
return Analyzer.SynchronizeWithBuildAsync(project, diagnostics);
return Analyzer.SynchronizeWithBuildAsync(token, project, diagnostics);
}
public override Task SynchronizeWithBuildAsync(Document document, ImmutableArray<DiagnosticData> diagnostics)
public override Task SynchronizeWithBuildAsync(DiagnosticAnalyzerService.BatchUpdateToken token, Document document, ImmutableArray<DiagnosticData> diagnostics)
{
return Analyzer.SynchronizeWithBuildAsync(document, diagnostics);
return Analyzer.SynchronizeWithBuildAsync(token, document, diagnostics);
}
#endregion
......
// 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;
......@@ -68,6 +69,16 @@ public IEnumerable<StateSet> GetStateSets(Project project)
return GetStateSets(project.Id).Where(s => s.Language == project.Language);
}
/// <summary>
/// Return <see cref="StateSet"/>s that are added as the given <see cref="Project"/>'s AnalyzerReferences.
/// This will never create new <see cref="StateSet"/> but will return ones already created.
/// </summary>
public ImmutableArray<StateSet> GetBuildOnlyStateSets(object cache, Project project)
{
var stateSetCache = (IDictionary<Project, ImmutableArray<StateSet>>)cache;
return stateSetCache.GetOrAdd(project, CreateBuildOnlyProjectStateSet);
}
/// <summary>
/// Return <see cref="StateSet"/>s for the given <see cref="Project"/>.
/// This will either return already created <see cref="StateSet"/>s for the specific snapshot of <see cref="Project"/> or
......@@ -116,6 +127,47 @@ public void RemoveStateSet(ProjectId projectId)
_projectStates.RemoveStateSet(projectId);
}
private ImmutableArray<StateSet> 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<StateSet>();
// 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();
}
private void RaiseProjectAnalyzerReferenceChanged(ProjectAnalyzerReferenceChangedEventArgs args)
{
ProjectAnalyzerReferenceChanged?.Invoke(this, args);
......
// 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;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Diagnostics.EngineV1
{
internal partial class DiagnosticIncrementalAnalyzer
{
public override async Task SynchronizeWithBuildAsync(Project project, ImmutableArray<DiagnosticData> diagnostics)
private readonly static Func<object, object> s_cacheCreator = _ => new ConcurrentDictionary<Project, ImmutableArray<StateSet>>(concurrencyLevel: 2, capacity: 10);
public override async Task SynchronizeWithBuildAsync(DiagnosticAnalyzerService.BatchUpdateToken token, Project project, ImmutableArray<DiagnosticData> diagnostics)
{
if (!PreferBuildErrors(project.Solution.Workspace))
{
......@@ -25,7 +28,7 @@ public override async Task SynchronizeWithBuildAsync(Project project, ImmutableA
{
var lookup = CreateDiagnosticIdLookup(diagnostics);
foreach (var stateSet in _stateManager.GetStateSets(project))
foreach (var stateSet in _stateManager.GetBuildOnlyStateSets(token.GetCache(_stateManager, s_cacheCreator), project))
{
var descriptors = HostAnalyzerManager.GetDiagnosticDescriptors(stateSet.Analyzer);
var liveDiagnostics = ConvertToLiveDiagnostics(lookup, descriptors, poolObject.Object);
......@@ -44,7 +47,7 @@ public override async Task SynchronizeWithBuildAsync(Project project, ImmutableA
}
}
public override async Task SynchronizeWithBuildAsync(Document document, ImmutableArray<DiagnosticData> diagnostics)
public override async Task SynchronizeWithBuildAsync(DiagnosticAnalyzerService.BatchUpdateToken token, Document document, ImmutableArray<DiagnosticData> diagnostics)
{
var workspace = document.Project.Solution.Workspace;
if (!PreferBuildErrors(workspace))
......@@ -65,7 +68,7 @@ public override async Task SynchronizeWithBuildAsync(Document document, Immutabl
{
var lookup = CreateDiagnosticIdLookup(diagnostics);
foreach (var stateSet in _stateManager.GetStateSets(document.Project))
foreach (var stateSet in _stateManager.GetBuildOnlyStateSets(token.GetCache(_stateManager, s_cacheCreator), document.Project))
{
// we are using Default so that things like LB can't use cached information
var textVersion = VersionStamp.Default;
......@@ -130,6 +133,7 @@ private ImmutableArray<DiagnosticData> MergeDiagnostics(ImmutableArray<Diagnosti
if (existingDiagnostics.Length > 0)
{
// retain hidden live diagnostics since it won't be comes from build.
builder = builder ?? ImmutableArray.CreateBuilder<DiagnosticData>();
builder.AddRange(existingDiagnostics.Where(d => d.Severity == DiagnosticSeverity.Hidden));
}
......
......@@ -186,7 +186,7 @@ private IEnumerable<DiagnosticData> GetDiagnosticData(Project project, Immutable
}
}
public override Task SynchronizeWithBuildAsync(Project project, ImmutableArray<DiagnosticData> diagnostics)
public override Task SynchronizeWithBuildAsync(DiagnosticAnalyzerService.BatchUpdateToken token, Project project, ImmutableArray<DiagnosticData> 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
......@@ -194,7 +194,7 @@ public override Task SynchronizeWithBuildAsync(Project project, ImmutableArray<D
return SpecializedTasks.EmptyTask;
}
public override Task SynchronizeWithBuildAsync(Document document, ImmutableArray<DiagnosticData> diagnostics)
public override Task SynchronizeWithBuildAsync(DiagnosticAnalyzerService.BatchUpdateToken token, Document document, ImmutableArray<DiagnosticData> 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
......
......@@ -5,12 +5,9 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using Microsoft.CodeAnalysis.Diagnostics.Log;
using Roslyn.Utilities;
using System.Runtime.CompilerServices;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
namespace Microsoft.CodeAnalysis.Diagnostics
{
......
......@@ -170,8 +170,6 @@ internal void OnSolutionBuild(object sender, UIContextChangedEventArgs e)
// result of the race will be us dropping some diagnostics from the build to the floor.
var solution = _workspace.CurrentSolution;
await CleanupAllLiveErrorsIfNeededAsync(solution, inprogressState).ConfigureAwait(false);
var supportedIdMap = GetSupportedLiveDiagnosticId(solution, inprogressState);
Func<DiagnosticData, bool> liveDiagnosticChecker = d =>
{
......@@ -202,41 +200,60 @@ internal void OnSolutionBuild(object sender, UIContextChangedEventArgs e)
return false;
};
await SyncBuildErrorsAndReportAsync(solution, liveDiagnosticChecker, inprogressState.GetDocumentAndErrors(solution)).ConfigureAwait(false);
await SyncBuildErrorsAndReportAsync(solution, liveDiagnosticChecker, inprogressState.GetProjectAndErrors(solution)).ConfigureAwait(false);
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);
}
}
inprogressState.Done();
}
}).CompletesAsyncOperation(asyncToken);
}
private async System.Threading.Tasks.Task CleanupAllLiveErrorsIfNeededAsync(Solution solution, InprogressState state)
private async System.Threading.Tasks.Task CleanupAllLiveErrorsIfNeededAsync(
DiagnosticAnalyzerService diagnosticService, IDisposable batchUpdateToken,
Solution solution, InprogressState state)
{
var buildErrorIsTheGod = _workspace.Options.GetOption(InternalDiagnosticsOptions.BuildErrorIsTheGod);
var clearProjectErrors = _workspace.Options.GetOption(InternalDiagnosticsOptions.ClearLiveErrorsForProjectBuilt);
if (_workspace.Options.GetOption(InternalDiagnosticsOptions.BuildErrorIsTheGod))
{
await CleanupAllLiveErrors(diagnosticService, batchUpdateToken, solution, state, solution.Projects).ConfigureAwait(false);
return;
}
if (!buildErrorIsTheGod && !clearProjectErrors)
if (_workspace.Options.GetOption(InternalDiagnosticsOptions.ClearLiveErrorsForProjectBuilt))
{
await CleanupAllLiveErrors(diagnosticService, batchUpdateToken, solution, state, state.GetProjectsBuilt(solution)).ConfigureAwait(false);
return;
}
var projects = buildErrorIsTheGod ? solution.Projects :
(clearProjectErrors ? state.GetProjectsBuilt(solution) : SpecializedCollections.EmptyEnumerable<Project>());
await CleanupAllLiveErrors(diagnosticService, batchUpdateToken, solution, state, state.GetProjectsWithoutErrors(solution)).ConfigureAwait(false);
return;
}
// clear all live errors
private static async System.Threading.Tasks.Task CleanupAllLiveErrors(
DiagnosticAnalyzerService diagnosticService, IDisposable batchUpdateToken,
Solution solution, InprogressState state, IEnumerable<Project> projects)
{
foreach (var project in projects)
{
foreach (var document in project.Documents)
{
await SynchronizeWithBuildAsync(document, ImmutableArray<DiagnosticData>.Empty).ConfigureAwait(false);
await SynchronizeWithBuildAsync(diagnosticService, batchUpdateToken, document, ImmutableArray<DiagnosticData>.Empty).ConfigureAwait(false);
}
await SynchronizeWithBuildAsync(project, ImmutableArray<DiagnosticData>.Empty).ConfigureAwait(false);
await SynchronizeWithBuildAsync(diagnosticService, batchUpdateToken, project, ImmutableArray<DiagnosticData>.Empty).ConfigureAwait(false);
}
}
private async System.Threading.Tasks.Task SyncBuildErrorsAndReportAsync<T>(
Solution solution,
DiagnosticAnalyzerService diagnosticService, IDisposable batchUpdateToken, Solution solution,
Func<DiagnosticData, bool> liveDiagnosticChecker, IEnumerable<KeyValuePair<T, HashSet<DiagnosticData>>> items)
{
foreach (var kv in items)
......@@ -245,7 +262,7 @@ private async System.Threading.Tasks.Task CleanupAllLiveErrorsIfNeededAsync(Solu
var liveErrors = kv.Value.Where(liveDiagnosticChecker).ToImmutableArray();
// make those errors live errors
await SynchronizeWithBuildAsync(kv.Key, liveErrors).ConfigureAwait(false);
await SynchronizeWithBuildAsync(diagnosticService, batchUpdateToken, kv.Key, liveErrors).ConfigureAwait(false);
// raise events for ones left-out
if (liveErrors.Length != kv.Value.Count)
......@@ -256,25 +273,20 @@ private async System.Threading.Tasks.Task CleanupAllLiveErrorsIfNeededAsync(Solu
}
}
private async System.Threading.Tasks.Task SynchronizeWithBuildAsync<T>(T item, ImmutableArray<DiagnosticData> liveErrors)
private static async System.Threading.Tasks.Task SynchronizeWithBuildAsync<T>(
DiagnosticAnalyzerService diagnosticService, IDisposable batchUpdateToken,
T item, ImmutableArray<DiagnosticData> liveErrors)
{
var diagnosticService = _diagnosticService as DiagnosticAnalyzerService;
if (diagnosticService == null)
{
// we don't synchronize if implementation is not DiagnosticAnalyzerService
return;
}
var project = item as Project;
if (project != null)
{
await diagnosticService.SynchronizeWithBuildAsync(project, liveErrors).ConfigureAwait(false);
await diagnosticService.SynchronizeWithBuildAsync(batchUpdateToken, project, liveErrors).ConfigureAwait(false);
return;
}
// must be not null
var document = item as Document;
await diagnosticService.SynchronizeWithBuildAsync(document, liveErrors).ConfigureAwait(false);
await diagnosticService.SynchronizeWithBuildAsync(batchUpdateToken, document, liveErrors).ConfigureAwait(false);
}
private void ReportBuildErrors<T>(T item, ImmutableArray<DiagnosticData> buildErrors)
......@@ -449,6 +461,11 @@ public IEnumerable<Project> GetProjectsWithErrors(Solution solution)
}
}
public IEnumerable<Project> GetProjectsWithoutErrors(Solution solution)
{
return GetProjectsBuilt(solution).Except(GetProjectsWithErrors(solution));
}
public IEnumerable<KeyValuePair<Document, HashSet<DiagnosticData>>> GetDocumentAndErrors(Solution solution)
{
foreach (var kv in _documentMap)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册