forked from dotnet/roslyn
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request dotnet#1402 from heejaechang/staleerror
change the way build and live diagnostics are merged
- Loading branch information
Showing
17 changed files
with
618 additions
and
294 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
43 changes: 43 additions & 0 deletions
43
src/Features/Core/Diagnostics/DiagnosticAnalyzerService_BuildSynchronization.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// 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 | ||
{ | ||
internal partial class DiagnosticAnalyzerService | ||
{ | ||
/// <summary> | ||
/// Synchronize build errors with live error. | ||
/// | ||
/// no cancellationToken since this can't be cancelled | ||
/// </summary> | ||
public Task SynchronizeWithBuildAsync(Project project, ImmutableArray<DiagnosticData> diagnostics) | ||
{ | ||
BaseDiagnosticIncrementalAnalyzer analyzer; | ||
if (_map.TryGetValue(project.Solution.Workspace, out analyzer)) | ||
{ | ||
return analyzer.SynchronizeWithBuildAsync(project, diagnostics); | ||
} | ||
|
||
return SpecializedTasks.EmptyTask; | ||
} | ||
|
||
/// <summary> | ||
/// Synchronize build errors with live error | ||
/// | ||
/// no cancellationToken since this can't be cancelled | ||
/// </summary> | ||
public Task SynchronizeWithBuildAsync(Document document, ImmutableArray<DiagnosticData> diagnostics) | ||
{ | ||
BaseDiagnosticIncrementalAnalyzer analyzer; | ||
if (_map.TryGetValue(document.Project.Solution.Workspace, out analyzer)) | ||
{ | ||
return analyzer.SynchronizeWithBuildAsync(document, diagnostics); | ||
} | ||
|
||
return SpecializedTasks.EmptyTask; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
191 changes: 191 additions & 0 deletions
191
src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer_BuildSynchronization.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
// 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 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) | ||
{ | ||
if (!PreferBuildErrors(project.Solution.Workspace)) | ||
{ | ||
// prefer live errors over build errors | ||
return; | ||
} | ||
|
||
using (var poolObject = SharedPools.Default<HashSet<string>>().GetPooledObject()) | ||
{ | ||
var lookup = CreateDiagnosticIdLookup(diagnostics); | ||
|
||
foreach (var stateSet in _stateManger.GetStateSets(project)) | ||
{ | ||
var descriptors = HostAnalyzerManager.GetDiagnosticDescriptors(stateSet.Analyzer); | ||
var liveDiagnostics = ConvertToLiveDiagnostics(lookup, descriptors, poolObject.Object); | ||
|
||
// we are using Default so that things like LB can't use cached information | ||
var projectTextVersion = VersionStamp.Default; | ||
var semanticVersion = await project.GetDependentSemanticVersionAsync(CancellationToken.None).ConfigureAwait(false); | ||
|
||
var state = stateSet.GetState(StateType.Project); | ||
var existingDiagnostics = await state.TryGetExistingDataAsync(project, CancellationToken.None).ConfigureAwait(false); | ||
|
||
var mergedDiagnostics = MergeDiagnostics(liveDiagnostics, GetExistingDiagnostics(existingDiagnostics)); | ||
await state.PersistAsync(project, new AnalysisData(projectTextVersion, semanticVersion, mergedDiagnostics), CancellationToken.None).ConfigureAwait(false); | ||
RaiseDiagnosticsUpdated(StateType.Project, project.Id, stateSet.Analyzer, new SolutionArgument(project), mergedDiagnostics); | ||
} | ||
} | ||
} | ||
|
||
public override async Task SynchronizeWithBuildAsync(Document document, ImmutableArray<DiagnosticData> diagnostics) | ||
{ | ||
if (!PreferBuildErrors(document.Project.Solution.Workspace)) | ||
{ | ||
// prefer live errors over build errors | ||
return; | ||
} | ||
|
||
using (var poolObject = SharedPools.Default<HashSet<string>>().GetPooledObject()) | ||
{ | ||
var lookup = CreateDiagnosticIdLookup(diagnostics); | ||
|
||
foreach (var stateSet in _stateManger.GetStateSets(document.Project)) | ||
{ | ||
// we are using Default so that things like LB can't use cached information | ||
var textVersion = VersionStamp.Default; | ||
var semanticVersion = await document.Project.GetDependentSemanticVersionAsync(CancellationToken.None).ConfigureAwait(false); | ||
|
||
// clear document and project live errors | ||
await PersistAndReportAsync(stateSet, StateType.Project, document, textVersion, semanticVersion, ImmutableArray<DiagnosticData>.Empty).ConfigureAwait(false); | ||
await PersistAndReportAsync(stateSet, StateType.Syntax, document, textVersion, semanticVersion, ImmutableArray<DiagnosticData>.Empty).ConfigureAwait(false); | ||
|
||
var descriptors = HostAnalyzerManager.GetDiagnosticDescriptors(stateSet.Analyzer); | ||
var liveDiagnostics = ConvertToLiveDiagnostics(lookup, descriptors, poolObject.Object); | ||
|
||
// REVIEW: for now, we are putting build error in document state rather than creating its own state. | ||
// reason is so that live error can take over it as soon as possible | ||
// this also means there can be slight race where it will clean up eventually. if live analysis runs for syntax but didn't run | ||
// for document yet, then we can have duplicated entries in the error list until live analysis catch. | ||
await PersistAndReportAsync(stateSet, StateType.Document, document, textVersion, semanticVersion, liveDiagnostics).ConfigureAwait(false); | ||
} | ||
} | ||
} | ||
|
||
private bool PreferBuildErrors(Workspace workspace) | ||
{ | ||
return workspace.Options.GetOption(InternalDiagnosticsOptions.BuildErrorIsTheGod) || workspace.Options.GetOption(InternalDiagnosticsOptions.BuildErrorWinLiveError); | ||
} | ||
|
||
private async Task PersistAndReportAsync( | ||
StateSet stateSet, StateType stateType, Document document, VersionStamp textVersion, VersionStamp semanticVersion, ImmutableArray<DiagnosticData> diagnostics) | ||
{ | ||
var state = stateSet.GetState(stateType); | ||
var existingDiagnostics = await state.TryGetExistingDataAsync(document, CancellationToken.None).ConfigureAwait(false); | ||
|
||
var mergedDiagnostics = MergeDiagnostics(diagnostics, GetExistingDiagnostics(existingDiagnostics)); | ||
await state.PersistAsync(document, new AnalysisData(textVersion, semanticVersion, mergedDiagnostics), CancellationToken.None).ConfigureAwait(false); | ||
RaiseDiagnosticsUpdated(stateType, document.Id, stateSet.Analyzer, new SolutionArgument(document), mergedDiagnostics); | ||
} | ||
|
||
private static ImmutableArray<DiagnosticData> GetExistingDiagnostics(AnalysisData analysisData) | ||
{ | ||
if (analysisData == null) | ||
{ | ||
return ImmutableArray<DiagnosticData>.Empty; | ||
} | ||
|
||
return analysisData.Items; | ||
} | ||
|
||
private ImmutableArray<DiagnosticData> MergeDiagnostics(ImmutableArray<DiagnosticData> liveDiagnostics, ImmutableArray<DiagnosticData> existingDiagnostics) | ||
{ | ||
ImmutableArray<DiagnosticData>.Builder builder = null; | ||
|
||
if (liveDiagnostics.Length > 0) | ||
{ | ||
builder = ImmutableArray.CreateBuilder<DiagnosticData>(); | ||
builder.AddRange(liveDiagnostics); | ||
} | ||
|
||
if (existingDiagnostics.Length > 0) | ||
{ | ||
builder = builder ?? ImmutableArray.CreateBuilder<DiagnosticData>(); | ||
builder.AddRange(existingDiagnostics.Where(d => d.Severity == DiagnosticSeverity.Hidden)); | ||
} | ||
|
||
return builder == null ? ImmutableArray<DiagnosticData>.Empty : builder.ToImmutable(); | ||
} | ||
|
||
private static ILookup<string, DiagnosticData> CreateDiagnosticIdLookup(ImmutableArray<DiagnosticData> diagnostics) | ||
{ | ||
if (diagnostics.Length == 0) | ||
{ | ||
return null; | ||
} | ||
|
||
return diagnostics.ToLookup(d => d.Id); | ||
} | ||
|
||
private ImmutableArray<DiagnosticData> ConvertToLiveDiagnostics( | ||
ILookup<string, DiagnosticData> lookup, ImmutableArray<DiagnosticDescriptor> descriptors, HashSet<string> seen) | ||
{ | ||
if (lookup == null) | ||
{ | ||
return ImmutableArray<DiagnosticData>.Empty; | ||
} | ||
|
||
ImmutableArray<DiagnosticData>.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 doesnt need this. | ||
continue; | ||
} | ||
|
||
var items = lookup[descriptor.Id]; | ||
if (items == null) | ||
{ | ||
continue; | ||
} | ||
|
||
builder = builder ?? ImmutableArray.CreateBuilder<DiagnosticData>(); | ||
builder.AddRange(items.Select(d => CreateLiveDiagnostic(descriptor, d))); | ||
} | ||
|
||
return builder == null ? ImmutableArray<DiagnosticData>.Empty : builder.ToImmutable(); | ||
} | ||
|
||
private static DiagnosticData CreateLiveDiagnostic(DiagnosticDescriptor descriptor, DiagnosticData diagnostic) | ||
{ | ||
return new DiagnosticData( | ||
descriptor.Id, | ||
descriptor.Category, | ||
diagnostic.Message, | ||
descriptor.MessageFormat.ToString(DiagnosticData.USCultureInfo), | ||
diagnostic.Severity, | ||
descriptor.DefaultSeverity, | ||
descriptor.IsEnabledByDefault, | ||
diagnostic.WarningLevel, | ||
descriptor.CustomTags.ToImmutableArray(), | ||
diagnostic.Properties, | ||
diagnostic.Workspace, | ||
diagnostic.ProjectId, | ||
diagnostic.DocumentId, | ||
diagnostic.HasTextSpan ? diagnostic.TextSpan : (TextSpan?)null, | ||
diagnostic.MappedFilePath, diagnostic.MappedStartLine, diagnostic.MappedStartColumn, diagnostic.MappedEndLine, diagnostic.MappedEndColumn, | ||
diagnostic.OriginalFilePath, diagnostic.OriginalStartLine, diagnostic.OriginalStartColumn, diagnostic.OriginalEndLine, diagnostic.OriginalEndColumn, | ||
descriptor.Title.ToString(CultureInfo.CurrentUICulture), | ||
descriptor.Description.ToString(CultureInfo.CurrentUICulture), | ||
descriptor.HelpLinkUri); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.