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

Merge pull request #10509 from heejaechang/oop2

OOP #1 - new analyzer engine that uses CompilerAnalyzer model
......@@ -1557,6 +1557,11 @@ private static void ComputeDeclarationsInNode(SemanticModel semanticModel, ISymb
ImmutableDictionary<TLanguageKindEnum, ImmutableArray<SyntaxNodeAnalyzerAction<TLanguageKindEnum>>> 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 static void ComputeDeclarationsInNode(SemanticModel semanticModel, ISymb
ImmutableDictionary<OperationKind, ImmutableArray<OperationAnalyzerAction>> 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 static void ComputeDeclarationsInNode(SemanticModel semanticModel, ISymb
{
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 static void ComputeDeclarationsInNode(SemanticModel semanticModel, ISymb
{
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,
......
// 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<string>.Empty, ImmutableDictionary<string, string>.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<string>("Test2"), ImmutableDictionary<string, string>.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<string>("Test3", "Test3_2"), ImmutableDictionary<string, string>.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<string>.Empty, ImmutableDictionary<string, string>.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<string>("Test2"), ImmutableDictionary<string, string>.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<string>("Test3", "Test3_2"), ImmutableDictionary<string, string>.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 '<empty anonymous type>' to 'string'
// static int x = F(new { });
Diagnostic(1503, "new { }").WithArguments("1", "<empty anonymous type>", "string").WithLocation(4, 22),
// (5,22): error CS1503: Argument 1: cannot convert from '<anonymous type: int A>' to 'string'
// static int y = F(new { A = 1 });
Diagnostic(1503, "new { A = 1 }").WithArguments("1", "<anonymous type: int A>", "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<DiagnosticData> items1, ImmutableArray<DiagnosticData> 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<object, Stream> _map = new Dictionary<object, Stream>();
public Task<Stream> ReadStreamAsync(string name, CancellationToken cancellationToken = default(CancellationToken))
{
var stream = _map[name];
stream.Position = 0;
return Task.FromResult(stream);
}
public Task<Stream> 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<Stream> 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<bool> WriteStreamAsync(string name, Stream stream, CancellationToken cancellationToken = default(CancellationToken))
{
_map[name] = new MemoryStream();
stream.CopyTo(_map[name]);
return SpecializedTasks.True;
}
public Task<bool> 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<bool> 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);
}
}
}
}
}
}
// 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<Exception, DiagnosticAnalyzer, Diagnostic> _onAnalyzerException;
private readonly ImmutableDictionary<object, AnalyzerReference> _hostAnalyzerReferenceMap;
internal TestDiagnosticAnalyzerService(
string language,
......@@ -54,6 +56,7 @@ internal sealed class TestDiagnosticAnalyzerService : DiagnosticAnalyzerService
IDiagnosticUpdateSourceRegistrationService registrationService = null)
: base(hostAnalyzerManager, hostDiagnosticUpdateSource, registrationService ?? new MockDiagnosticUpdateSourceRegistrationService())
{
_hostAnalyzerReferenceMap = hostAnalyzerManager.CreateAnalyzerReferencesMap(projectOpt: null);
_onAnalyzerException = onAnalyzerException;
}
......@@ -92,5 +95,7 @@ private static HostAnalyzerManager CreateHostAnalyzerManager(ImmutableArray<Anal
{
return _onAnalyzerException ?? base.GetOnAnalyzerException(projectId, diagnosticLogAggregator);
}
internal IEnumerable<AnalyzerReference> HostAnalyzerReferences => _hostAnalyzerReferenceMap.Values;
}
}
......@@ -230,6 +230,7 @@
<Compile Include="Diagnostics\AbstractDiagnosticProviderBasedUserDiagnosticTest.cs" />
<Compile Include="Diagnostics\AbstractUserDiagnosticTest.cs" />
<Compile Include="Diagnostics\DiagnosticAnalyzerServiceTests.cs" />
<Compile Include="Diagnostics\DiagnosticDataSerializerTests.cs" />
<Compile Include="Diagnostics\MockDiagnosticUpdateSourceRegistrationService.cs" />
<Compile Include="Diagnostics\TestAnalyzerReferenceByLanguage.cs" />
<Compile Include="Diagnostics\TestDiagnosticAnalyzerDriver.cs" />
......@@ -369,4 +370,4 @@
<Import Project="..\..\..\build\Targets\VSL.Imports.targets" />
<Import Project="..\..\..\build\Targets\Roslyn.Toolsets.Xunit.targets" />
</ImportGroup>
</Project>
</Project>
\ No newline at end of file
......@@ -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,18 +730,22 @@ class AnonymousFunctions
Assert.Equal(1, diagnostics.Count())
Assert.Equal(document.Id, diagnostics.First().DocumentId)
' 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)
Dim projectDiagnostics = diagnosticService.GetDiagnosticsForIdsAsync(project.Solution, project.Id).WaitAndGetResult(CancellationToken.None)
Assert.Equal(2, projectDiagnostics.Count())
Dim noLocationDiagnostic = projectDiagnostics.First()
Dim noLocationDiagnostic = projectDiagnostics.First(Function(d) Not d.HasTextSpan)
Assert.Equal(CompilationEndedAnalyzer.Descriptor.Id, noLocationDiagnostic.Id)
Assert.Equal(False, noLocationDiagnostic.HasTextSpan)
Dim withDocumentLocationDiagnostic = projectDiagnostics.Last()
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
......
......@@ -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
/// </summary>
internal abstract class DocumentDiagnosticAnalyzer : DiagnosticAnalyzer
{
// REVIEW: why DocumentDiagnosticAnalyzer doesn't have span based analysis?
public abstract Task AnalyzeSyntaxAsync(Document document, Action<Diagnostic> addDiagnostic, CancellationToken cancellationToken);
public abstract Task AnalyzeSemanticsAsync(Document document, Action<Diagnostic> addDiagnostic, CancellationToken cancellationToken);
public override void Initialize(AnalysisContext context)
/// <summary>
/// it is not allowed one to implement both DocumentDiagnosticAnalzyer and DiagnosticAnalyzer
/// </summary>
public sealed override void Initialize(AnalysisContext context)
{
}
}
......
......@@ -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<Diagnostic> addDiagnostic, CancellationToken cancellationToken);
public override void Initialize(AnalysisContext context)
/// <summary>
/// it is not allowed one to implement both ProjectDiagnosticAnalzyer and DiagnosticAnalyzer
/// </summary>
public sealed override void Initialize(AnalysisContext context)
{
}
}
......
......@@ -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.
/// </summary>
public abstract Task SynchronizeWithBuildAsync(DiagnosticAnalyzerService.BatchUpdateToken token, Project project, ImmutableArray<DiagnosticData> diagnostics);
/// <summary>
/// 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.
/// </summary>
public abstract Task SynchronizeWithBuildAsync(DiagnosticAnalyzerService.BatchUpdateToken token, Document document, ImmutableArray<DiagnosticData> diagnostics);
public abstract Task SynchronizeWithBuildAsync(Workspace workspace, ImmutableDictionary<ProjectId, 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;
......@@ -10,81 +8,20 @@ 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(IDisposable batchUpdateToken, Project project, ImmutableArray<DiagnosticData> diagnostics)
public Task SynchronizeWithBuildAsync(Workspace workspace, ImmutableDictionary<ProjectId, ImmutableArray<DiagnosticData>> 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;
}
/// <summary>
/// Synchronize build errors with live error
///
/// no cancellationToken since this can't be cancelled
/// </summary>
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))
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<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();
}
}
}
}
......@@ -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<DiagnosticData> diagnostics)
public override Task SynchronizeWithBuildAsync(Workspace workspace, ImmutableDictionary<ProjectId, ImmutableArray<DiagnosticData>> diagnostics)
{
return Analyzer.SynchronizeWithBuildAsync(token, project, diagnostics);
}
public override Task SynchronizeWithBuildAsync(DiagnosticAnalyzerService.BatchUpdateToken token, Document document, ImmutableArray<DiagnosticData> diagnostics)
{
return Analyzer.SynchronizeWithBuildAsync(token, document, diagnostics);
return Analyzer.SynchronizeWithBuildAsync(workspace, diagnostics);
}
#endregion
......
......@@ -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<Action<DiagnosticsUpdatedArgs>>
}
}
internal void RaiseBulkDiagnosticsUpdated(Func<Action<DiagnosticsUpdatedArgs>, Task> eventActionAsync)
{
// all diagnostics events are serialized.
var ev = _eventMap.GetEventHandlers<EventHandler<DiagnosticsUpdatedArgs>>(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<DiagnosticsUpdatedArgs> 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<DiagnosticData> IDiagnosticUpdateSource.GetDiagnostics(Workspace workspace, ProjectId projectId, DocumentId documentId, object id, bool includeSuppressedDiagnostics, CancellationToken cancellationToken)
......
......@@ -295,12 +295,15 @@ public async Task<ImmutableArray<Diagnostic>> GetProjectDiagnosticsAsync(Diagnos
using (var diagnostics = SharedPools.Default<List<Diagnostic>>().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();
}
......@@ -311,29 +314,17 @@ public async Task<ImmutableArray<Diagnostic>> GetProjectDiagnosticsAsync(Diagnos
}
}
private async Task GetProjectDiagnosticsWorkerAsync(DiagnosticAnalyzer analyzer, List<Diagnostic> diagnostics)
private async Task GetProjectDiagnosticsWorkerAsync(ProjectDiagnosticAnalyzer analyzer, List<Diagnostic> 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);
}
}
......
// 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,52 +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 : AnalyzerUpdateArgsId
{
public readonly StateType StateType;
public readonly object Key;
public ArgumentKey(DiagnosticAnalyzer analyzer, StateType stateType, object key) : base(analyzer)
{
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()));
}
}
}
}
// 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;
......@@ -69,16 +68,6 @@ 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
......@@ -127,7 +116,11 @@ public void RemoveStateSet(ProjectId projectId)
_projectStates.RemoveStateSet(projectId);
}
private ImmutableArray<StateSet> CreateBuildOnlyProjectStateSet(Project project)
/// <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> CreateBuildOnlyProjectStateSet(Project project)
{
var referenceIdentities = project.AnalyzerReferences.Select(r => _analyzerManager.GetAnalyzerReferenceIdentity(r)).ToSet();
var stateSetMap = GetStateSets(project).ToDictionary(s => s.Analyzer, s => s);
......
......@@ -824,7 +824,7 @@ private static ImmutableArray<DiagnosticData> GetDiagnosticData(ILookup<Document
StateType type, object key, StateSet stateSet, SolutionArgument solution, ImmutableArray<DiagnosticData> diagnostics, Action<DiagnosticsUpdatedArgs> 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));
}
......@@ -838,7 +838,7 @@ private static ImmutableArray<DiagnosticData> GetDiagnosticData(ILookup<Document
StateType type, object key, StateSet stateSet, SolutionArgument solution, Action<DiagnosticsUpdatedArgs> 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));
}
......@@ -868,13 +868,6 @@ private void RaiseProjectDiagnosticsRemoved(Project project, IEnumerable<StateSe
}
}
private static ArgumentKey CreateArgumentKey(StateType type, object key, StateSet stateSet)
{
return stateSet.ErrorSourceName != null
? new HostAnalyzerKey(stateSet.Analyzer, type, key, stateSet.ErrorSourceName)
: new ArgumentKey(stateSet.Analyzer, type, key);
}
private ImmutableArray<DiagnosticData> UpdateDocumentDiagnostics(
AnalysisData existingData, ImmutableArray<TextSpan> range, ImmutableArray<DiagnosticData> memberDiagnostics,
SyntaxTree tree, SyntaxNode member, int memberId)
......
// 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<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)
public override async Task SynchronizeWithBuildAsync(Workspace workspace, ImmutableDictionary<ProjectId, ImmutableArray<DiagnosticData>> 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<StateSet> stateSets, IEnumerable<DiagnosticData> diagnostics)
{
using (var poolObject = SharedPools.Default<HashSet<string>>().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<DiagnosticData> diagnostics)
private async Task SynchronizeWithBuildAsync(Document document, IEnumerable<StateSet> stateSets, IEnumerable<DiagnosticData> 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<DiagnosticData> MergeDiagnostics(ImmutableArray<Diagnosti
return builder == null ? ImmutableArray<DiagnosticData>.Empty : builder.ToImmutable();
}
private static ILookup<string, DiagnosticData> CreateDiagnosticIdLookup(ImmutableArray<DiagnosticData> diagnostics)
private static ILookup<string, DiagnosticData> CreateDiagnosticIdLookup(IEnumerable<DiagnosticData> diagnostics)
{
if (diagnostics.Length == 0)
{
return null;
}
return diagnostics.ToLookup(d => d.Id);
}
......
......@@ -236,7 +236,7 @@ protected void AppendDiagnostics(IEnumerable<DiagnosticData> items)
}
}
protected virtual ImmutableArray<DiagnosticData> GetDiagnosticData()
protected ImmutableArray<DiagnosticData> GetDiagnosticData()
{
return _builder != null ? _builder.ToImmutableArray() : ImmutableArray<DiagnosticData>.Empty;
}
......@@ -295,7 +295,7 @@ public async Task<ImmutableArray<DiagnosticData>> GetSpecificDiagnosticsAsync(Ca
return ImmutableArray<DiagnosticData>.Empty;
}
var key = Id as ArgumentKey;
var key = Id as LiveDiagnosticUpdateArgsId;
if (key == null)
{
return ImmutableArray<DiagnosticData>.Empty;
......@@ -316,7 +316,7 @@ public async Task<ImmutableArray<DiagnosticData>> GetSpecificDiagnosticsAsync(Ca
return ImmutableArray<DiagnosticData>.Empty;
}
var state = stateSet.GetState(key.StateType);
var state = stateSet.GetState((StateType)key.Kind);
if (state == null)
{
return ImmutableArray<DiagnosticData>.Empty;
......@@ -584,7 +584,7 @@ public async Task<ImmutableArray<DiagnosticData>> GetSpecificDiagnosticsAsync(Ca
return ImmutableArray<DiagnosticData>.Empty;
}
var key = Id as ArgumentKey;
var key = Id as LiveDiagnosticUpdateArgsId;
if (key == null)
{
return ImmutableArray<DiagnosticData>.Empty;
......@@ -597,7 +597,7 @@ public async Task<ImmutableArray<DiagnosticData>> GetSpecificDiagnosticsAsync(Ca
return ImmutableArray<DiagnosticData>.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<ImmutableArray<DiagnosticData>> GetSpecificDiagnosticsAsync(Ca
return await GetSpecificDiagnosticsAsync(GetProject(documentOrProject), key, cancellationToken).ConfigureAwait(false);
}
private async Task<ImmutableArray<DiagnosticData>> GetSpecificDiagnosticsAsync(object documentOrProject, ArgumentKey key, CancellationToken cancellationToken)
private async Task<ImmutableArray<DiagnosticData>> 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<ImmutableArray<DiagnosticData>> 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;
}
......
// 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
{
/// <summary>
/// This holds onto diagnostics for a specific version of project snapshot
/// in a way each kind of diagnostics can be queried fast.
/// </summary>
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<DocumentId> 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<DocumentId, ImmutableArray<DiagnosticData>> _syntaxLocals;
private readonly ImmutableDictionary<DocumentId, ImmutableArray<DiagnosticData>> _semanticLocals;
private readonly ImmutableDictionary<DocumentId, ImmutableArray<DiagnosticData>> _nonLocals;
private readonly ImmutableArray<DiagnosticData> _others;
public AnalysisResult(ProjectId projectId, VersionStamp version) : this(
projectId, version,
documentIds: ImmutableHashSet<DocumentId>.Empty,
syntaxLocals: ImmutableDictionary<DocumentId, ImmutableArray<DiagnosticData>>.Empty,
semanticLocals: ImmutableDictionary<DocumentId, ImmutableArray<DiagnosticData>>.Empty,
nonLocals: ImmutableDictionary<DocumentId, ImmutableArray<DiagnosticData>>.Empty,
others: ImmutableArray<DiagnosticData>.Empty)
{
}
public AnalysisResult(
ProjectId projectId, VersionStamp version, ImmutableHashSet<DocumentId> documentIds, bool isEmpty)
{
ProjectId = projectId;
Version = version;
DocumentIds = documentIds;
IsEmpty = isEmpty;
_syntaxLocals = null;
_semanticLocals = null;
_nonLocals = null;
_others = default(ImmutableArray<DiagnosticData>);
}
public AnalysisResult(
ProjectId projectId, VersionStamp version,
ImmutableDictionary<DocumentId, ImmutableArray<DiagnosticData>> syntaxLocals,
ImmutableDictionary<DocumentId, ImmutableArray<DiagnosticData>> semanticLocals,
ImmutableDictionary<DocumentId, ImmutableArray<DiagnosticData>> nonLocals,
ImmutableArray<DiagnosticData> others,
ImmutableHashSet<DocumentId> documentIds)
{
ProjectId = projectId;
Version = version;
_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<DocumentId> DocumentIdsOrEmpty => DocumentIds ?? ImmutableHashSet<DocumentId>.Empty;
// this shouldn't be called for aggregated form.
public ImmutableDictionary<DocumentId, ImmutableArray<DiagnosticData>> SyntaxLocals => ReturnIfNotDefault(_syntaxLocals);
public ImmutableDictionary<DocumentId, ImmutableArray<DiagnosticData>> SemanticLocals => ReturnIfNotDefault(_semanticLocals);
public ImmutableDictionary<DocumentId, ImmutableArray<DiagnosticData>> NonLocals => ReturnIfNotDefault(_nonLocals);
public ImmutableArray<DiagnosticData> Others => ReturnIfNotDefault(_others);
public ImmutableArray<DiagnosticData> GetResultOrEmpty(ImmutableDictionary<DocumentId, ImmutableArray<DiagnosticData>> map, DocumentId key)
{
// this is just a helper method.
ImmutableArray<DiagnosticData> diagnostics;
if (map.TryGetValue(key, out diagnostics))
{
Contract.ThrowIfFalse(DocumentIds.Contains(key));
return diagnostics;
}
return ImmutableArray<DiagnosticData>.Empty;
}
public AnalysisResult ToAggregatedForm()
{
return new AnalysisResult(ProjectId, Version, DocumentIds, IsEmpty);
}
private T ReturnIfNotDefault<T>(T value)
{
if (object.Equals(value, default(T)))
{
Contract.Fail("shouldn't be called");
}
return value;
}
private ImmutableHashSet<DocumentId> CreateDocumentIds()
{
var documents = SpecializedCollections.EmptyEnumerable<DocumentId>();
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
// 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 Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2
{
/// <summary>
/// Diagnostic Executor that only relies on compiler layer. this might be replaced by new CompilationWithAnalyzer API.
/// </summary>
internal static class CompilerDiagnosticExecutor
{
public static async Task<ImmutableDictionary<DiagnosticAnalyzer, AnalysisResult>> AnalyzeAsync(this CompilationWithAnalyzers analyzerDriver, Project project, CancellationToken cancellationToken)
{
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?
// 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 analyzers = analyzerDriver.Analyzers;
var compilation = analyzerDriver.Compilation;
var builder = ImmutableDictionary.CreateBuilder<DiagnosticAnalyzer, AnalysisResult>();
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);
foreach (var tree in compilation.SyntaxTrees)
{
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());
}
return builder.ToImmutable();
}
/// <summary>
/// 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.
/// </summary>
internal struct Builder
{
private readonly Project _project;
private readonly VersionStamp _version;
private HashSet<DocumentId> _lazySet;
private Dictionary<DocumentId, List<DiagnosticData>> _lazySyntaxLocals;
private Dictionary<DocumentId, List<DiagnosticData>> _lazySemanticLocals;
private Dictionary<DocumentId, List<DiagnosticData>> _lazyNonLocals;
private List<DiagnosticData> _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<DocumentId>.Empty : _lazySet.ToImmutableHashSet();
var syntaxLocals = Convert(_lazySyntaxLocals);
var semanticLocals = Convert(_lazySemanticLocals);
var nonLocals = Convert(_lazyNonLocals);
var others = _lazyOthers == null ? ImmutableArray<DiagnosticData>.Empty : _lazyOthers.ToImmutableArray();
return new AnalysisResult(_project.Id, _version, syntaxLocals, semanticLocals, nonLocals, others, documentIds);
}
private ImmutableDictionary<DocumentId, ImmutableArray<DiagnosticData>> Convert(Dictionary<DocumentId, List<DiagnosticData>> map)
{
return map == null ? ImmutableDictionary<DocumentId, ImmutableArray<DiagnosticData>>.Empty : map.ToImmutableDictionary(kv => kv.Key, kv => kv.Value.ToImmutableArray());
}
public void AddExternalSyntaxDiagnostics(DocumentId documentId, IEnumerable<Diagnostic> 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<Diagnostic> diagnostics)
{
// this is for diagnostic producer that doesnt use compiler based DiagnosticAnalyzer such as TypeScript.
AddExternalDiagnostics(ref _lazySemanticLocals, documentId, diagnostics);
}
private void AddExternalDiagnostics(
ref Dictionary<DocumentId, List<DiagnosticData>> lazyLocals, DocumentId documentId, IEnumerable<Diagnostic> 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<DocumentId, List<DiagnosticData>>();
lazyLocals.GetOrAdd(document.Id, _ => new List<DiagnosticData>()).Add(DiagnosticData.Create(document, diagnostic));
AddDocumentToSet(document);
}
}
else if (diagnosticDocumentId != null)
{
var document = _project.GetDocument(diagnosticDocumentId);
if (document != null)
{
// non local diagnostics to a file
_lazyNonLocals = _lazyNonLocals ?? new Dictionary<DocumentId, List<DiagnosticData>>();
_lazyNonLocals.GetOrAdd(document.Id, _ => new List<DiagnosticData>()).Add(DiagnosticData.Create(document, diagnostic));
AddDocumentToSet(document);
}
}
else
{
// non local diagnostics without location
_lazyOthers = _lazyOthers ?? new List<DiagnosticData>();
_lazyOthers.Add(DiagnosticData.Create(_project, diagnostic));
}
break;
}
case LocationKind.None:
{
_lazyOthers = _lazyOthers ?? new List<DiagnosticData>();
_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<Diagnostic> diagnostics)
{
AddDiagnostics(ref _lazySyntaxLocals, tree, diagnostics);
}
public void AddSemanticDiagnostics(SyntaxTree tree, IEnumerable<Diagnostic> diagnostics)
{
AddDiagnostics(ref _lazySemanticLocals, tree, diagnostics);
}
public void AddCompilationDiagnostics(IEnumerable<Diagnostic> diagnostics)
{
Dictionary<DocumentId, List<DiagnosticData>> dummy = null;
AddDiagnostics(ref dummy, tree: null, diagnostics: diagnostics);
// dummy should be always null
Contract.Requires(dummy == null);
}
private void AddDiagnostics(
ref Dictionary<DocumentId, List<DiagnosticData>> lazyLocals, SyntaxTree tree, IEnumerable<Diagnostic> diagnostics)
{
foreach (var diagnostic in diagnostics)
{
// REVIEW: what is our plan for additional locations?
switch (diagnostic.Location.Kind)
{
case LocationKind.ExternalFile:
{
// TODO: currently additional file location is not supported.
break;
}
case LocationKind.None:
{
_lazyOthers = _lazyOthers ?? new List<DiagnosticData>();
_lazyOthers.Add(DiagnosticData.Create(_project, diagnostic));
break;
}
case LocationKind.SourceFile:
{
if (tree != null && diagnostic.Location.SourceTree == tree)
{
var document = GetDocument(diagnostic);
if (document != null)
{
// local diagnostics to a file
lazyLocals = lazyLocals ?? new Dictionary<DocumentId, List<DiagnosticData>>();
lazyLocals.GetOrAdd(document.Id, _ => new List<DiagnosticData>()).Add(DiagnosticData.Create(document, diagnostic));
AddDocumentToSet(document);
}
}
else if (diagnostic.Location.SourceTree != null)
{
var document = _project.GetDocument(diagnostic.Location.SourceTree);
if (document != null)
{
// non local diagnostics to a file
_lazyNonLocals = _lazyNonLocals ?? new Dictionary<DocumentId, List<DiagnosticData>>();
_lazyNonLocals.GetOrAdd(document.Id, _ => new List<DiagnosticData>()).Add(DiagnosticData.Create(document, diagnostic));
AddDocumentToSet(document);
}
}
else
{
// non local diagnostics without location
_lazyOthers = _lazyOthers ?? new List<DiagnosticData>();
_lazyOthers.Add(DiagnosticData.Create(_project, diagnostic));
}
break;
}
case LocationKind.MetadataFile:
case LocationKind.XmlFile:
{
// something we don't care
continue;
}
default:
{
Contract.Fail("should not reach");
break;
}
}
}
}
private void AddDocumentToSet(Document document)
{
_lazySet = _lazySet ?? new HashSet<DocumentId>();
_lazySet.Add(document.Id);
}
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);
}
}
}
}
// 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
{
/// <summary>
/// DiagnosticData serializer
/// </summary>
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<bool> SerializeAsync(object documentOrProject, string key, ImmutableArray<DiagnosticData> items, CancellationToken cancellationToken)
{
using (var stream = SerializableBytes.CreateWritableStream())
{
WriteTo(stream, items, cancellationToken);
var solution = GetSolution(documentOrProject);
var persistService = solution.Workspace.Services.GetService<IPersistentStorageService>();
using (var storage = persistService.GetStorage(solution))
{
stream.Position = 0;
return await WriteStreamAsync(storage, documentOrProject, key, stream, cancellationToken).ConfigureAwait(false);
}
}
}
public async Task<ImmutableArray<DiagnosticData>> DeserializeAsync(object documentOrProject, string key, CancellationToken cancellationToken)
{
// we have persisted data
var solution = GetSolution(documentOrProject);
var persistService = solution.Workspace.Services.GetService<IPersistentStorageService>();
using (var storage = persistService.GetStorage(solution))
using (var stream = await ReadStreamAsync(storage, key, documentOrProject, cancellationToken).ConfigureAwait(false))
{
if (stream == null)
{
return default(ImmutableArray<DiagnosticData>);
}
return ReadFrom(stream, documentOrProject, cancellationToken);
}
}
private Task<bool> 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<DiagnosticData> 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<DiagnosticDataLocation> 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<Stream> 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<DiagnosticData> 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<DiagnosticData> ReadFrom(Stream stream, Project project, Document document, CancellationToken cancellationToken)
{
try
{
using (var pooledObject = SharedPools.Default<List<DiagnosticData>>().GetPooledObject())
using (var reader = new ObjectReader(stream))
{
var list = pooledObject.Object;
var format = reader.ReadInt32();
if (format != FormatVersion)
{
return default(ImmutableArray<DiagnosticData>);
}
// saved data is for same analyzer of different version of dll
var analyzerVersion = VersionStamp.ReadFrom(reader);
if (analyzerVersion != AnalyzerVersion)
{
return default(ImmutableArray<DiagnosticData>);
}
var version = VersionStamp.ReadFrom(reader);
if (version != VersionStamp.Default && version != Version)
{
return default(ImmutableArray<DiagnosticData>);
}
ReadFrom(reader, project, document, list, cancellationToken);
return list.ToImmutableArray();
}
}
catch (Exception)
{
return default(ImmutableArray<DiagnosticData>);
}
}
private static void ReadFrom(ObjectReader reader, Project project, Document document, List<DiagnosticData> 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<DiagnosticDataLocation> ReadAdditionalLocations(Project project, ObjectReader reader)
{
var count = reader.ReadInt32();
var result = new List<DiagnosticDataLocation>();
for (var i = 0; i < count; i++)
{
result.Add(ReadLocation(project, reader, documentOpt: null));
}
return result;
}
private static ImmutableDictionary<string, string> GetProperties(ObjectReader reader, int count)
{
if (count > 0)
{
var properties = ImmutableDictionary.CreateBuilder<string, string>();
for (var i = 0; i < count; i++)
{
properties.Add(reader.ReadString(), reader.ReadString());
}
return properties.ToImmutable();
}
return ImmutableDictionary<string, string>.Empty;
}
private static IReadOnlyList<string> GetCustomTags(ObjectReader reader, int count)
{
if (count > 0)
{
var tags = new List<string>(count);
for (var i = 0; i < count; i++)
{
tags.Add(reader.ReadString());
}
return new ReadOnlyCollection<string>(tags);
}
return SpecializedCollections.EmptyReadOnlyList<string>();
}
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;
}
}
}
// 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.EngineV2
{
internal partial class DiagnosticIncrementalAnalyzer
{
/// <summary>
/// state that is responsible to hold onto local diagnostics data regarding active/opened files (depends on host)
/// in memory.
/// </summary>
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<DocumentAnalysisData>("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;
}
}
}
}
}
// 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
{
/// <summary>
/// Simple data holder for local diagnostics for an analyzer
/// </summary>
private struct DocumentAnalysisData
{
public static readonly DocumentAnalysisData Empty = new DocumentAnalysisData(VersionStamp.Default, ImmutableArray<DiagnosticData>.Empty);
/// <summary>
/// Version of the Items
/// </summary>
public readonly VersionStamp Version;
/// <summary>
/// Current data that matches the version
/// </summary>
public readonly ImmutableArray<DiagnosticData> Items;
/// <summary>
/// When present, This hold onto last data we broadcast to outer world
/// </summary>
public readonly ImmutableArray<DiagnosticData> OldItems;
public DocumentAnalysisData(VersionStamp version, ImmutableArray<DiagnosticData> items)
{
this.Version = version;
this.Items = items;
}
public DocumentAnalysisData(VersionStamp version, ImmutableArray<DiagnosticData> oldItems, ImmutableArray<DiagnosticData> newItems) :
this(version, newItems)
{
this.OldItems = oldItems;
}
public DocumentAnalysisData ToPersistData()
{
return new DocumentAnalysisData(Version, Items);
}
public bool FromCache
{
get { return this.OldItems.IsDefault; }
}
}
/// <summary>
/// Data holder for all diagnostics for a project for an analyzer
/// </summary>
private struct ProjectAnalysisData
{
/// <summary>
/// ProjectId of this data
/// </summary>
public readonly ProjectId ProjectId;
/// <summary>
/// Version of the Items
/// </summary>
public readonly VersionStamp Version;
/// <summary>
/// Current data that matches the version
/// </summary>
public readonly ImmutableDictionary<DiagnosticAnalyzer, AnalysisResult> Result;
/// <summary>
/// When present, This hold onto last data we broadcast to outer world
/// </summary>
public readonly ImmutableDictionary<DiagnosticAnalyzer, AnalysisResult> OldResult;
public ProjectAnalysisData(ProjectId projectId, VersionStamp version, ImmutableDictionary<DiagnosticAnalyzer, AnalysisResult> result)
{
ProjectId = projectId;
Version = version;
Result = result;
OldResult = null;
}
public ProjectAnalysisData(
ProjectId projectId,
VersionStamp version,
ImmutableDictionary<DiagnosticAnalyzer, AnalysisResult> oldResult,
ImmutableDictionary<DiagnosticAnalyzer, AnalysisResult> newResult) :
this(projectId, version, newResult)
{
this.OldResult = oldResult;
}
public AnalysisResult GetResult(DiagnosticAnalyzer analyzer)
{
return GetResultOrEmpty(Result, analyzer, ProjectId, Version);
}
public bool FromCache
{
get { return this.OldResult == null; }
}
public static async Task<ProjectAnalysisData> CreateAsync(Project project, IEnumerable<StateSet> stateSets, bool avoidLoadingData, CancellationToken cancellationToken)
{
VersionStamp? version = null;
var builder = ImmutableDictionary.CreateBuilder<DiagnosticAnalyzer, AnalysisResult>();
foreach (var stateSet in stateSets)
{
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)
{
if (result.Version != VersionStamp.Default)
{
version = result.Version;
}
}
else
{
// 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);
}
if (!version.HasValue)
{
// there is no saved data to return.
return new ProjectAnalysisData(project.Id, VersionStamp.Default, ImmutableDictionary<DiagnosticAnalyzer, AnalysisResult>.Empty);
}
return new ProjectAnalysisData(project.Id, version.Value, builder.ToImmutable());
}
}
}
}
\ No newline at end of file
// 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
{
/// <summary>
/// enum for each analysis kind.
/// </summary>
private enum AnalysisKind
{
Syntax,
Semantic,
NonLocal
}
}
}
// 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
{
/// <summary>
/// This cache CompilationWithAnalyzer for active/open files.
/// This will aggressively let go cached compilationWithAnalyzers to not hold them into memory too long.
/// </summary>
private class CompilationManager
{
private readonly DiagnosticIncrementalAnalyzer _owner;
private ConditionalWeakTable<Project, CompilationWithAnalyzers> _map;
public CompilationManager(DiagnosticIncrementalAnalyzer owner)
{
_owner = owner;
_map = new ConditionalWeakTable<Project, CompilationWithAnalyzers>();
}
/// <summary>
/// Return CompilationWithAnalyzer for given project with given stateSets
/// </summary>
public async Task<CompilationWithAnalyzers> GetAnalyzerDriverAsync(Project project, IEnumerable<StateSet> stateSets, CancellationToken cancellationToken)
{
if (!project.SupportsCompilation)
{
return null;
}
CompilationWithAnalyzers analyzerDriverOpt;
if (_map.TryGetValue(project, out analyzerDriverOpt))
{
// we have cached one, return that.
AssertAnalyzers(analyzerDriverOpt, stateSets);
return analyzerDriverOpt;
}
// Create driver that holds onto compilation and associated analyzers
var includeSuppressedDiagnostics = true;
var newAnalyzerDriverOpt = await CreateAnalyzerDriverAsync(project, stateSets, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false);
// Add new analyzer driver to the map
analyzerDriverOpt = _map.GetValue(project, _ => newAnalyzerDriverOpt);
// if somebody has beat us, make sure analyzers are good.
if (analyzerDriverOpt != newAnalyzerDriverOpt)
{
AssertAnalyzers(analyzerDriverOpt, stateSets);
}
// return driver
return analyzerDriverOpt;
}
public Task<CompilationWithAnalyzers> CreateAnalyzerDriverAsync(Project project, IEnumerable<StateSet> stateSets, bool includeSuppressedDiagnostics, CancellationToken cancellationToken)
{
var analyzers = stateSets.Select(s => s.Analyzer).ToImmutableArrayOrEmpty();
return CreateAnalyzerDriverAsync(project, analyzers, includeSuppressedDiagnostics, cancellationToken);
}
public async Task<CompilationWithAnalyzers> CreateAnalyzerDriverAsync(
Project project, ImmutableArray<DiagnosticAnalyzer> analyzers, bool includeSuppressedDiagnostics, CancellationToken cancellationToken)
{
if (!project.SupportsCompilation)
{
return null;
}
var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
// Create driver that holds onto compilation and associated analyzers
return CreateAnalyzerDriver(
project, compilation, analyzers, logAnalyzerExecutionTime: false, reportSuppressedDiagnostics: includeSuppressedDiagnostics);
}
private CompilationWithAnalyzers CreateAnalyzerDriver(
Project project,
Compilation compilation,
ImmutableArray<DiagnosticAnalyzer> analyzers,
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);
var analysisOptions = GetAnalyzerOptions(project, logAnalyzerExecutionTime, reportSuppressedDiagnostics);
// Create driver that holds onto compilation and associated analyzers
return compilation.WithAnalyzers(analyzers, analysisOptions);
}
private CompilationWithAnalyzersOptions GetAnalyzerOptions(
Project project,
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: false,
logAnalyzerExecutionTime: logAnalyzerExecutionTime,
reportSuppressedDiagnostics: reportSuppressedDiagnostics);
}
private Func<Exception, bool> 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<Exception, DiagnosticAnalyzer, Diagnostic> 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<Project, CompilationWithAnalyzers>();
}
[Conditional("DEBUG")]
private void AssertAnalyzers(CompilationWithAnalyzers analyzerDriver, IEnumerable<StateSet> 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)));
}
[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
}
}
}
// 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<DiagnosticAnalyzer, ConcurrentDictionary<object, CacheEntry>> s_map =
new ConcurrentDictionary<DiagnosticAnalyzer, ConcurrentDictionary<object, CacheEntry>>(concurrencyLevel: 2, capacity: 10);
public static bool TryGetValue(DiagnosticAnalyzer analyzer, object key, out CacheEntry entry)
{
AssertKey(key);
entry = default(CacheEntry);
ConcurrentDictionary<object, CacheEntry> 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<object, CacheEntry>(concurrencyLevel: 2, capacity: 10));
analyzerMap[key] = entry;
}
public static void Remove(DiagnosticAnalyzer analyzer, object key)
{
AssertKey(key);
// remove the entry
ConcurrentDictionary<object, CacheEntry> 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<object, CacheEntry> analyzerMap;
s_map.TryRemove(analyzer, out analyzerMap);
}
// make sure key is either documentId or projectId
private static void AssertKey(object key)
{
var tuple = (ValueTuple<object, string>)key;
Contract.ThrowIfFalse(tuple.Item1 is DocumentId || tuple.Item1 is ProjectId);
}
}
// in memory cache entry
private struct CacheEntry
{
public readonly VersionStamp Version;
public readonly ImmutableArray<DiagnosticData> Diagnostics;
public CacheEntry(VersionStamp version, ImmutableArray<DiagnosticData> diagnostics)
{
Version = version;
Diagnostics = diagnostics;
}
}
}
}
// 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
{
/// <summary>
/// EventArgs for <see cref="StateManager.ProjectAnalyzerReferenceChanged"/>
///
/// this event args contains information such as <see cref="Project"/> the <see cref="AnalyzerReference"/> has changed
/// and what <see cref="StateSet"/> has changed.
/// </summary>
private class ProjectAnalyzerReferenceChangedEventArgs : EventArgs
{
public readonly Project Project;
public readonly ImmutableArray<StateSet> Added;
public readonly ImmutableArray<StateSet> Removed;
public ProjectAnalyzerReferenceChangedEventArgs(Project project, ImmutableArray<StateSet> added, ImmutableArray<StateSet> removed)
{
Project = project;
Added = added;
Removed = removed;
}
}
}
}
// 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.Threading;
using System.Threading.Tasks;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2
{
internal partial class DiagnosticIncrementalAnalyzer
{
/// <summary>
/// State for diagnostics that belong to a project at given time.
/// </summary>
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, documentIds: null, isEmpty: true);
}
public ImmutableHashSet<DocumentId> GetDocumentsWithDiagnostics()
{
return _lastResult.DocumentIdsOrEmpty;
}
public bool IsEmpty()
{
return _lastResult.IsEmpty;
}
public bool IsEmpty(DocumentId documentId)
{
return IsEmpty(_lastResult, documentId);
}
/// <summary>
/// Return all diagnostics for the given project stored in this state
/// </summary>
public async Task<AnalysisResult> GetAnalysisDataAsync(Project project, bool avoidLoadingData, CancellationToken cancellationToken)
{
// make a copy of last result.
var lastResult = _lastResult;
Contract.ThrowIfFalse(lastResult.ProjectId == project.Id);
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 strictly perf optimization.
var version = await GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false);
if (avoidLoadingData && lastResult.Version != version)
{
return lastResult;
}
// if given project 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, lastResult.Version);
var builder = new Builder(project.Id, lastResult.Version, lastResult.DocumentIds);
foreach (var documentId in lastResult.DocumentIds)
{
cancellationToken.ThrowIfCancellationRequested();
var document = project.GetDocument(documentId);
if (document == null)
{
continue;
}
if (!await TryDeserializeDocumentAsync(serializer, document, builder, cancellationToken).ConfigureAwait(false))
{
Contract.Requires(false, "How this can happen?");
continue;
}
}
if (!await TryDeserializeAsync(serializer, project, project.Id, _owner.NonLocalStateName, builder.AddOthers, cancellationToken).ConfigureAwait(false))
{
Contract.Requires(false, "How this can happen?");
}
return builder.ToResult();
}
/// <summary>
/// Return all diagnostics for the given document stored in this state including non local diagnostics for this document
/// </summary>
public async Task<AnalysisResult> GetAnalysisDataAsync(Document document, bool avoidLoadingData, CancellationToken cancellationToken)
{
// make a copy of last result.
var lastResult = _lastResult;
Contract.ThrowIfFalse(lastResult.ProjectId == document.Project.Id);
if (lastResult.IsDefault)
{
return await LoadInitialAnalysisDataAsync(document, cancellationToken).ConfigureAwait(false);
}
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, lastResult.Version);
var builder = new Builder(document.Project.Id, lastResult.Version);
if (!await TryDeserializeDocumentAsync(serializer, document, builder, cancellationToken).ConfigureAwait(false))
{
Contract.Requires(false, "How this can happen?");
}
return builder.ToResult();
}
/// <summary>
/// Return all no location diagnostics for the given project stored in this state
/// </summary>
public async Task<AnalysisResult> GetProjectAnalysisDataAsync(Project project, bool avoidLoadingData, CancellationToken cancellationToken)
{
// make a copy of last result.
var lastResult = _lastResult;
Contract.ThrowIfFalse(lastResult.ProjectId == project.Id);
if (lastResult.IsDefault)
{
return await LoadInitialProjectAnalysisDataAsync(project, cancellationToken).ConfigureAwait(false);
}
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, lastResult.Version);
var builder = new Builder(project.Id, lastResult.Version);
if (!await TryDeserializeAsync(serializer, project, project.Id, _owner.NonLocalStateName, builder.AddOthers, cancellationToken).ConfigureAwait(false))
{
Contract.Requires(false, "How this can happen?");
}
return builder.ToResult();
}
public async Task SaveAsync(Project project, AnalysisResult result)
{
Contract.ThrowIfTrue(result.IsAggregatedForm);
RemoveInMemoryCache(_lastResult);
// 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)
{
RemoveInMemoryCacheEntries(id);
return !IsEmpty(id);
}
public bool OnProjectRemoved(ProjectId id)
{
RemoveInMemoryCacheEntry(id, _owner.NonLocalStateName);
return !IsEmpty();
}
private async Task<AnalysisResult> 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)
{
cancellationToken.ThrowIfCancellationRequested();
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<DocumentId>.Empty, isEmpty: true);
}
return builder.ToResult();
}
private async Task<AnalysisResult> 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<DocumentId>.Empty, isEmpty: true);
}
return builder.ToResult();
}
private async Task<AnalysisResult> 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<DocumentId>.Empty, isEmpty: true);
}
return builder.ToResult();
}
private async Task SerializeAsync(DiagnosticDataSerializer serializer, object documentOrProject, object key, string stateKey, ImmutableArray<DiagnosticData> 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, stateKey);
return;
}
// if serialization fail, hold it in the memory
InMemoryStorage.Cache(_owner.Analyzer, ValueTuple.Create(key, stateKey), new CacheEntry(serializer.Version, diagnostics));
}
private async Task<bool> TryDeserializeDocumentAsync(DiagnosticDataSerializer serializer, Document document, Builder builder, CancellationToken cancellationToken)
{
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<bool> TryDeserializeAsync<T>(
DiagnosticDataSerializer serializer,
object documentOrProject, T key, string stateKey,
Action<T, ImmutableArray<DiagnosticData>> 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<ImmutableArray<DiagnosticData>> DeserializeAsync(DiagnosticDataSerializer serializer, object documentOrProject, object key, string stateKey, CancellationToken cancellationToken)
{
// check cache first
CacheEntry entry;
if (InMemoryStorage.TryGetValue(_owner.Analyzer, ValueTuple.Create(key, stateKey), out entry) && serializer.Version == entry.Version)
{
return entry.Diagnostics;
}
// try to deserialize it
return await serializer.DeserializeAsync(documentOrProject, stateKey, cancellationToken).ConfigureAwait(false);
}
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, ValueTuple.Create(key, stateKey));
}
private bool IsEmpty(AnalysisResult result, DocumentId documentId)
{
return !result.DocumentIdsOrEmpty.Contains(documentId);
}
// we have this builder to avoid allocating collections unnecessarily.
private class Builder
{
private readonly ProjectId _projectId;
private readonly VersionStamp _version;
private readonly ImmutableHashSet<DocumentId> _documentIds;
private ImmutableDictionary<DocumentId, ImmutableArray<DiagnosticData>>.Builder _syntaxLocals;
private ImmutableDictionary<DocumentId, ImmutableArray<DiagnosticData>>.Builder _semanticLocals;
private ImmutableDictionary<DocumentId, ImmutableArray<DiagnosticData>>.Builder _nonLocals;
private ImmutableArray<DiagnosticData> _others;
public Builder(ProjectId projectId, VersionStamp version, ImmutableHashSet<DocumentId> documentIds = null)
{
_projectId = projectId;
_version = version;
_documentIds = documentIds;
}
public void AddSyntaxLocals(DocumentId documentId, ImmutableArray<DiagnosticData> diagnostics)
{
Add(ref _syntaxLocals, documentId, diagnostics);
}
public void AddSemanticLocals(DocumentId documentId, ImmutableArray<DiagnosticData> diagnostics)
{
Add(ref _semanticLocals, documentId, diagnostics);
}
public void AddNonLocals(DocumentId documentId, ImmutableArray<DiagnosticData> diagnostics)
{
Add(ref _nonLocals, documentId, diagnostics);
}
public void AddOthers(ProjectId unused, ImmutableArray<DiagnosticData> diagnostics)
{
_others = diagnostics;
}
private void Add(ref ImmutableDictionary<DocumentId, ImmutableArray<DiagnosticData>>.Builder locals, DocumentId documentId, ImmutableArray<DiagnosticData> diagnostics)
{
locals = locals ?? ImmutableDictionary.CreateBuilder<DocumentId, ImmutableArray<DiagnosticData>>();
locals.Add(documentId, diagnostics);
}
public AnalysisResult ToResult()
{
return new AnalysisResult(_projectId, _version,
_syntaxLocals?.ToImmutable() ?? ImmutableDictionary<DocumentId, ImmutableArray<DiagnosticData>>.Empty,
_semanticLocals?.ToImmutable() ?? ImmutableDictionary<DocumentId, ImmutableArray<DiagnosticData>>.Empty,
_nonLocals?.ToImmutable() ?? ImmutableDictionary<DocumentId, ImmutableArray<DiagnosticData>>.Empty,
_others.IsDefault ? ImmutableArray<DiagnosticData>.Empty : _others,
_documentIds);
}
}
}
}
}
// 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
{
/// <summary>
/// This class is responsible for anything related to <see cref="StateSet"/> for host level <see cref="DiagnosticAnalyzer"/>s.
/// </summary>
private class HostStates
{
private readonly StateManager _owner;
private ImmutableDictionary<string, DiagnosticAnalyzerMap> _stateMap;
public HostStates(StateManager owner)
{
_owner = owner;
_stateMap = ImmutableDictionary<string, DiagnosticAnalyzerMap>.Empty;
}
public IEnumerable<StateSet> GetStateSets()
{
return _stateMap.Values.SelectMany(v => v.GetStateSets());
}
public IEnumerable<StateSet> GetOrCreateStateSets(string language)
{
return GetAnalyzerMap(language).GetStateSets();
}
public IEnumerable<DiagnosticAnalyzer> 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<DiagnosticAnalyzer, StateSet> _map;
public DiagnosticAnalyzerMap(HostAnalyzerManager analyzerManager, string language, ImmutableDictionary<DiagnosticAnalyzer, StateSet> 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<DiagnosticAnalyzer> 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<StateSet> 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;
}
}
}
}
}
}
// 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
{
/// <summary>
/// This class is responsible for anything related to <see cref="StateSet"/> for project level <see cref="DiagnosticAnalyzer"/>s.
/// </summary>
private class ProjectStates
{
private readonly StateManager _owner;
private readonly ConcurrentDictionary<ProjectId, Entry> _stateMap;
public ProjectStates(StateManager owner)
{
_owner = owner;
_stateMap = new ConcurrentDictionary<ProjectId, Entry>(concurrencyLevel: 2, capacity: 10);
}
public IEnumerable<StateSet> GetStateSets(ProjectId projectId)
{
var map = GetCachedAnalyzerMap(projectId);
return map.Values;
}
public IEnumerable<DiagnosticAnalyzer> GetOrCreateAnalyzers(Project project)
{
var map = GetOrCreateAnalyzerMap(project);
return map.Keys;
}
public IEnumerable<StateSet> GetOrUpdateStateSets(Project project)
{
var map = GetOrUpdateAnalyzerMap(project);
return map.Values;
}
public IEnumerable<StateSet> 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<DiagnosticAnalyzer, StateSet> 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<DiagnosticAnalyzer, StateSet> GetCachedAnalyzerMap(ProjectId projectId)
{
Entry entry;
if (_stateMap.TryGetValue(projectId, out entry))
{
return entry.AnalyzerMap;
}
return ImmutableDictionary<DiagnosticAnalyzer, StateSet>.Empty;
}
private ImmutableDictionary<DiagnosticAnalyzer, StateSet> 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<DiagnosticAnalyzer, StateSet> GetAnalyzerMap(Project project)
{
Entry entry;
if (_stateMap.TryGetValue(project.Id, out entry) && entry.AnalyzerReferences.Equals(project.AnalyzerReferences))
{
return entry.AnalyzerMap;
}
return null;
}
private ImmutableDictionary<DiagnosticAnalyzer, StateSet> CreateAnalyzerMap(Project project)
{
if (project.AnalyzerReferences.Count == 0)
{
return ImmutableDictionary<DiagnosticAnalyzer, StateSet>.Empty;
}
var analyzersPerReference = _owner.AnalyzerManager.CreateProjectDiagnosticAnalyzersPerReference(project);
if (analyzersPerReference.Count == 0)
{
return ImmutableDictionary<DiagnosticAnalyzer, StateSet>.Empty;
}
return StateManager.CreateAnalyzerMap(_owner.AnalyzerManager, project.Language, analyzersPerReference.Values);
}
private void RaiseProjectAnalyzerReferenceChangedIfNeeded(
Project project,
ImmutableDictionary<object, ImmutableArray<DiagnosticAnalyzer>> newMapPerReference,
ImmutableDictionary<DiagnosticAnalyzer, StateSet> 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<StateSet>.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<StateSet> DiffStateSets(
IEnumerable<AnalyzerReference> references,
ImmutableDictionary<object, ImmutableArray<DiagnosticAnalyzer>> mapPerReference,
ImmutableDictionary<DiagnosticAnalyzer, StateSet> map)
{
if (mapPerReference.Count == 0 || map.Count == 0)
{
// nothing to diff
return ImmutableArray<StateSet>.Empty;
}
var builder = ImmutableArray.CreateBuilder<StateSet>();
foreach (var reference in references)
{
var referenceIdentity = _owner.AnalyzerManager.GetAnalyzerReferenceIdentity(reference);
// check duplication
ImmutableArray<DiagnosticAnalyzer> 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<StateSet> 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<AnalyzerReference> AnalyzerReferences;
public readonly ImmutableDictionary<object, ImmutableArray<DiagnosticAnalyzer>> MapPerReferences;
public readonly ImmutableDictionary<DiagnosticAnalyzer, StateSet> AnalyzerMap;
public Entry(
IReadOnlyList<AnalyzerReference> analyzerReferences,
ImmutableDictionary<object, ImmutableArray<DiagnosticAnalyzer>> mapPerReferences,
ImmutableDictionary<DiagnosticAnalyzer, StateSet> analyzerMap)
{
Contract.ThrowIfNull(analyzerReferences);
Contract.ThrowIfNull(mapPerReferences);
Contract.ThrowIfNull(analyzerMap);
AnalyzerReferences = analyzerReferences;
MapPerReferences = mapPerReferences;
AnalyzerMap = analyzerMap;
}
}
}
}
}
}
// 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";
/// <summary>
/// This is in charge of anything related to <see cref="StateSet"/>
/// </summary>
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; } }
/// <summary>
/// This will be raised whenever <see cref="StateManager"/> finds <see cref="Project.AnalyzerReferences"/> change
/// </summary>
public event EventHandler<ProjectAnalyzerReferenceChangedEventArgs> ProjectAnalyzerReferenceChanged;
/// <summary>
/// Return existing or new <see cref="DiagnosticAnalyzer"/>s for the given <see cref="Project"/>.
/// </summary>
public IEnumerable<DiagnosticAnalyzer> GetOrCreateAnalyzers(Project project)
{
return _hostStates.GetAnalyzers(project.Language).Concat(_projectStates.GetOrCreateAnalyzers(project));
}
/// <summary>
/// Return <see cref="StateSet"/>s for the given <see cref="ProjectId"/>.
/// This will never create new <see cref="StateSet"/> but will return ones already created.
/// </summary>
public IEnumerable<StateSet> GetStateSets(ProjectId projectId)
{
return _hostStates.GetStateSets().Concat(_projectStates.GetStateSets(projectId));
}
/// <summary>
/// Return <see cref="StateSet"/>s for the given <see cref="Project"/>.
/// This will never create new <see cref="StateSet"/> but will return ones already created.
/// Difference with <see cref="GetStateSets(ProjectId)"/> is that
/// this will only return <see cref="StateSet"/>s that have same language as <paramref name="project"/>.
/// </summary>
public IEnumerable<StateSet> GetStateSets(Project project)
{
return GetStateSets(project.Id).Where(s => s.Language == project.Language);
}
/// <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
/// It will create new <see cref="StateSet"/>s for the <see cref="Project"/> and update internal state.
///
/// since this has a side-effect, this should never be called concurrently. and incremental analyzer (solution crawler) should guarantee that.
/// </summary>
public IEnumerable<StateSet> GetOrUpdateStateSets(Project project)
{
return _hostStates.GetOrCreateStateSets(project.Language).Concat(_projectStates.GetOrUpdateStateSets(project));
}
/// <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
/// It will create new <see cref="StateSet"/>s for the <see cref="Project"/>.
/// Unlike <see cref="GetOrUpdateStateSets(Project)"/>, this has no side effect.
/// </summary>
public IEnumerable<StateSet> GetOrCreateStateSets(Project project)
{
return _hostStates.GetOrCreateStateSets(project.Language).Concat(_projectStates.GetOrCreateStateSets(project));
}
/// <summary>
/// Return <see cref="StateSet"/> for the given <see cref="DiagnosticAnalyzer"/> in the context of <see cref="Project"/>.
/// This will either return already created <see cref="StateSet"/> for the specific snapshot of <see cref="Project"/> or
/// It will create new <see cref="StateSet"/> for the <see cref="Project"/>.
/// This will not have any side effect.
/// </summary>
public StateSet GetOrCreateStateSet(Project project, DiagnosticAnalyzer analyzer)
{
var stateSet = _hostStates.GetOrCreateStateSet(project.Language, analyzer);
if (stateSet != null)
{
return stateSet;
}
return _projectStates.GetOrCreateStateSet(project, analyzer);
}
/// <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> 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();
}
public bool OnDocumentReset(IEnumerable<StateSet> stateSets, DocumentId documentId)
{
var removed = false;
foreach (var stateSet in stateSets)
{
removed |= stateSet.OnDocumentReset(documentId);
}
return removed;
}
public bool OnDocumentClosed(IEnumerable<StateSet> stateSets, DocumentId documentId)
{
var removed = false;
foreach (var stateSet in stateSets)
{
removed |= stateSet.OnDocumentClosed(documentId);
}
return removed;
}
public bool OnDocumentRemoved(IEnumerable<StateSet> stateSets, DocumentId documentId)
{
var removed = false;
foreach (var stateSet in stateSets)
{
removed |= stateSet.OnDocumentRemoved(documentId);
}
return removed;
}
public bool OnProjectRemoved(IEnumerable<StateSet> 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<DiagnosticAnalyzer, StateSet> CreateAnalyzerMap(
HostAnalyzerManager analyzerManager, string language, IEnumerable<ImmutableArray<DiagnosticAnalyzer>> analyzerCollection)
{
var compilerAnalyzer = analyzerManager.GetCompilerDiagnosticAnalyzer(language);
var builder = ImmutableDictionary.CreateBuilder<DiagnosticAnalyzer, StateSet>();
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<StateSet> stateSets)
{
// Ensure diagnostic state name is indeed unique.
var set = new HashSet<ValueTuple<string, string>>();
foreach (var stateSet in stateSets)
{
if (!(set.Add(ValueTuple.Create(stateSet.Language, stateSet.StateName))))
{
Contract.Fail();
}
}
}
}
}
}
// 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
{
/// <summary>
/// this contains all states regarding a <see cref="DiagnosticAnalyzer"/>
/// </summary>
private class StateSet
{
private const string UserDiagnosticsPrefixTableName = "<UserDiagnostics2>";
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<DocumentId, ActiveFileState> _activeFileStates;
private readonly ConcurrentDictionary<ProjectId, ProjectState> _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<DocumentId, ActiveFileState>(concurrencyLevel: 2, capacity: 10);
_projectStates = new ConcurrentDictionary<ProjectId, ProjectState>(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<DocumentId> GetDocumentsWithDiagnostics(ProjectId projectId)
{
HashSet<DocumentId> set = null;
foreach (var state in GetActiveFileStates(projectId))
{
set = set ?? new HashSet<DocumentId>();
set.Add(state.DocumentId);
}
ProjectState projectState;
if (!_projectStates.TryGetValue(projectId, out projectState) || projectState.IsEmpty())
{
return set ?? SpecializedCollections.EmptyEnumerable<DocumentId>();
}
set = set ?? new HashSet<DocumentId>();
set.UnionWith(projectState.GetDocumentsWithDiagnostics());
return set;
}
private IEnumerable<ActiveFileState> 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);
}
/// <summary>
/// Get the unique state name for the given analyzer.
/// Note that this name is used by the underlying persistence stream of the corresponding <see cref="ProjectState"/> 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.
/// </summary>
private static ValueTuple<string, VersionStamp> 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);
}
}
}
}
// 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.Shared.Extensions;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2
{
internal class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncrementalAnalyzer
/// <summary>
/// Diagnostic Analyzer Engine V2
///
/// This one follows pattern compiler has set for diagnostic analyzer.
/// </summary>
internal partial class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncrementalAnalyzer
{
private readonly int _correlationId;
public DiagnosticIncrementalAnalyzer(DiagnosticAnalyzerService owner, int correlationId, Workspace workspace, HostAnalyzerManager hostAnalyzerManager, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource)
private readonly StateManager _stateManager;
private readonly Executor _executor;
private readonly CompilationManager _compilationManager;
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;
_stateManager = new StateManager(hostAnalyzerManager);
_stateManager.ProjectAnalyzerReferenceChanged += OnProjectAnalyzerReferenceChanged;
_executor = new Executor(this);
_compilationManager = new CompilationManager(this);
}
public override async Task AnalyzeProjectAsync(Project project, bool semanticsChanged, CancellationToken cancellationToken)
public override bool ContainsDiagnostics(Workspace workspace, ProjectId projectId)
{
var diagnostics = await GetDiagnosticsAsync(project.Solution, project.Id, null, includeSuppressedDiagnostics: true, cancellationToken: cancellationToken).ConfigureAwait(false);
foreach (var stateSet in _stateManager.GetStateSets(projectId))
{
if (stateSet.ContainsAnyDocumentOrProjectDiagnostics(projectId))
{
return true;
}
}
RaiseEvents(project, diagnostics);
return false;
}
public override Task AnalyzeSyntaxAsync(Document document, CancellationToken cancellationToken)
private bool SupportAnalysisKind(DiagnosticAnalyzer analyzer, string language, AnalysisKind kind)
{
return SpecializedTasks.EmptyTask;
}
// compiler diagnostic analyzer always support all kinds
if (HostAnalyzerManager.IsCompilerDiagnosticAnalyzer(language, analyzer))
{
return true;
}
public override Task DocumentOpenAsync(Document document, CancellationToken cancellationToken)
{
return SpecializedTasks.EmptyTask;
switch (kind)
{
case AnalysisKind.Syntax:
return analyzer.SupportsSyntaxDiagnosticAnalysis();
case AnalysisKind.Semantic:
return analyzer.SupportsSemanticDiagnosticAnalysis();
default:
return Contract.FailWithReturn<bool>("shouldn't reach here");
}
}
public override Task DocumentCloseAsync(Document document, CancellationToken cancellationToken)
private void OnProjectAnalyzerReferenceChanged(object sender, ProjectAnalyzerReferenceChangedEventArgs e)
{
return SpecializedTasks.EmptyTask;
}
if (e.Removed.Length == 0)
{
// nothing to refresh
return;
}
public override Task DocumentResetAsync(Document document, CancellationToken cancellationToken)
{
return SpecializedTasks.EmptyTask;
}
// events will be automatically serialized.
var project = e.Project;
var stateSets = e.Removed;
public override Task NewSolutionSnapshotAsync(Solution solution, CancellationToken cancellationToken)
{
return SpecializedTasks.EmptyTask;
}
// make sure we drop cache related to the analyzers
foreach (var stateSet in stateSets)
{
stateSet.OnRemoved();
}
public override void RemoveDocument(DocumentId documentId)
{
Owner.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs.DiagnosticsRemoved(
ValueTuple.Create(this, documentId), Workspace, null, null, null));
ClearAllDiagnostics(stateSets, project.Id);
}
public override void RemoveProject(ProjectId projectId)
private void ClearAllDiagnostics(ImmutableArray<StateSet> stateSets, ProjectId projectId)
{
Owner.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs.DiagnosticsRemoved(
ValueTuple.Create(this, projectId), Workspace, null, null, null));
}
#endregion
Owner.RaiseBulkDiagnosticsUpdated(raiseEvents =>
{
var handleActiveFile = true;
foreach (var stateSet in stateSets)
{
// PERF: don't fire events for ones that we dont have any diagnostics on
if (!stateSet.ContainsAnyDocumentOrProjectDiagnostics(projectId))
{
continue;
}
public override Task<ImmutableArray<DiagnosticData>> GetCachedDiagnosticsAsync(Solution solution, ProjectId projectId = null, DocumentId documentId = null, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken))
{
return GetDiagnosticsAsync(solution, projectId, documentId, includeSuppressedDiagnostics, cancellationToken);
RaiseProjectDiagnosticsRemoved(stateSet, projectId, stateSet.GetDocumentsWithDiagnostics(projectId), handleActiveFile, raiseEvents);
}
});
}
public override Task<ImmutableArray<DiagnosticData>> GetSpecificCachedDiagnosticsAsync(Solution solution, object id, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken))
private void RaiseDiagnosticsCreated(
Project project, StateSet stateSet, ImmutableArray<DiagnosticData> items, Action<DiagnosticsUpdatedArgs> raiseEvents)
{
return GetSpecificDiagnosticsAsync(solution, id, includeSuppressedDiagnostics, cancellationToken);
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));
}
public override async Task<ImmutableArray<DiagnosticData>> GetDiagnosticsAsync(Solution solution, ProjectId projectId = null, DocumentId documentId = null, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken))
private void RaiseDiagnosticsRemoved(
ProjectId projectId, Solution solution, StateSet stateSet, Action<DiagnosticsUpdatedArgs> raiseEvents)
{
if (documentId != null)
{
var diagnostics = await GetProjectDiagnosticsAsync(solution.GetProject(projectId), includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false);
return diagnostics.Where(d => d.DocumentId == documentId).ToImmutableArrayOrEmpty();
}
Contract.ThrowIfFalse(solution == null || solution.Workspace == Workspace);
if (projectId != null)
{
return await GetProjectDiagnosticsAsync(solution.GetProject(projectId), includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false);
}
raiseEvents(DiagnosticsUpdatedArgs.DiagnosticsRemoved(
CreateId(stateSet.Analyzer, projectId, AnalysisKind.NonLocal, stateSet.ErrorSourceName),
Workspace,
solution,
projectId,
documentId: null));
}
var builder = ImmutableArray.CreateBuilder<DiagnosticData>();
foreach (var project in solution.Projects)
{
builder.AddRange(await GetProjectDiagnosticsAsync(project, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false));
}
private void RaiseDiagnosticsCreated(
Document document, StateSet stateSet, AnalysisKind kind, ImmutableArray<DiagnosticData> items, Action<DiagnosticsUpdatedArgs> raiseEvents)
{
Contract.ThrowIfFalse(document.Project.Solution.Workspace == Workspace);
return builder.ToImmutable();
raiseEvents(DiagnosticsUpdatedArgs.DiagnosticsCreated(
CreateId(stateSet.Analyzer, document.Id, kind, stateSet.ErrorSourceName),
document.Project.Solution.Workspace,
document.Project.Solution,
document.Project.Id,
document.Id,
items));
}
public override async Task<ImmutableArray<DiagnosticData>> GetSpecificDiagnosticsAsync(Solution solution, object id, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken))
private void RaiseDiagnosticsRemoved(
DocumentId documentId, Solution solution, StateSet stateSet, AnalysisKind kind, Action<DiagnosticsUpdatedArgs> raiseEvents)
{
if (id is ValueTuple<DiagnosticIncrementalAnalyzer, DocumentId>)
{
var key = (ValueTuple<DiagnosticIncrementalAnalyzer, DocumentId>)id;
return await GetDiagnosticsAsync(solution, key.Item2.ProjectId, key.Item2, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false);
}
if (id is ValueTuple<DiagnosticIncrementalAnalyzer, ProjectId>)
{
var key = (ValueTuple<DiagnosticIncrementalAnalyzer, ProjectId>)id;
var diagnostics = await GetDiagnosticsAsync(solution, key.Item2, null, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false);
return diagnostics.Where(d => d.DocumentId == null).ToImmutableArray();
}
Contract.ThrowIfFalse(solution == null || solution.Workspace == Workspace);
return ImmutableArray<DiagnosticData>.Empty;
raiseEvents(DiagnosticsUpdatedArgs.DiagnosticsRemoved(
CreateId(stateSet.Analyzer, documentId, kind, stateSet.ErrorSourceName),
Workspace,
solution,
documentId.ProjectId,
documentId));
}
public override async Task<ImmutableArray<DiagnosticData>> GetDiagnosticsForIdsAsync(Solution solution, ProjectId projectId = null, DocumentId documentId = null, ImmutableHashSet<string> diagnosticIds = null, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken))
private object CreateId(DiagnosticAnalyzer analyzer, DocumentId key, AnalysisKind kind, string errorSourceName)
{
var diagnostics = await GetDiagnosticsAsync(solution, projectId, documentId, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false);
return diagnostics.Where(d => diagnosticIds.Contains(d.Id)).ToImmutableArrayOrEmpty();
return CreateIdInternal(analyzer, key, kind, errorSourceName);
}
public override async Task<ImmutableArray<DiagnosticData>> GetProjectDiagnosticsForIdsAsync(Solution solution, ProjectId projectId = null, ImmutableHashSet<string> diagnosticIds = null, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken))
private object CreateId(DiagnosticAnalyzer analyzer, ProjectId key, AnalysisKind kind, string errorSourceName)
{
var diagnostics = await GetDiagnosticsForIdsAsync(solution, projectId, null, diagnosticIds, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false);
return diagnostics.Where(d => d.DocumentId == null).ToImmutableArray();
return CreateIdInternal(analyzer, key, kind, errorSourceName);
}
public override async Task<bool> TryAppendDiagnosticsForSpanAsync(Document document, TextSpan range, List<DiagnosticData> result, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken))
private static object CreateIdInternal(DiagnosticAnalyzer analyzer, object key, AnalysisKind kind, string errorSourceName)
{
result.AddRange(await GetDiagnosticsForSpanAsync(document, range, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false));
return true;
return new LiveDiagnosticUpdateArgsId(analyzer, key, (int)kind, errorSourceName);
}
public override async Task<IEnumerable<DiagnosticData>> GetDiagnosticsForSpanAsync(Document document, TextSpan range, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken))
public static Task<VersionStamp> GetDiagnosticVersionAsync(Project project, CancellationToken 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));
return project.GetDependentVersionAsync(cancellationToken);
}
private async Task<ImmutableArray<DiagnosticData>> GetProjectDiagnosticsAsync(Project project, bool includeSuppressedDiagnostics, CancellationToken cancellationToken)
private static AnalysisResult GetResultOrEmpty(ImmutableDictionary<DiagnosticAnalyzer, AnalysisResult> map, DiagnosticAnalyzer analyzer, ProjectId projectId, VersionStamp version)
{
if (project == null)
AnalysisResult result;
if (map.TryGetValue(analyzer, out result))
{
return ImmutableArray<DiagnosticData>.Empty;
return result;
}
var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
var analyzers = HostAnalyzerManager.CreateDiagnosticAnalyzers(project);
var compilationWithAnalyzer = compilation.WithAnalyzers(analyzers, project.AnalyzerOptions, cancellationToken);
// REVIEW: this API is a bit strange.
// if getting diagnostic is cancelled, it has to create new compilation and do everything from scratch again?
var dxs = GetDiagnosticData(project, await compilationWithAnalyzer.GetAnalyzerDiagnosticsAsync().ConfigureAwait(false)).ToImmutableArrayOrEmpty();
return dxs;
return new AnalysisResult(projectId, version);
}
private IEnumerable<DiagnosticData> GetDiagnosticData(Project project, ImmutableArray<Diagnostic> diagnostics)
private static ImmutableArray<DiagnosticData> GetResult(AnalysisResult result, AnalysisKind kind, DocumentId id)
{
foreach (var diagnostic in diagnostics)
if (result.IsEmpty || !result.DocumentIds.Contains(id) || result.IsAggregatedForm)
{
if (diagnostic.Location == Location.None)
{
yield return DiagnosticData.Create(project, diagnostic);
continue;
}
var document = project.GetDocument(diagnostic.Location.SourceTree);
if (document == null)
{
continue;
}
yield return DiagnosticData.Create(document, diagnostic);
return ImmutableArray<DiagnosticData>.Empty;
}
}
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
// are already taken cared by engine
return SpecializedTasks.EmptyTask;
}
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
// are already taken cared by engine
return SpecializedTasks.EmptyTask;
}
private void RaiseEvents(Project project, ImmutableArray<DiagnosticData> diagnostics)
{
var groups = diagnostics.GroupBy(d => d.DocumentId);
var solution = project.Solution;
var workspace = solution.Workspace;
foreach (var kv in groups)
switch (kind)
{
if (kv.Key == null)
{
Owner.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs.DiagnosticsCreated(
ValueTuple.Create(this, project.Id), workspace, solution, project.Id, null, kv.ToImmutableArrayOrEmpty()));
continue;
}
Owner.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs.DiagnosticsCreated(
ValueTuple.Create(this, kv.Key), workspace, solution, project.Id, kv.Key, kv.ToImmutableArrayOrEmpty()));
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<ImmutableArray<DiagnosticData>>("shouldn't reach here");
}
}
public override bool ContainsDiagnostics(Workspace workspace, ProjectId projectId)
{
// for now, it always return false;
return false;
}
}
}
// 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;
namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2
{
internal partial class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncrementalAnalyzer
{
public override async Task SynchronizeWithBuildAsync(Workspace workspace, ImmutableDictionary<ProjectId, ImmutableArray<DiagnosticData>> map)
{
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<ProjectAnalysisData> CreateProjectAnalysisDataAsync(Project project, ImmutableArray<StateSet> stateSets, ImmutableArray<DiagnosticData> 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(project.Id, VersionStamp.Default, oldAnalysisData.Result, newResult);
}
private ImmutableDictionary<DiagnosticAnalyzer, AnalysisResult> CreateAnalysisResults(
Project project, ImmutableArray<StateSet> stateSets, ProjectAnalysisData oldAnalysisData, ImmutableArray<DiagnosticData> diagnostics)
{
using (var poolObject = SharedPools.Default<HashSet<string>>().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<DiagnosticAnalyzer, AnalysisResult>();
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<DocumentId, ImmutableArray<DiagnosticData>>.Empty,
semanticLocals: group.Where(g => g.Key != null).ToImmutableDictionary(g => g.Key, g => g.ToImmutableArray()),
nonLocals: ImmutableDictionary<DocumentId, ImmutableArray<DiagnosticData>>.Empty,
others: group.Where(g => g.Key == null).SelectMany(g => g).ToImmutableArrayOrEmpty());
builder.Add(stateSet.Analyzer, result);
}
return builder.ToImmutable();
}
}
private ImmutableArray<DiagnosticData> GetDiagnostics(AnalysisResult result)
{
// PERF: don't allocation anything if not needed
if (result.IsAggregatedForm || result.IsEmpty)
{
return ImmutableArray<DiagnosticData>.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<DiagnosticData> MergeDiagnostics(ImmutableArray<DiagnosticData> newDiagnostics, ImmutableArray<DiagnosticData> existingDiagnostics)
{
ImmutableArray<DiagnosticData>.Builder builder = null;
if (newDiagnostics.Length > 0)
{
builder = ImmutableArray.CreateBuilder<DiagnosticData>();
builder.AddRange(newDiagnostics);
}
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));
}
return builder == null ? ImmutableArray<DiagnosticData>.Empty : builder.ToImmutable();
}
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 don't 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.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);
}
}
}
// 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.ErrorReporting;
using Microsoft.CodeAnalysis.Shared.Extensions;
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)
{
return AnalyzeDocumentForKindAsync(document, AnalysisKind.Syntax, cancellationToken);
}
public override Task AnalyzeDocumentAsync(Document document, SyntaxNode bodyOpt, CancellationToken cancellationToken)
{
return AnalyzeDocumentForKindAsync(document, AnalysisKind.Semantic, cancellationToken);
}
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;
}
var stateSets = _stateManager.GetOrUpdateStateSets(document.Project);
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(analyzerDriverOpt, document, stateSet, kind, cancellationToken).ConfigureAwait(false);
if (result.FromCache)
{
RaiseDocumentDiagnosticsIfNeeded(document, stateSet, kind, result.Items);
continue;
}
// no cancellation after this point.
var state = stateSet.GetActiveFileState(document.Id);
state.Save(kind, result.ToPersistData());
RaiseDocumentDiagnosticsIfNeeded(document, stateSet, kind, result.OldItems, result.Items);
}
}
catch (Exception e) when (FatalError.ReportUnlessCanceled(e))
{
throw ExceptionUtilities.Unreachable;
}
}
public override async Task AnalyzeProjectAsync(Project project, bool semanticsChanged, CancellationToken cancellationToken)
{
try
{
var stateSets = _stateManager.GetOrUpdateStateSets(project);
// 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();
// get driver only with active analyzers.
var includeSuppressedDiagnostics = true;
var analyzerDriverOpt = await _compilationManager.CreateAnalyzerDriverAsync(project, activeAnalyzers, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false);
var result = await _executor.GetProjectAnalysisDataAsync(analyzerDriverOpt, project, stateSets, cancellationToken).ConfigureAwait(false);
if (result.FromCache)
{
RaiseProjectDiagnosticsIfNeeded(project, stateSets, result.Result);
return;
}
// 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);
}
RaiseProjectDiagnosticsIfNeeded(project, stateSets, result.OldResult, result.Result);
}
catch (Exception e) when (FatalError.ReportUnlessCanceled(e))
{
throw ExceptionUtilities.Unreachable;
}
}
public override Task DocumentOpenAsync(Document document, CancellationToken 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;
}
public override 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);
return SpecializedTasks.EmptyTask;
}
public override 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);
return SpecializedTasks.EmptyTask;
}
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)
{
// 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)
{
// 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)
{
// let other components knows about this event
_compilationManager.OnNewSolution();
return SpecializedTasks.EmptyTask;
}
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 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,
ImmutableDictionary<DiagnosticAnalyzer, AnalysisResult> result)
{
RaiseProjectDiagnosticsIfNeeded(project, stateSets, ImmutableDictionary<DiagnosticAnalyzer, AnalysisResult>.Empty, result);
}
private void RaiseProjectDiagnosticsIfNeeded(
Project project,
IEnumerable<StateSet> stateSets,
ImmutableDictionary<DiagnosticAnalyzer, AnalysisResult> oldResult,
ImmutableDictionary<DiagnosticAnalyzer, AnalysisResult> 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 = 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
// 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<DiagnosticData> items)
{
RaiseDocumentDiagnosticsIfNeeded(document, stateSet, kind, ImmutableArray<DiagnosticData>.Empty, items);
}
private void RaiseDocumentDiagnosticsIfNeeded(
Document document, StateSet stateSet, AnalysisKind kind, ImmutableArray<DiagnosticData> oldItems, ImmutableArray<DiagnosticData> newItems)
{
RaiseDocumentDiagnosticsIfNeeded(document, stateSet, kind, oldItems, newItems, Owner.RaiseDiagnosticsUpdated);
}
private void RaiseDocumentDiagnosticsIfNeeded(
Document document, StateSet stateSet, AnalysisKind kind,
AnalysisResult oldResult, AnalysisResult newResult,
Action<DiagnosticsUpdatedArgs> 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<DiagnosticData> oldItems, ImmutableArray<DiagnosticData> newItems,
Action<DiagnosticsUpdatedArgs> 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<DiagnosticsUpdatedArgs> 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<DocumentId> documentIds, Action<DiagnosticsUpdatedArgs> raiseEvents)
{
var handleActiveFile = false;
RaiseProjectDiagnosticsRemoved(stateSet, projectId, documentIds, handleActiveFile, raiseEvents);
}
private void RaiseProjectDiagnosticsRemoved(StateSet stateSet, ProjectId projectId, IEnumerable<DocumentId> documentIds, bool handleActiveFile, Action<DiagnosticsUpdatedArgs> 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);
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册