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

Merge pull request #10617 from heejaechang/cleanup2

some code clean up and made active and project state to be merged when a file is closed
......@@ -122,15 +122,14 @@ public static async Task<ProjectAnalysisData> CreateAsync(Project project, IEnum
if (!version.HasValue)
{
if (result.Version != VersionStamp.Default)
{
version = result.Version;
}
version = result.Version;
}
else
else if (version.Value != VersionStamp.Default && version.Value != result.Version)
{
// all version must be same or default (means not there yet)
Contract.Requires(version == result.Version || result.Version == VersionStamp.Default);
// if not all version is same, set version as default.
// this can happen at the initial data loading or
// when document is closed and we put active file state to project state
version = VersionStamp.Default;
}
builder.Add(stateSet.Analyzer, result);
......
......@@ -8,6 +8,7 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics.Log;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Shared.Options;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
......@@ -47,33 +48,41 @@ public IEnumerable<DiagnosticData> ConvertToLocalDiagnostics(Document targetDocu
public async Task<DocumentAnalysisData> GetDocumentAnalysisDataAsync(
CompilationWithAnalyzers analyzerDriverOpt, 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);
// get log title and functionId
string title;
FunctionId functionId;
GetLogFunctionIdAndTitle(kind, out functionId, out title);
if (existingData.Version == version)
using (Logger.LogBlock(functionId, GetDocumentLogMessage, title, document, stateSet.Analyzer, cancellationToken))
{
try
{
return existingData;
}
var version = await GetDiagnosticVersionAsync(document.Project, cancellationToken).ConfigureAwait(false);
var state = stateSet.GetActiveFileState(document.Id);
var existingData = state.GetAnalysisData(kind);
// 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<DiagnosticData>.Empty);
}
if (existingData.Version == version)
{
return existingData;
}
var nullFilterSpan = (TextSpan?)null;
var diagnostics = await ComputeDiagnosticsAsync(analyzerDriverOpt, document, stateSet.Analyzer, kind, nullFilterSpan, cancellationToken).ConfigureAwait(false);
// 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<DiagnosticData>.Empty);
}
// we only care about local diagnostics
return new DocumentAnalysisData(version, existingData.Items, diagnostics.ToImmutableArrayOrEmpty());
}
catch (Exception e) when (FatalError.ReportUnlessCanceled(e))
{
throw ExceptionUtilities.Unreachable;
var nullFilterSpan = (TextSpan?)null;
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());
}
catch (Exception e) when (FatalError.ReportUnlessCanceled(e))
{
throw ExceptionUtilities.Unreachable;
}
}
}
......@@ -83,31 +92,34 @@ public IEnumerable<DiagnosticData> ConvertToLocalDiagnostics(Document targetDocu
public async Task<ProjectAnalysisData> GetProjectAnalysisDataAsync(
CompilationWithAnalyzers analyzerDriverOpt, Project project, IEnumerable<StateSet> stateSets, CancellationToken cancellationToken)
{
try
using (Logger.LogBlock(FunctionId.Diagnostics_ProjectDiagnostic, GetProjectLogMessage, project, stateSets, cancellationToken))
{
// 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)
try
{
return existingData;
}
// 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);
// perf optimization. check whether we want to analyze this project or not.
if (!await FullAnalysisEnabledAsync(project, cancellationToken).ConfigureAwait(false))
{
return new ProjectAnalysisData(project.Id, version, existingData.Result, ImmutableDictionary<DiagnosticAnalyzer, AnalysisResult>.Empty);
}
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(project.Id, version, existingData.Result, ImmutableDictionary<DiagnosticAnalyzer, AnalysisResult>.Empty);
}
var result = await ComputeDiagnosticsAsync(analyzerDriverOpt, project, stateSets, cancellationToken).ConfigureAwait(false);
var result = await ComputeDiagnosticsAsync(analyzerDriverOpt, project, stateSets, cancellationToken).ConfigureAwait(false);
return new ProjectAnalysisData(project.Id, version, existingData.Result, result);
}
catch (Exception e) when (FatalError.ReportUnlessCanceled(e))
{
throw ExceptionUtilities.Unreachable;
return new ProjectAnalysisData(project.Id, version, existingData.Result, result);
}
catch (Exception e) when (FatalError.ReportUnlessCanceled(e))
{
throw ExceptionUtilities.Unreachable;
}
}
}
......@@ -381,6 +393,26 @@ private IEnumerable<DiagnosticData> ConvertToLocalDiagnosticsWithCompilation(Doc
yield return DiagnosticData.Create(document, diagnostic);
}
}
private static void GetLogFunctionIdAndTitle(AnalysisKind kind, out FunctionId functionId, out string title)
{
switch (kind)
{
case AnalysisKind.Syntax:
functionId = FunctionId.Diagnostics_SyntaxDiagnostic;
title = "syntax";
break;
case AnalysisKind.Semantic:
functionId = FunctionId.Diagnostics_SemanticDiagnostic;
title = "semantic";
break;
default:
functionId = FunctionId.Diagnostics_ProjectDiagnostic;
title = "nonLocal";
Contract.Fail("shouldn't reach here");
break;
}
}
}
}
}
......@@ -199,6 +199,38 @@ public async Task SaveAsync(Project project, AnalysisResult result)
await SerializeAsync(serializer, project, result.ProjectId, _owner.NonLocalStateName, result.Others).ConfigureAwait(false);
}
public async Task MergeAsync(ActiveFileState state, Document document)
{
Contract.ThrowIfFalse(state.DocumentId == document.Id);
// merge active file state to project state
var lastResult = _lastResult;
var syntax = state.GetAnalysisData(AnalysisKind.Syntax);
var semantic = state.GetAnalysisData(AnalysisKind.Semantic);
// if all versions are same, nothing to do
if (syntax.Version != VersionStamp.Default &&
syntax.Version == semantic.Version &&
syntax.Version == lastResult.Version)
{
// all data is in sync already.
return;
}
// we have mixed versions, set it to default so that it can be re-calculated next time so data can be in sync.
var version = VersionStamp.Default;
// serialization can't be cancelled.
var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, version);
await SerializeAsync(serializer, document, document.Id, _owner.SyntaxStateName, syntax.Items).ConfigureAwait(false);
await SerializeAsync(serializer, document, document.Id, _owner.SemanticStateName, semantic.Items).ConfigureAwait(false);
// save last aggregated form of analysis result
_lastResult = new AnalysisResult(_lastResult.ProjectId, version, _lastResult.DocumentIds.Add(state.DocumentId), isEmpty: false);
}
public bool OnDocumentRemoved(DocumentId id)
{
RemoveInMemoryCacheEntries(id);
......
......@@ -5,6 +5,7 @@
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2
......@@ -151,23 +152,25 @@ public ImmutableArray<StateSet> CreateBuildOnlyProjectStateSet(Project project)
return stateSets.ToImmutable();
}
public bool OnDocumentReset(IEnumerable<StateSet> stateSets, DocumentId documentId)
public async Task<bool> OnDocumentResetAsync(IEnumerable<StateSet> stateSets, Document document)
{
// can not be cancelled
var removed = false;
foreach (var stateSet in stateSets)
{
removed |= stateSet.OnDocumentReset(documentId);
removed |= await stateSet.OnDocumentResetAsync(document).ConfigureAwait(false);
}
return removed;
}
public bool OnDocumentClosed(IEnumerable<StateSet> stateSets, DocumentId documentId)
public async Task<bool> OnDocumentClosedAsync(IEnumerable<StateSet> stateSets, Document document)
{
// can not be cancelled
var removed = false;
foreach (var stateSet in stateSets)
{
removed |= stateSet.OnDocumentClosed(documentId);
removed |= await stateSet.OnDocumentClosedAsync(document).ConfigureAwait(false);
}
return removed;
......
......@@ -3,6 +3,7 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2
......@@ -132,27 +133,38 @@ public ProjectState GetProjectState(ProjectId projectId)
return _projectStates.GetOrAdd(projectId, id => new ProjectState(this, id));
}
public bool OnDocumentClosed(DocumentId id)
public Task<bool> OnDocumentClosedAsync(Document document)
{
return OnDocumentReset(id);
// can not be cancelled
return OnDocumentResetAsync(document);
}
public bool OnDocumentReset(DocumentId id)
public async Task<bool> OnDocumentResetAsync(Document document)
{
// remove active file state
ActiveFileState state;
if (_activeFileStates.TryRemove(id, out state))
// can not be cancelled
// remove active file state and put it in project state
ActiveFileState activeFileState;
if (!_activeFileStates.TryRemove(document.Id, out activeFileState))
{
return !state.IsEmpty;
// no active file state
return false;
}
return false;
var projectState = GetProjectState(document.Project.Id);
await projectState.MergeAsync(activeFileState, document).ConfigureAwait(false);
return true;
}
public bool OnDocumentRemoved(DocumentId id)
{
// remove active file state for removed document
var removed = OnDocumentReset(id);
var removed = false;
ActiveFileState activeFileState;
if (_activeFileStates.TryRemove(id, out activeFileState))
{
removed = true;
}
// remove state for the file that got removed.
ProjectState state;
......
// 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.Shared.Extensions;
using Roslyn.Utilities;
......@@ -214,5 +217,44 @@ private static ImmutableArray<DiagnosticData> GetResult(AnalysisResult result, A
return Contract.FailWithReturn<ImmutableArray<DiagnosticData>>("shouldn't reach here");
}
}
public override void LogAnalyzerCountSummary()
{
DiagnosticAnalyzerLogger.LogAnalyzerCrashCountSummary(_correlationId, DiagnosticLogAggregator);
DiagnosticAnalyzerLogger.LogAnalyzerTypeCountSummary(_correlationId, DiagnosticLogAggregator);
// reset the log aggregator
ResetDiagnosticLogAggregator();
}
private static string GetDocumentLogMessage(string title, Document document, DiagnosticAnalyzer analyzer)
{
return string.Format($"{title}: {document.FilePath ?? document.Name}, {analyzer.ToString()}");
}
private static string GetProjectLogMessage(Project project, IEnumerable<StateSet> stateSets)
{
return string.Format($"project: {project.FilePath ?? project.Name}, {string.Join(",", stateSets.Select(s => s.Analyzer.ToString()))}");
}
private static string GetResetLogMessage(Document document)
{
return string.Format($"document close/reset: {document.FilePath ?? document.Name}");
}
private static string GetOpenLogMessage(Document document)
{
return string.Format($"document open: {document.FilePath ?? document.Name}");
}
private static string GetRemoveLogMessage(DocumentId id)
{
return string.Format($"document remove: {id.ToString()}");
}
private static string GetRemoveLogMessage(ProjectId id)
{
return string.Format($"project remove: {id.ToString()}");
}
}
}
......@@ -7,6 +7,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
......@@ -101,95 +102,100 @@ public override async Task AnalyzeProjectAsync(Project project, bool semanticsCh
public override Task DocumentOpenAsync(Document document, CancellationToken cancellationToken)
{
// let other component knows about this event
_compilationManager.OnDocumentOpened();
using (Logger.LogBlock(FunctionId.Diagnostics_DocumentOpen, GetOpenLogMessage, document, cancellationToken))
{
// let other component knows about this event
_compilationManager.OnDocumentOpened();
// here we dont need to raise any event, it will be taken cared by analyze methods.
return SpecializedTasks.EmptyTask;
// 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)
public override async Task DocumentCloseAsync(Document document, CancellationToken cancellationToken)
{
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);
using (Logger.LogBlock(FunctionId.Diagnostics_DocumentClose, GetResetLogMessage, document, cancellationToken))
{
var stateSets = _stateManager.GetStateSets(document.Project);
return SpecializedTasks.EmptyTask;
// let other components knows about this event
_compilationManager.OnDocumentClosed();
await _stateManager.OnDocumentClosedAsync(stateSets, document).ConfigureAwait(false);
}
}
public override Task DocumentResetAsync(Document document, CancellationToken cancellationToken)
public override async Task DocumentResetAsync(Document document, CancellationToken cancellationToken)
{
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);
using (Logger.LogBlock(FunctionId.Diagnostics_DocumentReset, GetResetLogMessage, document, cancellationToken))
{
var stateSets = _stateManager.GetStateSets(document.Project);
return SpecializedTasks.EmptyTask;
// let other components knows about this event
_compilationManager.OnDocumentReset();
await _stateManager.OnDocumentResetAsync(stateSets, document).ConfigureAwait(false);
}
}
public override void RemoveDocument(DocumentId documentId)
{
var stateSets = _stateManager.GetStateSets(documentId.ProjectId);
// let other components knows about this event
_compilationManager.OnDocumentRemoved();
var changed = _stateManager.OnDocumentRemoved(stateSets, documentId);
// if there was no diagnostic reported for this document, nothing to clean up
if (!changed)
using (Logger.LogBlock(FunctionId.Diagnostics_RemoveDocument, GetRemoveLogMessage, documentId, CancellationToken.None))
{
// this is Perf to reduce raising events unnecessarily.
return;
}
var stateSets = _stateManager.GetStateSets(documentId.ProjectId);
// remove all diagnostics for the document
Owner.RaiseBulkDiagnosticsUpdated(raiseEvents =>
{
Solution nullSolution = null;
foreach (var stateSet in stateSets)
// let other components knows about this event
_compilationManager.OnDocumentRemoved();
var changed = _stateManager.OnDocumentRemoved(stateSets, documentId);
// if there was no diagnostic reported for this document, nothing to clean up
if (!changed)
{
// 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);
// 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 void RemoveProject(ProjectId projectId)
{
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)
using (Logger.LogBlock(FunctionId.Diagnostics_RemoveProject, GetRemoveLogMessage, projectId, CancellationToken.None))
{
// this is Perf to reduce raising events unnecessarily.
return;
}
var stateSets = _stateManager.GetStateSets(projectId);
// remove all diagnostics for the project
Owner.RaiseBulkDiagnosticsUpdated(raiseEvents =>
{
Solution nullSolution = null;
foreach (var stateSet in stateSets)
// 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)
{
// clear all project diagnostics
RaiseDiagnosticsRemoved(projectId, nullSolution, stateSet, raiseEvents);
// this is Perf to reduce raising events unnecessarily.
return;
}
});
// 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)
......@@ -207,50 +213,6 @@ private static bool AnalysisEnabled(Document document)
return document.IsOpen();
}
private void RaiseLocalDocumentEventsFromProjectOverActiveFile(IEnumerable<StateSet> stateSets, Document document, bool activeFileDiagnosticExist)
{
// 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<StateSet> stateSets,
......
......@@ -322,6 +322,7 @@ private void ClearDocumentErrors(ProjectId projectId, DocumentId documentId)
public void AddNewErrors(DocumentId documentId, DiagnosticData diagnostic)
{
// capture state that will be processed in background thread.
var state = GetOrCreateInprogressState();
var asyncToken = _listener.BeginAsyncOperation("Document New Errors");
......@@ -334,6 +335,7 @@ public void AddNewErrors(DocumentId documentId, DiagnosticData diagnostic)
public void AddNewErrors(
ProjectId projectId, HashSet<DiagnosticData> projectErrors, Dictionary<DocumentId, HashSet<DiagnosticData>> documentErrorMap)
{
// capture state that will be processed in background thread
var state = GetOrCreateInprogressState();
var asyncToken = _listener.BeginAsyncOperation("Project New Errors");
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册