From a174a299329cf70706089ff41673242f22a3ace9 Mon Sep 17 00:00:00 2001 From: Heejae Chang Date: Mon, 23 Feb 2015 17:59:52 -0800 Subject: [PATCH 1/5] renamed WorkspaceAnalyzerManager to HostAnalyzerManager. and added place holder for state manager --- .../AbstractHostDiagnosticUpdateSource.cs | 2 +- .../Diagnostics/DiagnosticAnalyzerService.cs | 6 +-- ...sticAnalyzerService_IncrementalAnalyzer.cs | 4 +- ...alAnalyzer.DiagnosticAnalyzersAndStates.cs | 4 +- ...gnosticIncrementalAnalyzer.StateManager.cs | 45 +++++++++++++++++++ ...AndStates.PerLanguageAnalyzersAndStates.cs | 4 +- ...talAnalyzer.WorkspaceAnalyzersAndStates.cs | 12 +++-- .../EngineV1/DiagnosticIncrementalAnalyzer.cs | 2 +- .../EngineV2/DiagnosticIncrementalAnalyzer.cs | 4 +- ...lyzerManager.cs => HostAnalyzerManager.cs} | 20 ++++++--- src/Features/Core/Features.csproj | 3 +- 11 files changed, 84 insertions(+), 22 deletions(-) create mode 100644 src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.cs rename src/Features/Core/Diagnostics/{WorkspaceAnalyzerManager.cs => HostAnalyzerManager.cs} (95%) diff --git a/src/Features/Core/Diagnostics/AbstractHostDiagnosticUpdateSource.cs b/src/Features/Core/Diagnostics/AbstractHostDiagnosticUpdateSource.cs index ec2e901e647ec..2580723a0ab4e 100644 --- a/src/Features/Core/Diagnostics/AbstractHostDiagnosticUpdateSource.cs +++ b/src/Features/Core/Diagnostics/AbstractHostDiagnosticUpdateSource.cs @@ -114,7 +114,7 @@ private void ClearAnalyzerDiagnostics(DiagnosticAnalyzer analyzer, ProjectId pro private DiagnosticsUpdatedArgs MakeArgs(DiagnosticAnalyzer analyzer, ImmutableHashSet items, Project project) { - var id = WorkspaceAnalyzerManager.GetUniqueIdForAnalyzer(analyzer); + var id = HostAnalyzerManager.GetUniqueIdForAnalyzer(analyzer); return new DiagnosticsUpdatedArgs( id: Tuple.Create(this, id, project?.Id), diff --git a/src/Features/Core/Diagnostics/DiagnosticAnalyzerService.cs b/src/Features/Core/Diagnostics/DiagnosticAnalyzerService.cs index 1722c6c56a2ce..dadd0a8302fad 100644 --- a/src/Features/Core/Diagnostics/DiagnosticAnalyzerService.cs +++ b/src/Features/Core/Diagnostics/DiagnosticAnalyzerService.cs @@ -15,7 +15,7 @@ namespace Microsoft.CodeAnalysis.Diagnostics [Shared] internal partial class DiagnosticAnalyzerService : IDiagnosticAnalyzerService { - private readonly WorkspaceAnalyzerManager _workspaceAnalyzerManager; + private readonly HostAnalyzerManager _workspaceAnalyzerManager; private readonly AbstractHostDiagnosticUpdateSource _hostDiagnosticUpdateSource; [ImportingConstructor] @@ -30,14 +30,14 @@ public DiagnosticAnalyzerService([Import(AllowDefault = true)]IWorkspaceDiagnost private DiagnosticAnalyzerService(IEnumerable workspaceAnalyzerAssemblies, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource) : this() { - _workspaceAnalyzerManager = new WorkspaceAnalyzerManager(workspaceAnalyzerAssemblies, hostDiagnosticUpdateSource); + _workspaceAnalyzerManager = new HostAnalyzerManager(workspaceAnalyzerAssemblies, hostDiagnosticUpdateSource); _hostDiagnosticUpdateSource = hostDiagnosticUpdateSource; } // internal for testing purposes. internal DiagnosticAnalyzerService(ImmutableArray workspaceAnalyzers, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource = null) : this() { - _workspaceAnalyzerManager = new WorkspaceAnalyzerManager(workspaceAnalyzers, hostDiagnosticUpdateSource); + _workspaceAnalyzerManager = new HostAnalyzerManager(workspaceAnalyzers, hostDiagnosticUpdateSource); _hostDiagnosticUpdateSource = hostDiagnosticUpdateSource; } diff --git a/src/Features/Core/Diagnostics/DiagnosticAnalyzerService_IncrementalAnalyzer.cs b/src/Features/Core/Diagnostics/DiagnosticAnalyzerService_IncrementalAnalyzer.cs index d8a05e0ded115..36d1fd7b609e1 100644 --- a/src/Features/Core/Diagnostics/DiagnosticAnalyzerService_IncrementalAnalyzer.cs +++ b/src/Features/Core/Diagnostics/DiagnosticAnalyzerService_IncrementalAnalyzer.cs @@ -60,7 +60,7 @@ private void OnDocumentActiveContextChanged(object sender, DocumentEventArgs e) // internal for testing internal class IncrementalAnalyzerDelegatee : BaseDiagnosticIncrementalAnalyzer { - private readonly WorkspaceAnalyzerManager _workspaceAnalyzerManager; + private readonly HostAnalyzerManager _workspaceAnalyzerManager; private readonly DiagnosticAnalyzerService _owner; // v1 diagnostic engine @@ -69,7 +69,7 @@ internal class IncrementalAnalyzerDelegatee : BaseDiagnosticIncrementalAnalyzer // v2 diagnostic engine - for now v1 private readonly EngineV2.DiagnosticIncrementalAnalyzer _engineV2; - public IncrementalAnalyzerDelegatee(DiagnosticAnalyzerService owner, Workspace workspace, WorkspaceAnalyzerManager workspaceAnalyzerManager, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource) + public IncrementalAnalyzerDelegatee(DiagnosticAnalyzerService owner, Workspace workspace, HostAnalyzerManager workspaceAnalyzerManager, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource) : base(workspace, hostDiagnosticUpdateSource) { _workspaceAnalyzerManager = workspaceAnalyzerManager; diff --git a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.DiagnosticAnalyzersAndStates.cs b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.DiagnosticAnalyzersAndStates.cs index aed05e00cb515..cfdd7867626be 100644 --- a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.DiagnosticAnalyzersAndStates.cs +++ b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.DiagnosticAnalyzersAndStates.cs @@ -30,7 +30,7 @@ private partial class DiagnosticAnalyzersAndStates public readonly Workspace Workspace; - public DiagnosticAnalyzersAndStates(DiagnosticIncrementalAnalyzer owner, Workspace workspace, WorkspaceAnalyzerManager workspaceAnalyzerManager) + public DiagnosticAnalyzersAndStates(DiagnosticIncrementalAnalyzer owner, Workspace workspace, HostAnalyzerManager workspaceAnalyzerManager) { _owner = owner; _sharedAnalyzersAndStates = new WorkspaceAnalyzersAndStates(workspaceAnalyzerManager); @@ -172,7 +172,7 @@ private static ValueTuple GetUniqueDiagnosticStateNameAndV // Get the unique ID for given diagnostic analyzer. // note that we also put version stamp so that we can detect changed analyzer. - var tuple = WorkspaceAnalyzerManager.GetUniqueIdForAnalyzer(provider); + var tuple = HostAnalyzerManager.GetUniqueIdForAnalyzer(provider); return ValueTuple.Create(UserDiagnosticsPrefixTableName + "_" + type.ToString() + "_" + tuple.Item1, tuple.Item2); } diff --git a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.cs b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.cs new file mode 100644 index 0000000000000..e3500e6a393ee --- /dev/null +++ b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.cs @@ -0,0 +1,45 @@ +// 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.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.SolutionCrawler.State; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Diagnostics.EngineV1 +{ + internal partial class DiagnosticIncrementalAnalyzer + { + internal class StateManager + { + private static readonly int s_stateTypeCount = Enum.GetNames(typeof(StateType)).Count(); + private static readonly ImmutableArray s_documentScopeStateTypes = ImmutableArray.Create(StateType.Syntax, StateType.Document); + + private readonly ConditionalWeakTable _stateMap; + + public StateManager() + { + _stateMap = new ConditionalWeakTable(); + } + + public DiagnosticState GetState(DiagnosticAnalyzer analyzer, StateType statetype) + { + return _stateMap.GetValue(analyzer, CreateAnalyzerStates)[(int)statetype]; + } + + private DiagnosticState[] CreateAnalyzerStates(DiagnosticAnalyzer unused) + { + return new DiagnosticState[s_stateTypeCount]; + } + } + } +} diff --git a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.WorkspaceAnalyzersAndStates.PerLanguageAnalyzersAndStates.cs b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.WorkspaceAnalyzersAndStates.PerLanguageAnalyzersAndStates.cs index 36cfdc6b1e4e6..d68585ddd6ea6 100644 --- a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.WorkspaceAnalyzersAndStates.PerLanguageAnalyzersAndStates.cs +++ b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.WorkspaceAnalyzersAndStates.PerLanguageAnalyzersAndStates.cs @@ -26,7 +26,7 @@ private class PerLanguageAnalyzersAndStates private readonly int _analyzerCount; private readonly DiagnosticState[,] _diagnosticStateMaps; - public PerLanguageAnalyzersAndStates(WorkspaceAnalyzerManager workspaceAnalyzerManager, string language) + public PerLanguageAnalyzersAndStates(HostAnalyzerManager workspaceAnalyzerManager, string language) { _language = language; @@ -37,7 +37,7 @@ public PerLanguageAnalyzersAndStates(WorkspaceAnalyzerManager workspaceAnalyzerM } private static ImmutableDictionary> CreateAnalyzerIdMap( - WorkspaceAnalyzerManager workspaceAnalyzerManager, string language) + HostAnalyzerManager workspaceAnalyzerManager, string language) { var index = 0; var map = ImmutableDictionary.CreateBuilder>(); diff --git a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.WorkspaceAnalyzersAndStates.cs b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.WorkspaceAnalyzersAndStates.cs index 11a91c44fec46..56cc472317938 100644 --- a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.WorkspaceAnalyzersAndStates.cs +++ b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.WorkspaceAnalyzersAndStates.cs @@ -19,10 +19,10 @@ private partial class DiagnosticAnalyzersAndStates /// private partial class WorkspaceAnalyzersAndStates { - private readonly WorkspaceAnalyzerManager _workspaceAnalyzerManager; + private readonly HostAnalyzerManager _workspaceAnalyzerManager; private ImmutableDictionary _perLanguageAnalyzersAndStatesMap; - public WorkspaceAnalyzersAndStates(WorkspaceAnalyzerManager workspaceAnalyzerManager) + public WorkspaceAnalyzersAndStates(HostAnalyzerManager workspaceAnalyzerManager) { _workspaceAnalyzerManager = workspaceAnalyzerManager; _perLanguageAnalyzersAndStatesMap = ImmutableDictionary.Empty; @@ -40,8 +40,14 @@ private PerLanguageAnalyzersAndStates GetOrCreatePerLanguageAnalyzersAndStates(s public int GetAnalyzerCount(string language) { + var count = _workspaceAnalyzerManager.GetHostAnalyzerCount(language); + +#if DEBUG var analyzersAndStates = this.GetOrCreatePerLanguageAnalyzersAndStates(language); - return analyzersAndStates.AnalyzerCount; + Contract.Requires(count == analyzersAndStates.AnalyzerCount); +#endif + + return count; } public bool HasAnalyzerReference(AnalyzerReference analyzerReference, string language) diff --git a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.cs b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.cs index f95514b45e754..50b781442933a 100644 --- a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.cs +++ b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.cs @@ -39,7 +39,7 @@ public DiagnosticIncrementalAnalyzer( DiagnosticAnalyzerService owner, int correlationId, Workspace workspace, - WorkspaceAnalyzerManager workspaceAnalyzerManager, + HostAnalyzerManager workspaceAnalyzerManager, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource) : base(workspace, hostDiagnosticUpdateSource) { diff --git a/src/Features/Core/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs b/src/Features/Core/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs index c7b8d10ffce8f..7c2fe1c208c3b 100644 --- a/src/Features/Core/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs +++ b/src/Features/Core/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs @@ -15,9 +15,9 @@ internal class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncrementalAnalyzer { private readonly int _correlationId; private readonly DiagnosticAnalyzerService _owner; - private readonly WorkspaceAnalyzerManager _workspaceAnalyzerManager; + private readonly HostAnalyzerManager _workspaceAnalyzerManager; - public DiagnosticIncrementalAnalyzer(DiagnosticAnalyzerService owner, int correlationId, Workspace workspace, WorkspaceAnalyzerManager workspaceAnalyzerManager, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource) + public DiagnosticIncrementalAnalyzer(DiagnosticAnalyzerService owner, int correlationId, Workspace workspace, HostAnalyzerManager workspaceAnalyzerManager, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource) : base(workspace, hostDiagnosticUpdateSource) { _correlationId = correlationId; diff --git a/src/Features/Core/Diagnostics/WorkspaceAnalyzerManager.cs b/src/Features/Core/Diagnostics/HostAnalyzerManager.cs similarity index 95% rename from src/Features/Core/Diagnostics/WorkspaceAnalyzerManager.cs rename to src/Features/Core/Diagnostics/HostAnalyzerManager.cs index 107556424c947..b4dd193c8907c 100644 --- a/src/Features/Core/Diagnostics/WorkspaceAnalyzerManager.cs +++ b/src/Features/Core/Diagnostics/HostAnalyzerManager.cs @@ -22,7 +22,7 @@ namespace Microsoft.CodeAnalysis.Diagnostics /// /// this should be alway thread-safe. /// - internal sealed partial class WorkspaceAnalyzerManager + internal sealed partial class HostAnalyzerManager { /// /// Key is analyzer reference identity . @@ -53,17 +53,18 @@ internal sealed partial class WorkspaceAnalyzerManager /// private readonly AbstractHostDiagnosticUpdateSource _hostDiagnosticUpdateSource; - public WorkspaceAnalyzerManager(IEnumerable hostAnalyzerAssemblies, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource) : + public HostAnalyzerManager(IEnumerable hostAnalyzerAssemblies, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource) : this(CreateAnalyzerReferencesFromAssemblies(hostAnalyzerAssemblies), hostDiagnosticUpdateSource) { } - public WorkspaceAnalyzerManager(ImmutableArray hostAnalyzerReferences, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource) + public HostAnalyzerManager(ImmutableArray hostAnalyzerReferences, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource) { + _hostDiagnosticUpdateSource = hostDiagnosticUpdateSource; + _hostAnalyzerReferencesMap = hostAnalyzerReferences.IsDefault ? ImmutableDictionary.Empty : CreateAnalyzerReferencesMap(hostAnalyzerReferences); - _hostDiagnosticAnalyzersPerLanguageMap = new ConcurrentDictionary>>(concurrencyLevel: 2, capacity: 2); + _hostDiagnosticAnalyzersPerLanguageMap = new ConcurrentDictionary>>(concurrencyLevel: 2, capacity: 2); _lazyHostDiagnosticAnalyzersPerReferenceMap = new Lazy>>(() => CreateDiagnosticAnalyzersPerReferenceMap(_hostAnalyzerReferencesMap), isThreadSafe: true); - _hostDiagnosticUpdateSource = hostDiagnosticUpdateSource; DiagnosticAnalyzerLogger.LogWorkspaceAnalyzers(hostAnalyzerReferences); } @@ -94,6 +95,15 @@ public ImmutableDictionary> GetHostDi return _hostDiagnosticAnalyzersPerLanguageMap.GetOrAdd(language, CreateHostDiagnosticAnalyzers); } + /// + /// Get number of analyzers for the language + /// + public int GetHostAnalyzerCount(string language) + { + var map = GetHostDiagnosticAnalyzersPerReference(language); + return map.Values.Sum(v => v.Length); + } + /// /// Create identity and s map /// diff --git a/src/Features/Core/Features.csproj b/src/Features/Core/Features.csproj index 8aaac55b5b6e3..a285b696475fe 100644 --- a/src/Features/Core/Features.csproj +++ b/src/Features/Core/Features.csproj @@ -179,7 +179,8 @@ - + + From f9d09d9f6a85856c532f597480e07201253691ee Mon Sep 17 00:00:00 2001 From: Heejae Chang Date: Fri, 27 Feb 2015 05:03:02 -0800 Subject: [PATCH 2/5] finished diagnostic analyzer state refactoring all tests passing. now StateManager owns all state related operation. --- .../Diagnostics/DiagnosticServiceTests.vb | 5 +- .../AbstractHostDiagnosticUpdateSource.cs | 2 +- .../Core/Diagnostics/AnalyzerHelper.cs | 38 +- .../BaseDiagnosticIncrementalAnalyzer.cs | 2 +- .../Diagnostics/DiagnosticAnalyzerService.cs | 10 +- .../DiagnosticAnalyzerService_Test.cs | 6 + .../EngineV1/DiagnosticAnalyzerDriver.cs | 16 +- ...alAnalyzer.DiagnosticAnalyzersAndStates.cs | 304 --------------- ...er.IncrementalAnalyzer.AnalyzerExecutor.cs | 44 +-- ...agnosticIncrementalAnalyzer.NestedTypes.cs | 10 +- ...entalAnalyzer.ProjectAnalyzersAndStates.cs | 139 ------- ...ementalAnalyzer.StateManager.HostStates.cs | 71 ++++ ...ntalAnalyzer.StateManager.ProjectStates.cs | 243 ++++++++++++ ...gnosticIncrementalAnalyzer.StateManager.cs | 229 +++++++++++- ...AndStates.PerLanguageAnalyzersAndStates.cs | 126 ------- ...talAnalyzer.WorkspaceAnalyzersAndStates.cs | 127 ------- .../EngineV1/DiagnosticIncrementalAnalyzer.cs | 346 ++++++++---------- ...osticIncrementalAnalyzer_GetDiagnostics.cs | 92 ++--- .../Diagnostics/EngineV1/MemberRangeMap.cs | 26 +- .../Core/Diagnostics/HostAnalyzerManager.cs | 47 +-- .../Log/DiagnosticAnalyzerLogger.cs | 2 +- src/Features/Core/Features.csproj | 6 +- .../Portable/Utilities/SimpleTaskQueue.cs | 8 +- 23 files changed, 830 insertions(+), 1069 deletions(-) delete mode 100644 src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.DiagnosticAnalyzersAndStates.cs delete mode 100644 src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.ProjectAnalyzersAndStates.cs create mode 100644 src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.HostStates.cs create mode 100644 src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs delete mode 100644 src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.WorkspaceAnalyzersAndStates.PerLanguageAnalyzersAndStates.cs delete mode 100644 src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.WorkspaceAnalyzersAndStates.cs diff --git a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb index 37bd15203e6ef..26264d59cbc5f 100644 --- a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb @@ -113,7 +113,8 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim duplicateProjectAnalyzersReference = New AnalyzerImageReference(duplicateProjectAnalyzers) project = project.WithAnalyzerReferences({duplicateProjectAnalyzersReference}) - ' Verify no duplicate descriptors or diagnsotics. + ' Verify duplicate descriptors or diagnsotics. + ' 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) descriptors = descriptorsMap.Values.SelectMany(Function(d) d).OrderBy(Function(d) d.Id).ToImmutableArray() @@ -123,7 +124,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests diagnostics = diagnosticService.GetDiagnosticsForSpanAsync(document, document.GetSyntaxRootAsync().WaitAndGetResult(CancellationToken.None).FullSpan, CancellationToken.None).WaitAndGetResult(CancellationToken.None) - Assert.Equal(1, diagnostics.Count()) + Assert.Equal(2, diagnostics.Count()) End Using End Sub diff --git a/src/Features/Core/Diagnostics/AbstractHostDiagnosticUpdateSource.cs b/src/Features/Core/Diagnostics/AbstractHostDiagnosticUpdateSource.cs index 2580723a0ab4e..6efa2eed81410 100644 --- a/src/Features/Core/Diagnostics/AbstractHostDiagnosticUpdateSource.cs +++ b/src/Features/Core/Diagnostics/AbstractHostDiagnosticUpdateSource.cs @@ -114,7 +114,7 @@ private void ClearAnalyzerDiagnostics(DiagnosticAnalyzer analyzer, ProjectId pro private DiagnosticsUpdatedArgs MakeArgs(DiagnosticAnalyzer analyzer, ImmutableHashSet items, Project project) { - var id = HostAnalyzerManager.GetUniqueIdForAnalyzer(analyzer); + var id = analyzer.GetUniqueIdForAnalyzer(); return new DiagnosticsUpdatedArgs( id: Tuple.Create(this, id, project?.Id), diff --git a/src/Features/Core/Diagnostics/AnalyzerHelper.cs b/src/Features/Core/Diagnostics/AnalyzerHelper.cs index 5be54eec9948e..1e11334b8bbdc 100644 --- a/src/Features/Core/Diagnostics/AnalyzerHelper.cs +++ b/src/Features/Core/Diagnostics/AnalyzerHelper.cs @@ -1,9 +1,11 @@ // 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.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.ErrorReporting; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics { @@ -12,12 +14,12 @@ internal static class AnalyzerHelper private const string CSharpCompilerAnalyzerTypeName = "Microsoft.CodeAnalysis.Diagnostics.CSharp.CSharpCompilerDiagnosticAnalyzer"; private const string VisualBasicCompilerAnalyzerTypeName = "Microsoft.CodeAnalysis.Diagnostics.VisualBasic.VisualBasicCompilerDiagnosticAnalyzer"; - public static bool IsBuiltInAnalyzer(DiagnosticAnalyzer analyzer) + public static bool IsBuiltInAnalyzer(this DiagnosticAnalyzer analyzer) { - return analyzer is IBuiltInAnalyzer || analyzer is DocumentDiagnosticAnalyzer || analyzer is ProjectDiagnosticAnalyzer || IsCompilerAnalyzer(analyzer); + return analyzer is IBuiltInAnalyzer || analyzer is DocumentDiagnosticAnalyzer || analyzer is ProjectDiagnosticAnalyzer || analyzer.IsCompilerAnalyzer(); } - public static bool IsCompilerAnalyzer(DiagnosticAnalyzer analyzer) + public static bool IsCompilerAnalyzer(this DiagnosticAnalyzer analyzer) { // TODO: find better way. var typeString = analyzer.GetType().ToString(); @@ -34,25 +36,33 @@ public static bool IsCompilerAnalyzer(DiagnosticAnalyzer analyzer) return false; } - public static Action GetAddExceptionDiagnosticDelegate(DiagnosticAnalyzer analyzer, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource, Project project) + public static ValueTuple GetUniqueIdForAnalyzer(this DiagnosticAnalyzer analyzer) + { + // Get the unique ID for given diagnostic analyzer. + // note that we also put version stamp so that we can detect changed analyzer. + var type = analyzer.GetType(); + return ValueTuple.Create(type.AssemblyQualifiedName, GetProviderVersion(type.Assembly.Location)); + } + + public static Action GetAddExceptionDiagnosticDelegate(this DiagnosticAnalyzer analyzer, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource, Project project) { return diagnostic => hostDiagnosticUpdateSource?.ReportAnalyzerDiagnostic(analyzer, diagnostic, project.Solution.Workspace, project); } - public static Action GetAddExceptionDiagnosticDelegate(DiagnosticAnalyzer analyzer, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource, Workspace workspace) + public static Action GetAddExceptionDiagnosticDelegate(this DiagnosticAnalyzer analyzer, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource, Workspace workspace) { return diagnostic => hostDiagnosticUpdateSource?.ReportAnalyzerDiagnostic(analyzer, diagnostic, workspace, null); } public static AnalyzerExecutor GetAnalyzerExecutorForSupportedDiagnostics( - DiagnosticAnalyzer analyzer, + this DiagnosticAnalyzer analyzer, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource, Func continueOnAnalyzerException, CancellationToken cancellationToken) { - var addExceptionDiagnostic = GetAddExceptionDiagnosticDelegate(analyzer, hostDiagnosticUpdateSource, hostDiagnosticUpdateSource?.Workspace); + var addExceptionDiagnostic = analyzer.GetAddExceptionDiagnosticDelegate(hostDiagnosticUpdateSource, hostDiagnosticUpdateSource?.Workspace); // Skip telemetry logging if the exception is thrown as we are computing supported diagnostics and // we can't determine if any descriptors support getting telemetry without having the descriptors. @@ -60,7 +70,7 @@ public static AnalyzerExecutor GetAnalyzerExecutorForSupportedDiagnostics( } public static AnalyzerExecutor GetAnalyzerExecutor( - DiagnosticAnalyzer analyzer, + this DiagnosticAnalyzer analyzer, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource, Project project, Compilation compilation, @@ -69,8 +79,18 @@ public static AnalyzerExecutor GetAnalyzerExecutor( Func continueOnAnalyzerException, CancellationToken cancellationToken) { - var addExceptionDiagnostic = GetAddExceptionDiagnosticDelegate(analyzer, hostDiagnosticUpdateSource, project); + var addExceptionDiagnostic = analyzer.GetAddExceptionDiagnosticDelegate(hostDiagnosticUpdateSource, project); return AnalyzerExecutor.Create(compilation, analyzerOptions, addDiagnostic, addExceptionDiagnostic, continueOnAnalyzerException, cancellationToken); } + + private static VersionStamp GetProviderVersion(string path) + { + if (path == null || !File.Exists(path)) + { + return VersionStamp.Default; + } + + return VersionStamp.Create(File.GetLastWriteTimeUtc(path)); + } } } \ No newline at end of file diff --git a/src/Features/Core/Diagnostics/BaseDiagnosticIncrementalAnalyzer.cs b/src/Features/Core/Diagnostics/BaseDiagnosticIncrementalAnalyzer.cs index f1939bccb4bcc..753c7e9c38dbf 100644 --- a/src/Features/Core/Diagnostics/BaseDiagnosticIncrementalAnalyzer.cs +++ b/src/Features/Core/Diagnostics/BaseDiagnosticIncrementalAnalyzer.cs @@ -162,7 +162,7 @@ protected BaseDiagnosticIncrementalAnalyzer(Workspace workspace, AbstractHostDia public abstract Task> GetDiagnosticsForSpanAsync(Document document, TextSpan range, CancellationToken cancellationToken); #endregion - public Workspace Workspace { get; private set; } + protected Workspace Workspace { get; private set; } protected AbstractHostDiagnosticUpdateSource HostDiagnosticUpdateSource { get; private set; } public virtual bool NeedsReanalysisOnOptionChanged(object sender, OptionChangedEventArgs e) diff --git a/src/Features/Core/Diagnostics/DiagnosticAnalyzerService.cs b/src/Features/Core/Diagnostics/DiagnosticAnalyzerService.cs index dadd0a8302fad..82f383064b2d8 100644 --- a/src/Features/Core/Diagnostics/DiagnosticAnalyzerService.cs +++ b/src/Features/Core/Diagnostics/DiagnosticAnalyzerService.cs @@ -1,10 +1,12 @@ // 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.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -17,17 +19,23 @@ internal partial class DiagnosticAnalyzerService : IDiagnosticAnalyzerService { private readonly HostAnalyzerManager _workspaceAnalyzerManager; private readonly AbstractHostDiagnosticUpdateSource _hostDiagnosticUpdateSource; + private readonly IAsynchronousOperationListener _listener; [ImportingConstructor] - public DiagnosticAnalyzerService([Import(AllowDefault = true)]IWorkspaceDiagnosticAnalyzerProviderService diagnosticAnalyzerProviderService = null, + public DiagnosticAnalyzerService( + [ImportMany] IEnumerable> asyncListeners, + [Import(AllowDefault = true)]IWorkspaceDiagnosticAnalyzerProviderService diagnosticAnalyzerProviderService = null, [Import(AllowDefault = true)]AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource = null) : this(workspaceAnalyzerAssemblies: diagnosticAnalyzerProviderService != null ? diagnosticAnalyzerProviderService.GetWorkspaceAnalyzerAssemblies() : SpecializedCollections.EmptyEnumerable(), hostDiagnosticUpdateSource: hostDiagnosticUpdateSource) { + _listener = new AggregateAsynchronousOperationListener(asyncListeners, FeatureAttribute.DiagnosticService); } + public IAsynchronousOperationListener Listener => _listener; + private DiagnosticAnalyzerService(IEnumerable workspaceAnalyzerAssemblies, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource) : this() { _workspaceAnalyzerManager = new HostAnalyzerManager(workspaceAnalyzerAssemblies, hostDiagnosticUpdateSource); diff --git a/src/Features/Core/Diagnostics/DiagnosticAnalyzerService_Test.cs b/src/Features/Core/Diagnostics/DiagnosticAnalyzerService_Test.cs index c8c0b403134d9..e74ace858efa4 100644 --- a/src/Features/Core/Diagnostics/DiagnosticAnalyzerService_Test.cs +++ b/src/Features/Core/Diagnostics/DiagnosticAnalyzerService_Test.cs @@ -28,6 +28,12 @@ internal DiagnosticAnalyzerService(ImmutableDictionary(), hostDiagnosticUpdateSource: hostDiagnosticUpdateSource) + { + } + private class TestAnalyzerReferenceByLanguage : AnalyzerReference { private readonly ImmutableDictionary> _analyzersMap; diff --git a/src/Features/Core/Diagnostics/EngineV1/DiagnosticAnalyzerDriver.cs b/src/Features/Core/Diagnostics/EngineV1/DiagnosticAnalyzerDriver.cs index 5153dcbbc6368..e958e85a4fb10 100644 --- a/src/Features/Core/Diagnostics/EngineV1/DiagnosticAnalyzerDriver.cs +++ b/src/Features/Core/Diagnostics/EngineV1/DiagnosticAnalyzerDriver.cs @@ -83,9 +83,9 @@ internal DiagnosticAnalyzerDriver( // internal for testing purposes internal DiagnosticAnalyzerDriver( - Project project, - ISyntaxNodeAnalyzerService syntaxNodeAnalyzerService, - AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource, + Project project, + ISyntaxNodeAnalyzerService syntaxNodeAnalyzerService, + AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource, CancellationToken cancellationToken) { _project = project; @@ -339,19 +339,19 @@ internal void ReportAnalyzerExceptionDiagnostic(DiagnosticAnalyzer analyzer, Dia private Action GetAddExceptionDiagnosticDelegate(DiagnosticAnalyzer analyzer) { - return AnalyzerHelper.GetAddExceptionDiagnosticDelegate(analyzer, _hostDiagnosticUpdateSource, _project); + return analyzer.GetAddExceptionDiagnosticDelegate(_hostDiagnosticUpdateSource, _project); } private AnalyzerExecutor GetAnalyzerExecutorForSupportedDiagnostics(DiagnosticAnalyzer analyzer) { // Skip telemetry logging if the exception is thrown as we are computing supported diagnostics and // we can't determine if any descriptors support getting telemetry without having the descriptors. - return AnalyzerHelper.GetAnalyzerExecutorForSupportedDiagnostics(analyzer, _hostDiagnosticUpdateSource, CatchAnalyzerException_NoTelemetryLogging, _cancellationToken); + return analyzer.GetAnalyzerExecutorForSupportedDiagnostics(_hostDiagnosticUpdateSource, CatchAnalyzerException_NoTelemetryLogging, _cancellationToken); } private AnalyzerExecutor GetAnalyzerExecutor(DiagnosticAnalyzer analyzer, Compilation compilation, Action addDiagnostic) { - return AnalyzerHelper.GetAnalyzerExecutor(analyzer, _hostDiagnosticUpdateSource, _project, + return analyzer.GetAnalyzerExecutor(_hostDiagnosticUpdateSource, _project, compilation, addDiagnostic, _analyzerOptions, CatchAnalyzerException, _cancellationToken); } @@ -461,7 +461,7 @@ public async Task> GetProjectDiagnosticsAsync(Diagnos { Contract.ThrowIfNull(_project); Contract.ThrowIfFalse(_document == null); - + using (var diagnostics = SharedPools.Default>().GetPooledObject()) { if (_project.SupportsCompilation) @@ -573,7 +573,7 @@ internal static bool CatchAnalyzerException_NoTelemetryLogging(Exception e, Diag return false; } - if (AnalyzerHelper.IsBuiltInAnalyzer(analyzer)) + if (analyzer.IsBuiltInAnalyzer()) { return FatalError.ReportWithoutCrashUnlessCanceled(e); } diff --git a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.DiagnosticAnalyzersAndStates.cs b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.DiagnosticAnalyzersAndStates.cs deleted file mode 100644 index cfdd7867626be..0000000000000 --- a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.DiagnosticAnalyzersAndStates.cs +++ /dev/null @@ -1,304 +0,0 @@ -// 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.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Diagnostics.EngineV1 -{ - using ProviderId = Int32; - - internal partial class DiagnosticIncrementalAnalyzer - { - /// - /// Maintains all MEF-imported diagnostic analyzers (with diagnostic states), which are enabled for all projects in the workspace, - /// and a map of per-project diagnostic analyzers (with diagnostic states). - /// - private partial class DiagnosticAnalyzersAndStates - { - private const string UserDiagnosticsPrefixTableName = ""; - - private readonly DiagnosticIncrementalAnalyzer _owner; - private readonly WorkspaceAnalyzersAndStates _sharedAnalyzersAndStates; - private readonly ConcurrentDictionary _projectAnalyzersAndStatesMap; - - public readonly Workspace Workspace; - - public DiagnosticAnalyzersAndStates(DiagnosticIncrementalAnalyzer owner, Workspace workspace, HostAnalyzerManager workspaceAnalyzerManager) - { - _owner = owner; - _sharedAnalyzersAndStates = new WorkspaceAnalyzersAndStates(workspaceAnalyzerManager); - _projectAnalyzersAndStatesMap = new ConcurrentDictionary(); - - this.Workspace = workspace; - } - - public IEnumerable> GetAllExistingDiagnosticStates(ProjectId projectId) - { - var project = this.Workspace.CurrentSolution.GetProject(projectId); - var language = project != null ? project.Language : null; - return GetAllExistingDiagnosticStates(projectId, language); - } - - public IEnumerable> GetAllExistingDiagnosticStates(ProjectId projectId, string language) - { - var current = _sharedAnalyzersAndStates.GetAllExistingDiagnosticStates(language); - - ProjectAnalyzersAndStates projectAnalyzersAndStates; - if (_projectAnalyzersAndStatesMap.TryGetValue(projectId, out projectAnalyzersAndStates) && - projectAnalyzersAndStates != null) - { - current = current.Concat(projectAnalyzersAndStates.GetAllExistingDiagnosticStates()); - } - - return current; - } - - public IEnumerable> GetAllExistingDiagnosticStates(ProjectId projectId, StateType type) - { - var project = this.Workspace.CurrentSolution.GetProject(projectId); - var language = project != null ? project.Language : null; - return GetAllExistingDiagnosticStates(projectId, type, language); - } - - public IEnumerable> GetAllExistingDiagnosticStates(ProjectId projectId, StateType type, string language) - { - var current = _sharedAnalyzersAndStates.GetAllExistingDiagnosticStates(type, language); - - ProjectAnalyzersAndStates projectAnalyzersAndStates; - if (_projectAnalyzersAndStatesMap.TryGetValue(projectId, out projectAnalyzersAndStates) && - projectAnalyzersAndStates != null) - { - current = current.Concat(projectAnalyzersAndStates.GetAllExistingDiagnosticStates(type)); - } - - return current; - } - - public IEnumerable> GetAllExistingProviderAndIds(Project project) - { - var current = _sharedAnalyzersAndStates.GetAllProviderAndIds(project.Language); - - ProjectAnalyzersAndStates projectAnalyzersAndStates; - if (_projectAnalyzersAndStatesMap.TryGetValue(project.Id, out projectAnalyzersAndStates) && - projectAnalyzersAndStates != null) - { - current = current.Concat(projectAnalyzersAndStates.GetAllProviderAndIds()); - } - - return current; - } - - public async Task>> GetAllProviderAndIdsAsync(Project project, CancellationToken cancellationToken) - { - var current = _sharedAnalyzersAndStates.GetAllProviderAndIds(project.Language); - - var projectAnalyzersAndStates = await GetOrCreateProjectAnalyzersAndStatesAsync(project, cancellationToken).ConfigureAwait(false); - if (projectAnalyzersAndStates != null) - { - current = current.Concat(projectAnalyzersAndStates.GetAllProviderAndIds()); - } - - return current; - } - - public DiagnosticState GetOrCreateDiagnosticState(StateType stateType, ProviderId providerId, DiagnosticAnalyzer provider, ProjectId projectId, string language) - { - Contract.ThrowIfFalse(providerId >= 0); - - var sharedAnalyzersCount = _sharedAnalyzersAndStates.GetAnalyzerCount(language); - if (providerId < sharedAnalyzersCount) - { - return _sharedAnalyzersAndStates.GetOrCreateDiagnosticState(stateType, providerId, provider, language); - } - - ProjectAnalyzersAndStates projectAnalyzersAndStates; - if (!_projectAnalyzersAndStatesMap.TryGetValue(projectId, out projectAnalyzersAndStates) || - projectAnalyzersAndStates == null) - { - return null; - } - - return projectAnalyzersAndStates.GetOrCreateDiagnosticState(stateType, providerId, provider); - } - - internal static DiagnosticState GetOrCreateDiagnosticState(DiagnosticState[,] diagnosticStateMaps, StateType stateType, int providerIndex, ProviderId providerId, DiagnosticAnalyzer provider, string language) - { - Contract.ThrowIfFalse(providerIndex >= 0); - Contract.ThrowIfFalse(providerIndex < diagnosticStateMaps.GetLength(1)); - - if (diagnosticStateMaps[(int)stateType, providerIndex] == null) - { - var nameAndVersion = GetUniqueDiagnosticStateNameAndVersion(stateType, providerId, provider); - - var name = nameAndVersion.Item1; - var version = nameAndVersion.Item2; - diagnosticStateMaps[(int)stateType, providerIndex] = new DiagnosticState(name, version, language); - -#if DEBUG - // Ensure diagnostic state name is indeed unique. - foreach (var type in s_documentScopeStateTypes) - { - for (var pId = 0; pId < diagnosticStateMaps.GetLength(1); pId++) - { - if (diagnosticStateMaps[(int)type, pId] != null) - { - Contract.ThrowIfFalse(name != diagnosticStateMaps[(int)type, pId].Name || - (stateType == type && - (pId == providerIndex || language != diagnosticStateMaps[(int)type, pId].Language))); - } - } - } -#endif - } - - return diagnosticStateMaps[(int)stateType, providerIndex]; - } - - /// - /// Get the unique state name for the given {type, provider} tuple. - /// Note that this name is used by the underlying persistence stream of the corresponding to Read/Write diagnostic data into the stream. - /// If any two distinct {type, provider} tuples have the same diagnostic state name, we will end up sharing the persistence stream between them, leading to duplicate/missing/incorrect diagnostic data. - /// - private static ValueTuple GetUniqueDiagnosticStateNameAndVersion(StateType type, ProviderId providerId, DiagnosticAnalyzer provider) - { - Contract.ThrowIfNull(provider); - - // Get the unique ID for given diagnostic analyzer. - // note that we also put version stamp so that we can detect changed analyzer. - var tuple = HostAnalyzerManager.GetUniqueIdForAnalyzer(provider); - return ValueTuple.Create(UserDiagnosticsPrefixTableName + "_" + type.ToString() + "_" + tuple.Item1, tuple.Item2); - } - - public DiagnosticState GetDiagnosticState(StateType stateType, ProviderId providerId, ProjectId projectId, string language) - { - Contract.ThrowIfFalse(providerId >= 0); - - var sharedAnalyzersCount = _sharedAnalyzersAndStates.GetAnalyzerCount(language); - if (providerId < sharedAnalyzersCount) - { - return _sharedAnalyzersAndStates.GetDiagnosticState(stateType, providerId, language); - } - - ProjectAnalyzersAndStates projectAnalyzersAndStates; - if (!_projectAnalyzersAndStatesMap.TryGetValue(projectId, out projectAnalyzersAndStates) || - projectAnalyzersAndStates == null) - { - return null; - } - - return projectAnalyzersAndStates.GetDiagnosticState(stateType, providerId); - } - - public bool RemoveProjectAnalyzersAndStates(ProjectId projectId) - { - ProjectAnalyzersAndStates projectAnalyzersAndStates; - return _projectAnalyzersAndStatesMap.TryRemove(projectId, out projectAnalyzersAndStates); - } - - private async Task GetOrCreateProjectAnalyzersAndStatesAsync(Project project, CancellationToken cancellationToken) - { - // Update per-project analyzers if needed. - IEnumerable> removedStates; - ProjectAnalyzersAndStates projectAnalyzersAndStates = this.GetOrCreateProjectAnalyzersAndStates(project, out removedStates); - if (removedStates.Any()) - { - // Analyzers got updated, so clear the existing persistent states. - await _owner.RemoveCacheDataAsync(project, removedStates, cancellationToken).ConfigureAwait(false); - } - - return projectAnalyzersAndStates; - } - - private ProjectAnalyzersAndStates GetOrCreateProjectAnalyzersAndStates(Project project, - out IEnumerable> removedStates) - { - removedStates = SpecializedCollections.EmptyEnumerable>(); - - var newAnalyzersBuilder = ImmutableArray.CreateBuilder>>(); - foreach (var analyzerReference in project.AnalyzerReferences) - { - // Filter out duplicate analyzer references. - if (_sharedAnalyzersAndStates.HasAnalyzerReference(analyzerReference, project.Language)) - { - continue; - } - - // Get analyzers in the analyzer reference for the given project language. - // Filter out duplicate analyzers. - // NOTE: The HasAnalyzerReference check above to filter out duplicate analyzer references works only for duplicate AnalyzerFileReference with the same underlying assembly. - // However, we can also have AnalyzerImageReference, which might contain the same DiagnosticAnalyzer instance across different AnalyzerImageReference instances - // and we want to avoid duplicate analyzers for that case. Hence we apply the HasAnalyzer filter here. - var analyzers = analyzerReference.GetAnalyzers(project.Language) - .Where(a => !_sharedAnalyzersAndStates.HasAnalyzer(a, project.Language)); - - if (analyzers.Any()) - { - newAnalyzersBuilder.Add(KeyValuePair.Create(analyzerReference.Display, analyzers)); - } - } - - var newAnalyzers = newAnalyzersBuilder.ToImmutable(); - ProjectAnalyzersAndStates newProjectAnalyzersAndStates = null; - - ProjectAnalyzersAndStates currentProjectAnalyzersAndStates; - if (_projectAnalyzersAndStatesMap.TryGetValue(project.Id, out currentProjectAnalyzersAndStates)) - { - var newAnalyzersCount = newAnalyzers.Sum(kv => kv.Value.Count()); - if (currentProjectAnalyzersAndStates != null && currentProjectAnalyzersAndStates.AnalyzerCount == newAnalyzersCount) - { - Contract.ThrowIfFalse(currentProjectAnalyzersAndStates.AnalyzerCount > 0); - - // Project still has the same number of analyzers, does the saved projectAnalyzersAndStates has the same set of analyzers? - var hasSameAnalyzers = true; - foreach (var analyzerPair in newAnalyzers) - { - foreach (var analyzer in analyzerPair.Value) - { - if (!currentProjectAnalyzersAndStates.HasAnalyzer(analyzer)) - { - hasSameAnalyzers = false; - break; - } - } - } - - if (hasSameAnalyzers) - { - return currentProjectAnalyzersAndStates; - } - } - } - else - { - currentProjectAnalyzersAndStates = null; - } - - if (currentProjectAnalyzersAndStates != null) - { - removedStates = currentProjectAnalyzersAndStates.GetAllExistingDiagnosticStates(); - } - - var workspaceAnalyzersCount = _sharedAnalyzersAndStates.GetAnalyzerCount(project.Language); - newProjectAnalyzersAndStates = ProjectAnalyzersAndStates.CreateIfAnyAnalyzers(newAnalyzers, workspaceAnalyzersCount, project.Language); - - // this cache logic is completely broken. cache can't be used in this way. if we didn't have an issue before that is just purely by luck. - // the reason it is broken is that, this method can be called from any place (no central call path so, can't figure out all the possible paths, but I confirmed that there - // are at least more than one path) from any workspace snapshot from any thread but the cache doesn't have any versioning or snapshot check, - // but regardless, this code updates the cache. which introduce a race where if code uses the cache in 2 different places in the same method, the return value might be different. - // and that is causing us to crash since one gets two completely different state. - // - // I tried to figure out how to fix this but, eventually gave up since it is so deeply spread in the code, I don't see any way to properly fix this. - // my conculusion is ripping out this code and re-write this state management code. - - return _projectAnalyzersAndStatesMap.AddOrUpdate(project.Id, newProjectAnalyzersAndStates, (k, c) => newProjectAnalyzersAndStates); - } - } - } -} diff --git a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.IncrementalAnalyzer.AnalyzerExecutor.cs b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.IncrementalAnalyzer.AnalyzerExecutor.cs index a5cd6e9de7a0f..3303c3c3e36aa 100644 --- a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.IncrementalAnalyzer.AnalyzerExecutor.cs +++ b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.IncrementalAnalyzer.AnalyzerExecutor.cs @@ -9,8 +9,6 @@ namespace Microsoft.CodeAnalysis.Diagnostics.EngineV1 { - using ProviderId = Int32; - internal partial class DiagnosticIncrementalAnalyzer { /// @@ -19,7 +17,7 @@ internal partial class DiagnosticIncrementalAnalyzer /// /// this is not finished form. as refactoring going on, this class will become more stateless in respect to caller, and less dependency on owner. /// - internal class AnalyzerExecutor + private class AnalyzerExecutor { private readonly DiagnosticIncrementalAnalyzer _owner; @@ -28,15 +26,14 @@ public AnalyzerExecutor(DiagnosticIncrementalAnalyzer owner) _owner = owner; } - public async Task GetSyntaxAnalysisDataAsync( - DiagnosticAnalyzer provider, ProviderId providerId, VersionArgument versions, DiagnosticAnalyzerDriver analyzerDriver) + public async Task GetSyntaxAnalysisDataAsync(DiagnosticAnalyzerDriver analyzerDriver, StateSet stateSet, VersionArgument versions) { try { var document = analyzerDriver.Document; var cancellationToken = analyzerDriver.CancellationToken; - var state = AnalyzersAndState.GetOrCreateDiagnosticState(StateType.Syntax, providerId, provider, document.Project.Id, document.Project.Language); + var state = stateSet.GetState(StateType.Syntax); var existingData = await state.TryGetExistingDataAsync(document, cancellationToken).ConfigureAwait(false); if (CheckSyntaxVersions(document, existingData, versions)) @@ -44,7 +41,7 @@ public async Task GetSyntaxAnalysisDataAsync( return existingData; } - var diagnosticData = await GetSyntaxDiagnosticsAsync(providerId, provider, analyzerDriver).ConfigureAwait(false); + var diagnosticData = await GetSyntaxDiagnosticsAsync(analyzerDriver, stateSet.Analyzer).ConfigureAwait(false); return new AnalysisData(versions.TextVersion, versions.DataVersion, GetExistingItems(existingData), diagnosticData.AsImmutableOrEmpty()); } catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) @@ -53,15 +50,14 @@ public async Task GetSyntaxAnalysisDataAsync( } } - public async Task GetDocumentAnalysisDataAsync( - DiagnosticAnalyzer provider, ProviderId providerId, VersionArgument versions, DiagnosticAnalyzerDriver analyzerDrvier) + public async Task GetDocumentAnalysisDataAsync(DiagnosticAnalyzerDriver analyzerDrvier, StateSet stateSet, VersionArgument versions) { try { var document = analyzerDrvier.Document; var cancellationToken = analyzerDrvier.CancellationToken; - var state = AnalyzersAndState.GetOrCreateDiagnosticState(StateType.Document, providerId, provider, document.Project.Id, document.Project.Language); + var state = stateSet.GetState(StateType.Document); var existingData = await state.TryGetExistingDataAsync(document, cancellationToken).ConfigureAwait(false); if (CheckSemanticVersions(document, existingData, versions)) @@ -69,7 +65,7 @@ public async Task GetDocumentAnalysisDataAsync( return existingData; } - var diagnosticData = await GetSemanticDiagnosticsAsync(providerId, provider, analyzerDrvier).ConfigureAwait(false); + var diagnosticData = await GetSemanticDiagnosticsAsync(analyzerDrvier, stateSet.Analyzer).ConfigureAwait(false); return new AnalysisData(versions.TextVersion, versions.DataVersion, GetExistingItems(existingData), diagnosticData.AsImmutableOrEmpty()); } catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) @@ -79,7 +75,7 @@ public async Task GetDocumentAnalysisDataAsync( } public async Task GetDocumentBodyAnalysisDataAsync( - DiagnosticAnalyzer provider, ProviderId providerId, VersionArgument versions, DiagnosticAnalyzerDriver analyzerDriver, + StateSet stateSet, VersionArgument versions, DiagnosticAnalyzerDriver analyzerDriver, SyntaxNode root, SyntaxNode member, int memberId, bool supportsSemanticInSpan, MemberRangeMap.MemberRanges ranges) { try @@ -87,21 +83,21 @@ public async Task GetDocumentBodyAnalysisDataAsync( var document = analyzerDriver.Document; var cancellationToken = analyzerDriver.CancellationToken; - var state = AnalyzersAndState.GetOrCreateDiagnosticState(StateType.Document, providerId, provider, document.Project.Id, document.Project.Language); + var state = stateSet.GetState(StateType.Document); var existingData = await state.TryGetExistingDataAsync(document, cancellationToken).ConfigureAwait(false); ImmutableArray diagnosticData; if (supportsSemanticInSpan && CanUseDocumentState(existingData, ranges.TextVersion, versions.DataVersion)) { - var memberDxData = await GetSemanticDiagnosticsAsync(providerId, provider, analyzerDriver).ConfigureAwait(false); + var memberDxData = await GetSemanticDiagnosticsAsync(analyzerDriver, stateSet.Analyzer).ConfigureAwait(false); diagnosticData = _owner.UpdateDocumentDiagnostics(existingData, ranges.Ranges, memberDxData.AsImmutableOrEmpty(), root.SyntaxTree, member, memberId); - ValidateMemberDiagnostics(providerId, provider, document, root, diagnosticData); + ValidateMemberDiagnostics(stateSet.Analyzer, document, root, diagnosticData); } else { // if we can't re-use existing document state, only option we have is updating whole document state here. - var dx = await GetSemanticDiagnosticsAsync(providerId, provider, analyzerDriver).ConfigureAwait(false); + var dx = await GetSemanticDiagnosticsAsync(analyzerDriver, stateSet.Analyzer).ConfigureAwait(false); diagnosticData = dx.AsImmutableOrEmpty(); } @@ -113,15 +109,14 @@ public async Task GetDocumentBodyAnalysisDataAsync( } } - public async Task GetProjectAnalysisDataAsync( - DiagnosticAnalyzer provider, ProviderId providerId, VersionArgument versions, DiagnosticAnalyzerDriver analyzerDriver) + public async Task GetProjectAnalysisDataAsync(DiagnosticAnalyzerDriver analyzerDriver, StateSet stateSet, VersionArgument versions) { try { var project = analyzerDriver.Project; var cancellationToken = analyzerDriver.CancellationToken; - var state = AnalyzersAndState.GetOrCreateDiagnosticState(StateType.Project, providerId, provider, project.Id, project.Language); + var state = stateSet.GetState(StateType.Project); var existingData = await state.TryGetExistingDataAsync(project, cancellationToken).ConfigureAwait(false); if (CheckSemanticVersions(project, existingData, versions)) @@ -130,7 +125,7 @@ public async Task GetProjectAnalysisDataAsync( } // TODO: remove ForceAnalyzeAllDocuments at some point - var diagnosticData = await GetProjectDiagnosticsAsync(providerId, provider, analyzerDriver, _owner.ForceAnalyzeAllDocuments).ConfigureAwait(false); + var diagnosticData = await GetProjectDiagnosticsAsync(analyzerDriver, stateSet.Analyzer, _owner.ForceAnalyzeAllDocuments).ConfigureAwait(false); return new AnalysisData(VersionStamp.Default, versions.DataVersion, GetExistingItems(existingData), diagnosticData.AsImmutableOrEmpty()); } catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) @@ -139,11 +134,6 @@ public async Task GetProjectAnalysisDataAsync( } } - private DiagnosticAnalyzersAndStates AnalyzersAndState - { - get { return _owner._analyzersAndState; } - } - private bool CanUseDocumentState(AnalysisData existingData, VersionStamp textVersion, VersionStamp dataVersion) { if (existingData == null) @@ -166,11 +156,11 @@ private static ImmutableArray GetExistingItems(AnalysisData exis } [Conditional("DEBUG")] - private void ValidateMemberDiagnostics(ProviderId providerId, DiagnosticAnalyzer provider, Document document, SyntaxNode root, ImmutableArray diagnostics) + private void ValidateMemberDiagnostics(DiagnosticAnalyzer analyzer, Document document, SyntaxNode root, ImmutableArray diagnostics) { #if RANGE var documentBasedDriver = new DiagnosticAnalyzerDriver(document, root.FullSpan, root, CancellationToken.None); - var expected = GetSemanticDiagnosticsAsync(providerId, provider, documentBasedDriver).WaitAndGetResult(documentBasedDriver.CancellationToken) ?? SpecializedCollections.EmptyEnumerable(); + var expected = GetSemanticDiagnosticsAsync(documentBasedDriver, analyzer).WaitAndGetResult(documentBasedDriver.CancellationToken) ?? SpecializedCollections.EmptyEnumerable(); Contract.Requires(diagnostics.SetEquals(expected)); #endif } diff --git a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.NestedTypes.cs b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.NestedTypes.cs index 848e09c76e24f..22895a30f511d 100644 --- a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.NestedTypes.cs +++ b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.NestedTypes.cs @@ -83,13 +83,13 @@ public VersionArgument(VersionStamp textVersion, VersionStamp dataVersion, Versi public class ArgumentKey { - public readonly int ProviderId; + public readonly DiagnosticAnalyzer Analyzer; public readonly StateType StateTypeId; public readonly object Key; - public ArgumentKey(int providerId, StateType stateTypeId, object key) + public ArgumentKey(DiagnosticAnalyzer analyzer, StateType stateTypeId, object key) { - this.ProviderId = providerId; + this.Analyzer = analyzer; this.StateTypeId = stateTypeId; this.Key = key; } @@ -102,12 +102,12 @@ public override bool Equals(object obj) return false; } - return ProviderId == other.ProviderId && StateTypeId == other.StateTypeId && Key == other.Key; + return Analyzer == other.Analyzer && StateTypeId == other.StateTypeId && Key == other.Key; } public override int GetHashCode() { - return Hash.Combine(Key, Hash.Combine(ProviderId, (int)StateTypeId)); + return Hash.Combine(Key, Hash.Combine(Analyzer.GetHashCode(), (int)StateTypeId)); } } } diff --git a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.ProjectAnalyzersAndStates.cs b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.ProjectAnalyzersAndStates.cs deleted file mode 100644 index 7e38c517610f2..0000000000000 --- a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.ProjectAnalyzersAndStates.cs +++ /dev/null @@ -1,139 +0,0 @@ -// 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 Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Diagnostics.EngineV1 -{ - using ProviderId = Int32; - - internal partial class DiagnosticIncrementalAnalyzer - { - private partial class DiagnosticAnalyzersAndStates - { - /// - /// Maintains per-project diagnostic analyzers and the corresponding diagnostic states. - /// - private class ProjectAnalyzersAndStates - { - private readonly ImmutableDictionary> _analyzerIdMap; - private readonly DiagnosticState[,] _diagnosticStateMaps; - private readonly int _startAnalyzerId; - private readonly int _analyzerCount; - private readonly string _projectLanguage; - - public static ProjectAnalyzersAndStates CreateIfAnyAnalyzers(IEnumerable>> projectSpecificAnalyzers, int sharedWorkspaceAnalyzersCount, string projectLanguage) - { - // Make sure we have at least one analyzer. - if (projectSpecificAnalyzers == null || !projectSpecificAnalyzers.Any()) - { - return null; - } - - return new ProjectAnalyzersAndStates(projectSpecificAnalyzers, sharedWorkspaceAnalyzersCount, projectLanguage); - } - - private ProjectAnalyzersAndStates(IEnumerable>> projectSpecificAnalyzers, int sharedWorkspaceAnalyzersCount, string projectLanguage) - { - Contract.ThrowIfFalse(projectSpecificAnalyzers != null && projectSpecificAnalyzers.Any()); - - _startAnalyzerId = sharedWorkspaceAnalyzersCount; - _projectLanguage = projectLanguage; - _analyzerIdMap = CreateAnalyzerIdMap(projectSpecificAnalyzers, _startAnalyzerId); - _analyzerCount = _analyzerIdMap.Values.Flatten().Count(); - Contract.ThrowIfFalse(_analyzerCount > 0); - - _diagnosticStateMaps = new DiagnosticState[s_stateTypeCount, _analyzerCount]; - } - - private static ImmutableDictionary> CreateAnalyzerIdMap( - IEnumerable>> projectSpecificAnalyzers, int startAnalyzerId) - { - var index = startAnalyzerId; - var map = ImmutableDictionary.CreateBuilder>(); - - foreach (var analyzerList in projectSpecificAnalyzers) - { - var analyzerMap = ImmutableDictionary.CreateBuilder(); - - foreach (var analyzer in analyzerList.Value) - { - Contract.ThrowIfNull(analyzer); - analyzerMap.Add(analyzer, index++); - } - - if (map.ContainsKey(analyzerList.Key)) - { - map[analyzerList.Key] = map[analyzerList.Key].AddRange(analyzerMap); - } - else - { - map.Add(analyzerList.Key, analyzerMap.ToImmutable()); - } - } - - return map.ToImmutable(); - } - - public int AnalyzerCount - { - get - { - Contract.ThrowIfFalse(_analyzerCount > 0); - return _analyzerCount; - } - } - - public IEnumerable> GetAllExistingDiagnosticStates() - { - var current = SpecializedCollections.EmptyEnumerable>(); - foreach (var type in s_documentScopeStateTypes) - { - current = current.Concat(GetAllExistingDiagnosticStates(type)); - } - - return current; - } - - public IEnumerable> GetAllExistingDiagnosticStates(StateType type) - { - foreach (var analyzerAndId in _analyzerIdMap.Values.Flatten()) - { - var state = _diagnosticStateMaps[(int)type, analyzerAndId.Value - _startAnalyzerId]; - yield return Tuple.Create(state, analyzerAndId.Value, type); - } - } - - public IEnumerable> GetAllProviderAndIds() - { - return _analyzerIdMap.Values.Flatten(); - } - - public DiagnosticState GetOrCreateDiagnosticState(StateType stateType, ProviderId providerId, DiagnosticAnalyzer provider) - { - Contract.ThrowIfFalse(providerId >= _startAnalyzerId); - Contract.ThrowIfFalse(providerId < _startAnalyzerId + this.AnalyzerCount); - - return DiagnosticAnalyzersAndStates.GetOrCreateDiagnosticState(_diagnosticStateMaps, stateType, providerId - _startAnalyzerId, providerId, provider, _projectLanguage); - } - - public DiagnosticState GetDiagnosticState(StateType stateType, ProviderId providerId) - { - Contract.ThrowIfFalse(providerId >= _startAnalyzerId); - Contract.ThrowIfFalse(providerId < _startAnalyzerId + this.AnalyzerCount); - - return _diagnosticStateMaps[(int)stateType, providerId - _startAnalyzerId]; - } - - public bool HasAnalyzer(DiagnosticAnalyzer analyzer) - { - Contract.ThrowIfNull(analyzer); - return _analyzerIdMap.Values.Any(dict => dict.ContainsKey(analyzer)); - } - } - } - } -} diff --git a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.HostStates.cs b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.HostStates.cs new file mode 100644 index 0000000000000..163b74b01b12c --- /dev/null +++ b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.HostStates.cs @@ -0,0 +1,71 @@ +// 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.Diagnostics; +using System.Linq; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Diagnostics.EngineV1 +{ + internal partial class DiagnosticIncrementalAnalyzer + { + private partial class StateManager + { + /// + /// This class is responsible for anything related to for host level s. + /// + private class HostStates + { + private readonly StateManager _owner; + + private ImmutableDictionary> _stateMap; + + public HostStates(StateManager owner) + { + _owner = owner; + _stateMap = ImmutableDictionary>.Empty; + } + + public IEnumerable GetStateSets() + { + return _stateMap.Values.SelectMany(v => v.Values); + } + + public IEnumerable GetOrCreateStateSets(string language) + { + var map = GetAnalyzerMap(language); + return map.Values; + } + + public StateSet GetOrCreateStateSet(string language, DiagnosticAnalyzer analyzer) + { + var map = GetAnalyzerMap(language); + + StateSet set; + if (map.TryGetValue(analyzer, out set)) + { + return set; + } + + return null; + } + + private ImmutableDictionary GetAnalyzerMap(string language) + { + return ImmutableInterlocked.GetOrAdd(ref _stateMap, language, CreateLanguageSpecificAnalyzerMap, this); + } + + private ImmutableDictionary CreateLanguageSpecificAnalyzerMap(string language, HostStates @this) + { + var analyzersPerReference = _owner.AnalyzerManager.GetHostDiagnosticAnalyzersPerReference(language); + + var analyzerMap = CreateAnalyzerMap(language, analyzersPerReference.Values); + VerifyDiagnosticStates(analyzerMap.Values); + + return analyzerMap; + } + } + } + } +} diff --git a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs new file mode 100644 index 0000000000000..6f171aa17ca6d --- /dev/null +++ b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs @@ -0,0 +1,243 @@ +// 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.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Diagnostics.EngineV1 +{ + internal partial class DiagnosticIncrementalAnalyzer + { + private partial class StateManager + { + /// + /// This class is responsible for anything related to for project level s. + /// + private class ProjectStates + { + private readonly StateManager _owner; + private readonly ConcurrentDictionary _stateMap; + + public ProjectStates(StateManager owner) + { + _owner = owner; + _stateMap = new ConcurrentDictionary(concurrencyLevel: 2, capacity: 10); + } + + public IEnumerable GetStateSets(ProjectId projectId) + { + var map = GetCachedAnalyzerMap(projectId); + return map.Values; + } + + public IEnumerable GetOrUpdateStateSets(Project project) + { + var map = GetOrUpdateAnalyzerMap(project); + return map.Values; + } + + public IEnumerable GetOrCreateStateSets(Project project) + { + var map = GetOrCreateAnalyzerMap(project); + return map.Values; + } + + public StateSet GetOrCreateStateSet(Project project, DiagnosticAnalyzer analyzer) + { + var map = GetOrCreateAnalyzerMap(project); + + StateSet set; + if (map.TryGetValue(analyzer, out set)) + { + return set; + } + + return null; + } + + public void RemoveStateSet(ProjectId projectId) + { + if (projectId == null) + { + return; + } + + Entry unused; + _stateMap.TryRemove(projectId, out unused); + } + + private ImmutableDictionary GetOrUpdateAnalyzerMap(Project project) + { + var map = GetAnalyzerMap(project); + if (map != null) + { + return map; + } + + var newAnalyzersPerReference = _owner.AnalyzerManager.CreateProjectDiagnosticAnalyzersPerReference(project); + var newMap = StateManager.CreateAnalyzerMap(project.Language, newAnalyzersPerReference.Values); + + RaiseProjectAnalyzerReferenceChangedIfNeeded(project, newAnalyzersPerReference, newMap); + + // update cache. + // add and update is same since this method will not be called concurrently. + var entry = _stateMap.AddOrUpdate(project.Id, + _ => new Entry(project.AnalyzerReferences, newAnalyzersPerReference, newMap), (_1, _2) => new Entry(project.AnalyzerReferences, newAnalyzersPerReference, newMap)); + + VerifyDiagnosticStates(entry.AnalyzerMap.Values); + + return entry.AnalyzerMap; + } + + private ImmutableDictionary GetCachedAnalyzerMap(ProjectId projectId) + { + Entry entry; + if (_stateMap.TryGetValue(projectId, out entry)) + { + return entry.AnalyzerMap; + } + + return ImmutableDictionary.Empty; + } + + private ImmutableDictionary GetOrCreateAnalyzerMap(Project project) + { + // if we can't use cached one, we will create a new analyzer map. which is a bit of waste since + // we will create new StateSet for all analyzers. but since this only happens when project analyzer references + // are changed, I believe it is acceptable to have a bit of waste for simplicity. + return GetAnalyzerMap(project) ?? CreateAnalyzerMap(project); + } + + private ImmutableDictionary GetAnalyzerMap(Project project) + { + Entry entry; + if (_stateMap.TryGetValue(project.Id, out entry) && entry.AnalyzerReferences.Equals(project.AnalyzerReferences)) + { + return entry.AnalyzerMap; + } + + return null; + } + + private ImmutableDictionary CreateAnalyzerMap(Project project) + { + if (project.AnalyzerReferences.Count == 0) + { + return ImmutableDictionary.Empty; + } + + var analyzersPerReference = _owner.AnalyzerManager.CreateProjectDiagnosticAnalyzersPerReference(project); + if (analyzersPerReference.Count == 0) + { + return ImmutableDictionary.Empty; + } + + return StateManager.CreateAnalyzerMap(project.Language, analyzersPerReference.Values); + } + + private void RaiseProjectAnalyzerReferenceChangedIfNeeded( + Project project, ImmutableDictionary> newMapPerReference, ImmutableDictionary newMap) + { + Entry entry; + if (!_stateMap.TryGetValue(project.Id, out entry)) + { + // no previous references and we still don't have any references + if (newMap.Count == 0) + { + return; + } + + // new reference added + _owner.RaiseProjectAnalyzerReferenceChanged( + new ProjectAnalyzerReferenceChangedEventArgs(project, newMap.Values.ToImmutableArrayOrEmpty(), ImmutableArray.Empty)); + return; + } + + Contract.Requires(!entry.AnalyzerReferences.Equals(project.AnalyzerReferences)); + + // there has been change. find out what has changed + var addedStates = DiffStateSets(project.AnalyzerReferences.Except(entry.AnalyzerReferences), newMapPerReference, newMap); + var removedStates = DiffStateSets(entry.AnalyzerReferences.Except(project.AnalyzerReferences), entry.MapPerReferences, entry.AnalyzerMap); + + // nothing has changed + if (addedStates.Length == 0 && removedStates.Length == 0) + { + return; + } + + _owner.RaiseProjectAnalyzerReferenceChanged( + new ProjectAnalyzerReferenceChangedEventArgs(project, addedStates, removedStates)); + } + + private ImmutableArray DiffStateSets( + IEnumerable references, + ImmutableDictionary> mapPerReference, + ImmutableDictionary map) + { + if (mapPerReference.Count == 0 || map.Count == 0) + { + // nothing to diff + return ImmutableArray.Empty; + } + + var builder = ImmutableArray.CreateBuilder(); + foreach (var reference in references) + { + var referenceIdentity = _owner.AnalyzerManager.GetAnalyzerReferenceIdentity(reference); + + // check duplication + ImmutableArray analyzers; + if (!mapPerReference.TryGetValue(referenceIdentity, out analyzers)) + { + continue; + } + + // okay, this is real reference. get stateset + foreach (var analyzer in analyzers) + { + StateSet set; + if (!map.TryGetValue(analyzer, out set)) + { + continue; + } + + builder.Add(set); + } + } + + return builder.ToImmutable(); + } + + [Conditional("DEBUG")] + private void VerifyDiagnosticStates(IEnumerable stateSets) + { + StateManager.VerifyDiagnosticStates(_owner._hostStates.GetStateSets().Concat(stateSets)); + } + + private struct Entry + { + public readonly IReadOnlyList AnalyzerReferences; + public readonly ImmutableDictionary> MapPerReferences; + public readonly ImmutableDictionary AnalyzerMap; + + public Entry( + IReadOnlyList analyzerReferences, + ImmutableDictionary> mapPerReferences, + ImmutableDictionary analyzerMap) + { + Contract.ThrowIfNull(analyzerReferences); + Contract.ThrowIfNull(mapPerReferences); + Contract.ThrowIfNull(analyzerMap); + + AnalyzerReferences = analyzerReferences; + MapPerReferences = mapPerReferences; + AnalyzerMap = analyzerMap; + } + } + } + } + } +} diff --git a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.cs b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.cs index e3500e6a393ee..a9d885fc5d942 100644 --- a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.cs +++ b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.cs @@ -3,42 +3,233 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Collections.ObjectModel; -using System.IO; +using System.Diagnostics; using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.SolutionCrawler.State; -using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics.EngineV1 { internal partial class DiagnosticIncrementalAnalyzer { - internal class StateManager + private static readonly int s_stateTypeCount = Enum.GetNames(typeof(StateType)).Count(); + + /// + /// This is in charge of anything related to + /// + private partial class StateManager { - private static readonly int s_stateTypeCount = Enum.GetNames(typeof(StateType)).Count(); - private static readonly ImmutableArray s_documentScopeStateTypes = ImmutableArray.Create(StateType.Syntax, StateType.Document); + private readonly HostAnalyzerManager _analyzerManager; + + private readonly HostStates _hostStates; + private readonly ProjectStates _projectStates; + + public StateManager(HostAnalyzerManager analyzerManager) + { + _analyzerManager = analyzerManager; + + _hostStates = new HostStates(this); + _projectStates = new ProjectStates(this); + } + + private HostAnalyzerManager AnalyzerManager { get { return _analyzerManager; } } + + /// + /// This will be raised whenever finds change + /// + public event EventHandler ProjectAnalyzerReferenceChanged; + + /// + /// Return s for the given . + /// This will never create new but will return ones already created. + /// + public IEnumerable GetStateSets(ProjectId projectId) + { + return _hostStates.GetStateSets().Concat(_projectStates.GetStateSets(projectId)); + } + + /// + /// Return s for the given . + /// This will never create new but will return ones already created. + /// Difference with is that + /// this will only return s that have same language as . + /// + public IEnumerable GetStateSets(Project project) + { + return GetStateSets(project.Id).Where(s => s.Language == project.Language); + } + + /// + /// Return s for the given . + /// This will either return already created s for the specific snapshot of or + /// It will create new s for the and update internal state. + /// + /// since this has a side-effect, this should never be called concurrently. and incremental analyzer (solution crawler) should guarantee that. + /// + public IEnumerable GetOrUpdateStateSets(Project project) + { + return _hostStates.GetOrCreateStateSets(project.Language).Concat(_projectStates.GetOrUpdateStateSets(project)); + } + + /// + /// Return s for the given . + /// This will either return already created s for the specific snapshot of or + /// It will create new s for the . + /// Unlike , this has no side effect. + /// + public IEnumerable GetOrCreateStateSets(Project project) + { + return _hostStates.GetOrCreateStateSets(project.Language).Concat(_projectStates.GetOrCreateStateSets(project)); + } + + /// + /// Return for the given in the context of . + /// This will either return already created for the specific snapshot of or + /// It will create new for the . + /// This will not have any side effect. + /// + public StateSet GetOrCreateStateSet(Project project, DiagnosticAnalyzer analyzer) + { + var stateSet = _hostStates.GetOrCreateStateSet(project.Language, analyzer); + if (stateSet != null) + { + return stateSet; + } + + return _projectStates.GetOrCreateStateSet(project, analyzer); + } - private readonly ConditionalWeakTable _stateMap; + /// + /// Remove given . + /// + public void RemoveStateSet(ProjectId projectId) + { + _projectStates.RemoveStateSet(projectId); + } - public StateManager() + private void RaiseProjectAnalyzerReferenceChanged(ProjectAnalyzerReferenceChangedEventArgs args) { - _stateMap = new ConditionalWeakTable(); + ProjectAnalyzerReferenceChanged?.Invoke(this, args); } - public DiagnosticState GetState(DiagnosticAnalyzer analyzer, StateType statetype) + private static ImmutableDictionary CreateAnalyzerMap( + string language, IEnumerable> analyzerCollection) { - return _stateMap.GetValue(analyzer, CreateAnalyzerStates)[(int)statetype]; + var builder = ImmutableDictionary.CreateBuilder(); + foreach (var analyzers in analyzerCollection) + { + foreach (var analyzer in analyzers) + { + // TODO: + // #1, all de -duplication should move to HostAnalyzerManager + // #2, not sure whether de-duplicatoin of analyzer itself makes sense. this can only happen + // if user delibrately put same analyzer twice. + if (builder.ContainsKey(analyzer)) + { + continue; + } + + builder.Add(analyzer, new StateSet(language, analyzer)); + } + } + + return builder.ToImmutable(); } - private DiagnosticState[] CreateAnalyzerStates(DiagnosticAnalyzer unused) + [Conditional("DEBUG")] + private static void VerifyDiagnosticStates(IEnumerable stateSets) { - return new DiagnosticState[s_stateTypeCount]; + // Ensure diagnostic state name is indeed unique. + for (var i = 0; i < s_stateTypeCount; i++) + { + var set = new HashSet>(); + + foreach (var stateSet in stateSets) + { + var state = stateSet.GetState((StateType)i); + + Contract.Requires(set.Add(ValueTuple.Create(state.Language, state.Name))); + } + } + } + } + + private class ProjectAnalyzerReferenceChangedEventArgs : EventArgs + { + public readonly Project Project; + public readonly ImmutableArray Added; + public readonly ImmutableArray Removed; + + public ProjectAnalyzerReferenceChangedEventArgs(Project project, ImmutableArray added, ImmutableArray removed) + { + Project = project; + Added = added; + Removed = removed; + } + } + + private class StateSet + { + private const string UserDiagnosticsPrefixTableName = ""; + + private readonly string _language; + private readonly DiagnosticAnalyzer _analyzer; + + private readonly DiagnosticState[] _state; + + public StateSet(string language, DiagnosticAnalyzer analyzer) + { + _language = language; + _analyzer = analyzer; + + _state = CreateDiagnosticStates(language, analyzer); + } + + public string Language => _language; + public DiagnosticAnalyzer Analyzer => _analyzer; + + public DiagnosticState GetState(StateType stateType) + { + return _state[(int)stateType]; + } + + public void Remove(object key) + { + for (var stateType = 0; stateType < s_stateTypeCount; stateType++) + { + _state[stateType].Remove(key); + } + } + + private static DiagnosticState[] CreateDiagnosticStates(string language, DiagnosticAnalyzer analyzer) + { + var states = new DiagnosticState[s_stateTypeCount]; + + for (int stateType = 0; stateType < s_stateTypeCount; stateType++) + { + var nameAndVersion = GetNameAndVersion(analyzer, (StateType)stateType); + + var name = nameAndVersion.Item1; + var version = nameAndVersion.Item2; + + states[stateType] = new DiagnosticState(name, version, language); + } + + return states; + } + + /// + /// Get the unique state name for the given {type, provider} tuple. + /// Note that this name is used by the underlying persistence stream of the corresponding to Read/Write diagnostic data into the stream. + /// If any two distinct {type, provider} tuples have the same diagnostic state name, we will end up sharing the persistence stream between them, leading to duplicate/missing/incorrect diagnostic data. + /// + private static ValueTuple GetNameAndVersion(DiagnosticAnalyzer analyzer, StateType type) + { + 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.GetUniqueIdForAnalyzer(); + return ValueTuple.Create(UserDiagnosticsPrefixTableName + "_" + type.ToString() + "_" + tuple.Item1, tuple.Item2); } } } diff --git a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.WorkspaceAnalyzersAndStates.PerLanguageAnalyzersAndStates.cs b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.WorkspaceAnalyzersAndStates.PerLanguageAnalyzersAndStates.cs deleted file mode 100644 index d68585ddd6ea6..0000000000000 --- a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.WorkspaceAnalyzersAndStates.PerLanguageAnalyzersAndStates.cs +++ /dev/null @@ -1,126 +0,0 @@ -// 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 Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Diagnostics.EngineV1 -{ - using ProviderId = Int32; - - internal partial class DiagnosticIncrementalAnalyzer - { - private partial class DiagnosticAnalyzersAndStates - { - private partial class WorkspaceAnalyzersAndStates - { - /// - /// Maintains all workspace diagnostic analyzers (with diagnostic states) for a specific language. - /// - private class PerLanguageAnalyzersAndStates - { - private readonly string _language; - private readonly ImmutableDictionary> _diagnosticAnalyzerIdMap; - private readonly int _analyzerCount; - private readonly DiagnosticState[,] _diagnosticStateMaps; - - public PerLanguageAnalyzersAndStates(HostAnalyzerManager workspaceAnalyzerManager, string language) - { - _language = language; - - // TODO: dynamically re-order providers so that cheap one runs first and slower runs later. - _diagnosticAnalyzerIdMap = CreateAnalyzerIdMap(workspaceAnalyzerManager, language); - _analyzerCount = _diagnosticAnalyzerIdMap.Values.Flatten().Count(); - _diagnosticStateMaps = new DiagnosticState[s_stateTypeCount, _analyzerCount]; - } - - private static ImmutableDictionary> CreateAnalyzerIdMap( - HostAnalyzerManager workspaceAnalyzerManager, string language) - { - var index = 0; - var map = ImmutableDictionary.CreateBuilder>(); - - foreach (var kv in workspaceAnalyzerManager.GetHostDiagnosticAnalyzersPerReference(language)) - { - var perAnalyzerMap = ImmutableDictionary.CreateBuilder(); - - var referenceIdentity = kv.Key; - var perLanguageAnalyzers = kv.Value; - - if (perLanguageAnalyzers.Length > 0) - { - foreach (var analyzer in perLanguageAnalyzers) - { - var analyzerId = index++; - perAnalyzerMap.Add(analyzer, analyzerId); - } - - map.Add(referenceIdentity, perAnalyzerMap.ToImmutable()); - } - } - - return map.ToImmutable(); - } - - public int AnalyzerCount - { - get { return _analyzerCount; } - } - - public bool HasAnalyzerReference(AnalyzerReference analyzerReference) - { - Contract.ThrowIfNull(analyzerReference); - - if (analyzerReference is AnalyzerFileReference) - { - // Filter out duplicate analyzer references with same assembly name/full path. - return analyzerReference.Display != null && _diagnosticAnalyzerIdMap.ContainsKey(analyzerReference.Display); - } - else - { - // For non-file references, we will check individual DiagnosticAnalyzer instances for duplicates. - return false; - } - } - - public bool HasAnalyzer(DiagnosticAnalyzer analyzer) - { - Contract.ThrowIfNull(analyzer); - return _diagnosticAnalyzerIdMap.Values.Any(dict => dict.ContainsKey(analyzer)); - } - - public IEnumerable> GetAllExistingDiagnosticStates(StateType type) - { - foreach (var analyzerAndId in _diagnosticAnalyzerIdMap.Values.Flatten()) - { - yield return Tuple.Create(_diagnosticStateMaps[(int)type, analyzerAndId.Value], analyzerAndId.Value, type); - } - } - - public IEnumerable> GetAllProviderAndIds() - { - return _diagnosticAnalyzerIdMap.Values.Flatten(); - } - - public DiagnosticState GetOrCreateDiagnosticState(StateType stateType, ProviderId providerId, DiagnosticAnalyzer provider) - { - Contract.ThrowIfFalse(providerId >= 0); - Contract.ThrowIfFalse(providerId < this.AnalyzerCount); - - return DiagnosticAnalyzersAndStates.GetOrCreateDiagnosticState(_diagnosticStateMaps, stateType, providerId, providerId, provider, _language); - } - - public DiagnosticState GetDiagnosticState(StateType stateType, ProviderId providerId) - { - Contract.ThrowIfFalse(providerId >= 0); - Contract.ThrowIfFalse(providerId < this.AnalyzerCount); - - return _diagnosticStateMaps[(int)stateType, providerId]; - } - } - } - } - } -} diff --git a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.WorkspaceAnalyzersAndStates.cs b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.WorkspaceAnalyzersAndStates.cs deleted file mode 100644 index 56cc472317938..0000000000000 --- a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.WorkspaceAnalyzersAndStates.cs +++ /dev/null @@ -1,127 +0,0 @@ -// 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 Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Diagnostics.EngineV1 -{ - using ProviderId = Int32; - - internal partial class DiagnosticIncrementalAnalyzer - { - private partial class DiagnosticAnalyzersAndStates - { - /// - /// Maintains all workspace diagnostic analyzers (with diagnostic states), which are enabled for all projects in the workspace. - /// - private partial class WorkspaceAnalyzersAndStates - { - private readonly HostAnalyzerManager _workspaceAnalyzerManager; - private ImmutableDictionary _perLanguageAnalyzersAndStatesMap; - - public WorkspaceAnalyzersAndStates(HostAnalyzerManager workspaceAnalyzerManager) - { - _workspaceAnalyzerManager = workspaceAnalyzerManager; - _perLanguageAnalyzersAndStatesMap = ImmutableDictionary.Empty; - } - - private static PerLanguageAnalyzersAndStates CreatePerLanguageAnalyzersAndStates(string language, WorkspaceAnalyzersAndStates @this) - { - return new PerLanguageAnalyzersAndStates(@this._workspaceAnalyzerManager, language); - } - - private PerLanguageAnalyzersAndStates GetOrCreatePerLanguageAnalyzersAndStates(string language) - { - return ImmutableInterlocked.GetOrAdd(ref _perLanguageAnalyzersAndStatesMap, language, CreatePerLanguageAnalyzersAndStates, this); - } - - public int GetAnalyzerCount(string language) - { - var count = _workspaceAnalyzerManager.GetHostAnalyzerCount(language); - -#if DEBUG - var analyzersAndStates = this.GetOrCreatePerLanguageAnalyzersAndStates(language); - Contract.Requires(count == analyzersAndStates.AnalyzerCount); -#endif - - return count; - } - - public bool HasAnalyzerReference(AnalyzerReference analyzerReference, string language) - { - var analyzersAndStates = this.GetOrCreatePerLanguageAnalyzersAndStates(language); - return analyzersAndStates.HasAnalyzerReference(analyzerReference); - } - - public bool HasAnalyzer(DiagnosticAnalyzer analyzer, string language) - { - if (analyzer == null) - { - return false; - } - - var analyzersAndStates = this.GetOrCreatePerLanguageAnalyzersAndStates(language); - return analyzersAndStates.HasAnalyzer(analyzer); - } - - public IEnumerable> GetAllExistingDiagnosticStates(string languageOpt) - { - var current = SpecializedCollections.EmptyEnumerable>(); - foreach (var type in s_documentScopeStateTypes) - { - current = current.Concat(GetAllExistingDiagnosticStates(type, languageOpt)); - } - - return current; - } - - public IEnumerable> GetAllExistingDiagnosticStates(StateType type, string languageOpt) - { - if (languageOpt != null) - { - PerLanguageAnalyzersAndStates analyzersStates; - if (_perLanguageAnalyzersAndStatesMap.TryGetValue(languageOpt, out analyzersStates)) - { - return analyzersStates.GetAllExistingDiagnosticStates(type); - } - else - { - return SpecializedCollections.EmptyEnumerable>(); - } - } - - // This might be a removed or closed document/project/solution. - // Return all existing states. - var current = SpecializedCollections.EmptyEnumerable>(); - foreach (var analyzersAndStates in _perLanguageAnalyzersAndStatesMap.Values) - { - current = current.Concat(analyzersAndStates.GetAllExistingDiagnosticStates(type)); - } - - return current; - } - - public IEnumerable> GetAllProviderAndIds(string language) - { - var analyzersAndStates = this.GetOrCreatePerLanguageAnalyzersAndStates(language); - return analyzersAndStates.GetAllProviderAndIds(); - } - - public DiagnosticState GetOrCreateDiagnosticState(StateType stateType, ProviderId providerId, DiagnosticAnalyzer provider, string language) - { - var analyzersAndStates = this.GetOrCreatePerLanguageAnalyzersAndStates(language); - return analyzersAndStates.GetOrCreateDiagnosticState(stateType, providerId, provider); - } - - public DiagnosticState GetDiagnosticState(StateType stateType, ProviderId providerId, string language) - { - var analyzersAndStates = this.GetOrCreatePerLanguageAnalyzersAndStates(language); - return analyzersAndStates.GetDiagnosticState(stateType, providerId); - } - } - } - } -} diff --git a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.cs b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.cs index 50b781442933a..25c9d7b46b867 100644 --- a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.cs +++ b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.cs @@ -14,44 +14,59 @@ using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Options; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Versions; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics.EngineV1 { - using ProviderId = Int32; - internal partial class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncrementalAnalyzer { - private static readonly int s_stateTypeCount = Enum.GetNames(typeof(StateType)).Count(); private static readonly ImmutableArray s_documentScopeStateTypes = ImmutableArray.Create(StateType.Syntax, StateType.Document); private readonly int _correlationId; private readonly DiagnosticAnalyzerService _owner; private readonly MemberRangeMap _memberRangeMap; - private readonly DiagnosticAnalyzersAndStates _analyzersAndState; private readonly AnalyzerExecutor _executor; + private readonly StateManager _stateManger; + private readonly SimpleTaskQueue _eventQueue; private DiagnosticLogAggregator _diagnosticLogAggregator; public DiagnosticIncrementalAnalyzer( - DiagnosticAnalyzerService owner, - int correlationId, - Workspace workspace, - HostAnalyzerManager workspaceAnalyzerManager, + DiagnosticAnalyzerService owner, + int correlationId, + Workspace workspace, + HostAnalyzerManager analyzerManager, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource) : base(workspace, hostDiagnosticUpdateSource) { _owner = owner; _correlationId = correlationId; _memberRangeMap = new MemberRangeMap(); - _analyzersAndState = new DiagnosticAnalyzersAndStates(this, workspace, workspaceAnalyzerManager); _executor = new AnalyzerExecutor(this); - + _eventQueue = new SimpleTaskQueue(TaskScheduler.Default); + + _stateManger = new StateManager(analyzerManager); + _stateManger.ProjectAnalyzerReferenceChanged += OnProjectAnalyzerReferenceChanged; + _diagnosticLogAggregator = new DiagnosticLogAggregator(_owner); } + private void OnProjectAnalyzerReferenceChanged(object sender, ProjectAnalyzerReferenceChangedEventArgs e) + { + if (e.Removed.Length == 0) + { + // nothing to refresh + return; + } + + // guarantee order of the events. + var asyncToken = _owner.Listener.BeginAsyncOperation(nameof(OnProjectAnalyzerReferenceChanged)); + _eventQueue.ScheduleTask(() => ClearProjectStatesAsync(e.Project, e.Removed, CancellationToken.None), CancellationToken.None).CompletesAsyncOperation(asyncToken); + } + public override Task DocumentOpenAsync(Document document, CancellationToken cancellationToken) { using (Logger.LogBlock(FunctionId.Diagnostics_DocumentOpen, GetOpenLogMessage, document, cancellationToken)) @@ -59,7 +74,7 @@ public override Task DocumentOpenAsync(Document document, CancellationToken canc // we remove whatever information we used to have on document open/close and re-calcuate diagnostics // we had to do this since some diagnostic provider change its behavior based on whether the document is opend or not. // so we can't use cached information. - return RemoveAllCacheDataAsync(document, cancellationToken); + return ClearDocumentStatesAsync(document, _stateManger.GetStateSets(document.Project), cancellationToken); } } @@ -73,7 +88,7 @@ public override Task DocumentResetAsync(Document document, CancellationToken can // we remove whatever information we used to have on document open/close and re-calcuate diagnostics // we had to do this since some diagnostic provider change its behavior based on whether the document is opend or not. // so we can't use cached information. - return RemoveAllCacheDataAsync(document, cancellationToken); + return ClearDocumentStatesAsync(document, _stateManger.GetStateSets(document.Project), cancellationToken); } } @@ -117,29 +132,26 @@ private async Task AnalyzeSyntaxAsync(Document document, ImmutableHashSet var projectVersion = await project.GetDependentVersionAsync(cancellationToken).ConfigureAwait(false); var semanticVersion = await project.GetDependentSemanticVersionAsync(cancellationToken).ConfigureAwait(false); var userDiagnosticDriver = new DiagnosticAnalyzerDriver(project, _diagnosticLogAggregator, HostDiagnosticUpdateSource, cancellationToken); - + var versions = new VersionArgument(VersionStamp.Default, semanticVersion, projectVersion); - foreach (var providerAndId in await _analyzersAndState.GetAllProviderAndIdsAsync(project, cancellationToken).ConfigureAwait(false)) + foreach (var stateSet in _stateManger.GetOrUpdateStateSets(project)) { - var provider = providerAndId.Key; - var providerId = providerAndId.Value; - - if (userDiagnosticDriver.IsAnalyzerSuppressed(provider)) + if (userDiagnosticDriver.IsAnalyzerSuppressed(stateSet.Analyzer)) { - await HandleSuppressedAnalyzerAsync(project, providerId, provider, cancellationToken).ConfigureAwait(false); + await HandleSuppressedAnalyzerAsync(project, stateSet, cancellationToken).ConfigureAwait(false); } - else if (ShouldRunProviderForStateType(StateType.Project, provider, userDiagnosticDriver, diagnosticIds) && - (skipClosedFileChecks || ShouldRunProviderForClosedFile(openedDocument: false, provider: provider))) + else if (ShouldRunProviderForStateType(userDiagnosticDriver, stateSet.Analyzer, StateType.Project, diagnosticIds) && + (skipClosedFileChecks || ShouldRunProviderForClosedFile(openedDocument: false, analyzer: stateSet.Analyzer))) { - var data = await _executor.GetProjectAnalysisDataAsync(provider, providerId, versions, userDiagnosticDriver).ConfigureAwait(false); + var data = await _executor.GetProjectAnalysisDataAsync(userDiagnosticDriver, stateSet, versions).ConfigureAwait(false); if (data.FromCache) { - RaiseDiagnosticsUpdated(StateType.Project, project.Id, providerId, new SolutionArgument(project), data.Items); + RaiseDiagnosticsUpdated(StateType.Project, project.Id, stateSet.Analyzer, new SolutionArgument(project), data.Items); continue; } - var state = _analyzersAndState.GetOrCreateDiagnosticState(StateType.Project, providerId, provider, project.Id, project.Language); + var state = stateSet.GetState(StateType.Project); await state.PersistAsync(project, data.ToPersistData(), cancellationToken).ConfigureAwait(false); - RaiseDiagnosticsUpdatedIfNeeded(project, providerId, data.OldItems, data.Items); + RaiseDiagnosticsUpdatedIfNeeded(project, stateSet.Analyzer, data.OldItems, data.Items); } } } @@ -339,20 +342,15 @@ public override void RemoveDocument(DocumentId documentId) { _memberRangeMap.Remove(documentId); - foreach (var stateProviderIdAndType in _analyzersAndState.GetAllExistingDiagnosticStates(documentId.ProjectId)) + foreach (var stateSet in _stateManger.GetStateSets(documentId.ProjectId)) { - var state = stateProviderIdAndType.Item1; - var providerId = stateProviderIdAndType.Item2; - var type = stateProviderIdAndType.Item3; + stateSet.Remove(documentId); - if (state != null) + var solutionArgs = new SolutionArgument(null, documentId.ProjectId, documentId); + foreach (var stateType in s_documentScopeStateTypes) { - state.Remove(documentId); + RaiseDiagnosticsUpdated(stateType, documentId, stateSet.Analyzer, solutionArgs, ImmutableArray.Empty); } - - var solutionArgs = new SolutionArgument(null, documentId.ProjectId, documentId); - - RaiseDiagnosticsUpdated(type, documentId, providerId, solutionArgs, ImmutableArray.Empty); } } } @@ -361,23 +359,16 @@ public override void RemoveProject(ProjectId projectId) { using (Logger.LogBlock(FunctionId.Diagnostics_RemoveProject, GetRemoveLogMessage, projectId, CancellationToken.None)) { - foreach (var stateProviderIdAndType in _analyzersAndState.GetAllExistingDiagnosticStates(projectId, StateType.Project)) + foreach (var stateSet in _stateManger.GetStateSets(projectId)) { - var state = stateProviderIdAndType.Item1; - var providerId = stateProviderIdAndType.Item2; - - if (state != null) - { - state.Remove(projectId); - } + stateSet.Remove(projectId); var solutionArgs = new SolutionArgument(null, projectId, null); - - RaiseDiagnosticsUpdated(StateType.Project, projectId, providerId, solutionArgs, ImmutableArray.Empty); + RaiseDiagnosticsUpdated(StateType.Project, projectId, stateSet.Analyzer, solutionArgs, ImmutableArray.Empty); } - - _analyzersAndState.RemoveProjectAnalyzersAndStates(projectId); } + + _stateManger.RemoveStateSet(projectId); } public override async Task TryAppendDiagnosticsForSpanAsync(Document document, TextSpan range, List diagnostics, CancellationToken cancellationToken) @@ -449,11 +440,10 @@ public override async Task> GetDiagnosticsForSpanAsy } private async Task TryGetLatestDiagnosticsAsync( - StateType stateType, - Document document, TextSpan range, SyntaxNode root, + StateType stateType, Document document, TextSpan range, SyntaxNode root, List diagnostics, bool requireUpToDateDocumentDiagnostic, Func versionCheck, - Func>> getDiagnostics, + Func>> getDiagnostics, CancellationToken cancellationToken) { try @@ -464,21 +454,17 @@ private async Task TryGetLatestDiagnosticsAsync( // Share the diagnostic analyzer driver across all analyzers. var spanBasedDriver = new DiagnosticAnalyzerDriver(document, range, root, _diagnosticLogAggregator, HostDiagnosticUpdateSource, cancellationToken); var documentBasedDriver = new DiagnosticAnalyzerDriver(document, fullSpan, root, _diagnosticLogAggregator, HostDiagnosticUpdateSource, cancellationToken); - - foreach (var providerAndId in await _analyzersAndState.GetAllProviderAndIdsAsync(document.Project, cancellationToken).ConfigureAwait(false)) - { - var provider = providerAndId.Key; - var providerId = providerAndId.Value; + foreach (var stateSet in _stateManger.GetOrCreateStateSets(document.Project)) + { bool supportsSemanticInSpan; - if (!spanBasedDriver.IsAnalyzerSuppressed(provider) && - ShouldRunProviderForStateType(stateType, provider, spanBasedDriver, out supportsSemanticInSpan)) + if (!spanBasedDriver.IsAnalyzerSuppressed(stateSet.Analyzer) && + ShouldRunProviderForStateType(spanBasedDriver, stateSet.Analyzer, stateType, out supportsSemanticInSpan)) { var userDiagnosticDriver = supportsSemanticInSpan ? spanBasedDriver : documentBasedDriver; result &= await TryGetLatestDiagnosticsAsync( - provider, providerId, stateType, - document, range, root, diagnostics, requireUpToDateDocumentDiagnostic, + stateSet, stateType, document, range, root, diagnostics, requireUpToDateDocumentDiagnostic, versionCheck, getDiagnostics, supportsSemanticInSpan, userDiagnosticDriver, cancellationToken).ConfigureAwait(false); } } @@ -492,11 +478,10 @@ private async Task TryGetLatestDiagnosticsAsync( } private async Task TryGetLatestDiagnosticsAsync( - DiagnosticAnalyzer provider, ProviderId providerId, - StateType stateType, Document document, TextSpan range, SyntaxNode root, + StateSet stateSet, StateType stateType, Document document, TextSpan range, SyntaxNode root, List diagnostics, bool requireUpToDateDocumentDiagnostic, Func versionCheck, - Func>> getDiagnostics, + Func>> getDiagnostics, bool supportsSemanticInSpan, DiagnosticAnalyzerDriver userDiagnosticDriver, CancellationToken cancellationToken) @@ -507,29 +492,19 @@ private async Task TryGetLatestDiagnosticsAsync( // make sure we get state even when none of our analyzer has ran yet. // but this shouldn't create analyzer that doesnt belong to this project (language) - var state = _analyzersAndState.GetOrCreateDiagnosticState(stateType, providerId, provider, document.Project.Id, document.Project.Language); - if (state == null) + var state = stateSet.GetState(stateType); + + // see whether we can use existing info + var existingData = await state.TryGetExistingDataAsync(document, cancellationToken).ConfigureAwait(false); + if (existingData != null && versionCheck(existingData.TextVersion, existingData.DataVersion)) { - if (!requireUpToDateDocumentDiagnostic) + if (existingData.Items == null) { - // the provider never ran yet. return true; } - } - else - { - // see whether we can use existing info - var existingData = await state.TryGetExistingDataAsync(document, cancellationToken).ConfigureAwait(false); - if (existingData != null && versionCheck(existingData.TextVersion, existingData.DataVersion)) - { - if (existingData.Items == null) - { - return true; - } - diagnostics.AddRange(existingData.Items.Where(shouldInclude)); - return true; - } + diagnostics.AddRange(existingData.Items.Where(shouldInclude)); + return true; } // check whether we want up-to-date document wide diagnostics @@ -538,7 +513,7 @@ private async Task TryGetLatestDiagnosticsAsync( return false; } - var dx = await getDiagnostics(providerId, provider, userDiagnosticDriver).ConfigureAwait(false); + var dx = await getDiagnostics(userDiagnosticDriver, stateSet.Analyzer).ConfigureAwait(false); if (dx != null) { // no state yet @@ -553,7 +528,7 @@ private async Task TryGetLatestDiagnosticsAsync( } } - private bool ShouldRunProviderForClosedFile(bool openedDocument, DiagnosticAnalyzer provider) + private bool ShouldRunProviderForClosedFile(bool openedDocument, DiagnosticAnalyzer analyzer) { // we have opened document, doesnt matter if (openedDocument) @@ -561,23 +536,17 @@ private bool ShouldRunProviderForClosedFile(bool openedDocument, DiagnosticAnaly return true; } - return _owner.GetDiagnosticDescriptors(provider).Any(d => d.DefaultSeverity != DiagnosticSeverity.Hidden); + return _owner.GetDiagnosticDescriptors(analyzer).Any(d => d.DefaultSeverity != DiagnosticSeverity.Hidden); } - private bool ShouldRunProviderForStateType(StateType stateTypeId, DiagnosticAnalyzer provider, - DiagnosticAnalyzerDriver driver, ImmutableHashSet diagnosticIds) - { - return ShouldRunProviderForStateType(stateTypeId, provider, driver, diagnosticIds, _owner.GetDiagnosticDescriptors); - } - - private static bool ShouldRunProviderForStateType(StateType stateTypeId, DiagnosticAnalyzer provider, DiagnosticAnalyzerDriver driver, - ImmutableHashSet diagnosticIds, Func> getDescriptor) + private bool ShouldRunProviderForStateType(DiagnosticAnalyzerDriver driver, DiagnosticAnalyzer provider, + StateType stateTypeId, ImmutableHashSet diagnosticIds) { bool discarded; - return ShouldRunProviderForStateType(stateTypeId, provider, driver, out discarded, diagnosticIds, getDescriptor); + return ShouldRunProviderForStateType(driver, provider, stateTypeId, out discarded, diagnosticIds, _owner.GetDiagnosticDescriptors); } - private static bool ShouldRunProviderForStateType(StateType stateTypeId, DiagnosticAnalyzer provider, DiagnosticAnalyzerDriver driver, + private static bool ShouldRunProviderForStateType(DiagnosticAnalyzerDriver driver, DiagnosticAnalyzer provider, StateType stateTypeId, out bool supportsSemanticInSpan, ImmutableHashSet diagnosticIds = null, Func> getDescriptor = null) { Debug.Assert(!driver.IsAnalyzerSuppressed(provider)); @@ -653,32 +622,33 @@ private static bool CheckSemanticVersions(Project project, AnalysisData existing } private void RaiseDiagnosticsUpdatedIfNeeded( - StateType type, Document document, ProviderId providerId, ImmutableArray existingItems, ImmutableArray newItems) + StateType type, Document document, DiagnosticAnalyzer analyzer, ImmutableArray existingItems, ImmutableArray newItems) { var noItems = existingItems.Length == 0 && newItems.Length == 0; if (!noItems) { - RaiseDiagnosticsUpdated(type, document.Id, providerId, new SolutionArgument(document), newItems); + RaiseDiagnosticsUpdated(type, document.Id, analyzer, new SolutionArgument(document), newItems); } } - private void RaiseDiagnosticsUpdatedIfNeeded(Project project, ProviderId providerId, ImmutableArray existingItems, ImmutableArray newItems) + private void RaiseDiagnosticsUpdatedIfNeeded( + Project project, DiagnosticAnalyzer analyzer, ImmutableArray existingItems, ImmutableArray newItems) { var noItems = existingItems.Length == 0 && newItems.Length == 0; if (!noItems) { - RaiseDiagnosticsUpdated(StateType.Project, project.Id, providerId, new SolutionArgument(project), newItems); + RaiseDiagnosticsUpdated(StateType.Project, project.Id, analyzer, new SolutionArgument(project), newItems); } } private void RaiseDiagnosticsUpdated( - StateType type, object key, ProviderId providerId, SolutionArgument solution, ImmutableArray diagnostics) + StateType type, object key, DiagnosticAnalyzer analyzer, SolutionArgument solution, ImmutableArray diagnostics) { if (_owner != null) { - var id = new ArgumentKey(providerId, type, key); + var id = new ArgumentKey(analyzer, type, key); _owner.RaiseDiagnosticsUpdated(this, - new DiagnosticsUpdatedArgs(id, _analyzersAndState.Workspace, solution.Solution, solution.ProjectId, solution.DocumentId, diagnostics)); + new DiagnosticsUpdatedArgs(id, Workspace, solution.Solution, solution.ProjectId, solution.DocumentId, diagnostics)); } } @@ -810,15 +780,15 @@ private static IEnumerable GetDiagnosticData(Project project, IE return diagnostics != null ? diagnostics.Select(d => DiagnosticData.Create(project, d)) : null; } - private static async Task> GetSyntaxDiagnosticsAsync(ProviderId providerId, DiagnosticAnalyzer provider, DiagnosticAnalyzerDriver userDiagnosticDriver) + private static async Task> GetSyntaxDiagnosticsAsync(DiagnosticAnalyzerDriver userDiagnosticDriver, DiagnosticAnalyzer analyzer) { - using (Logger.LogBlock(FunctionId.Diagnostics_SyntaxDiagnostic, GetSyntaxLogMessage, userDiagnosticDriver.Document, userDiagnosticDriver.Span, providerId, userDiagnosticDriver.CancellationToken)) + using (Logger.LogBlock(FunctionId.Diagnostics_SyntaxDiagnostic, GetSyntaxLogMessage, userDiagnosticDriver.Document, userDiagnosticDriver.Span, analyzer, userDiagnosticDriver.CancellationToken)) { try { - Contract.ThrowIfNull(provider); + Contract.ThrowIfNull(analyzer); - var diagnostics = await userDiagnosticDriver.GetSyntaxDiagnosticsAsync(provider).ConfigureAwait(false); + var diagnostics = await userDiagnosticDriver.GetSyntaxDiagnosticsAsync(analyzer).ConfigureAwait(false); return GetDiagnosticData(userDiagnosticDriver.Document, userDiagnosticDriver.Span, diagnostics); } catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) @@ -828,15 +798,15 @@ private static async Task> GetSyntaxDiagnosticsAsync } } - private static async Task> GetSemanticDiagnosticsAsync(ProviderId providerId, DiagnosticAnalyzer provider, DiagnosticAnalyzerDriver userDiagnosticDriver) + private static async Task> GetSemanticDiagnosticsAsync(DiagnosticAnalyzerDriver userDiagnosticDriver, DiagnosticAnalyzer analyzer) { - using (Logger.LogBlock(FunctionId.Diagnostics_SemanticDiagnostic, GetSemanticLogMessage, userDiagnosticDriver.Document, userDiagnosticDriver.Span, providerId, userDiagnosticDriver.CancellationToken)) + using (Logger.LogBlock(FunctionId.Diagnostics_SemanticDiagnostic, GetSemanticLogMessage, userDiagnosticDriver.Document, userDiagnosticDriver.Span, analyzer, userDiagnosticDriver.CancellationToken)) { try { - Contract.ThrowIfNull(provider); + Contract.ThrowIfNull(analyzer); - var diagnostics = await userDiagnosticDriver.GetSemanticDiagnosticsAsync(provider).ConfigureAwait(false); + var diagnostics = await userDiagnosticDriver.GetSemanticDiagnosticsAsync(analyzer).ConfigureAwait(false); return GetDiagnosticData(userDiagnosticDriver.Document, userDiagnosticDriver.Span, diagnostics); } catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) @@ -846,15 +816,15 @@ private static async Task> GetSemanticDiagnosticsAsy } } - private static async Task> GetProjectDiagnosticsAsync(ProviderId providerId, DiagnosticAnalyzer provider, DiagnosticAnalyzerDriver userDiagnosticDriver, Action forceAnalyzeAllDocuments) + private static async Task> GetProjectDiagnosticsAsync(DiagnosticAnalyzerDriver userDiagnosticDriver, DiagnosticAnalyzer analyzer, Action forceAnalyzeAllDocuments) { - using (Logger.LogBlock(FunctionId.Diagnostics_ProjectDiagnostic, GetProjectLogMessage, userDiagnosticDriver.Project, providerId, userDiagnosticDriver.CancellationToken)) + using (Logger.LogBlock(FunctionId.Diagnostics_ProjectDiagnostic, GetProjectLogMessage, userDiagnosticDriver.Project, analyzer, userDiagnosticDriver.CancellationToken)) { try { - Contract.ThrowIfNull(provider); + Contract.ThrowIfNull(analyzer); - var diagnostics = await userDiagnosticDriver.GetProjectDiagnosticsAsync(provider, forceAnalyzeAllDocuments).ConfigureAwait(false); + var diagnostics = await userDiagnosticDriver.GetProjectDiagnosticsAsync(analyzer, forceAnalyzeAllDocuments).ConfigureAwait(false); return GetDiagnosticData(userDiagnosticDriver.Project, diagnostics); } catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) @@ -864,28 +834,17 @@ private static async Task> GetProjectDiagnosticsAsyn } } - private async Task RemoveAllCacheDataAsync(Document document, CancellationToken cancellationToken) - { - var allExistingStates = _analyzersAndState.GetAllExistingDiagnosticStates(document.Project.Id, document.Project.Language); - await RemoveCacheDataAsync(document, allExistingStates, cancellationToken).ConfigureAwait(false); - } - - private async Task RemoveCacheDataAsync(Document document, IEnumerable> states, CancellationToken cancellationToken) + private async Task ClearDocumentStatesAsync(Document document, IEnumerable states, CancellationToken cancellationToken) { try { // Compiler + User diagnostics - foreach (var stateProviderIdAndType in states) + foreach (var state in states) { - var state = stateProviderIdAndType.Item1; - if (state == null) + foreach (var stateType in s_documentScopeStateTypes) { - continue; + await ClearDocumentStateAsync(document, state.Analyzer, stateType, state.GetState(stateType), cancellationToken).ConfigureAwait(false); } - - var providerId = stateProviderIdAndType.Item2; - var type = stateProviderIdAndType.Item3; - await RemoveCacheDataAsync(document, state, providerId, type, cancellationToken).ConfigureAwait(false); } } catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) @@ -894,7 +853,7 @@ private async Task RemoveCacheDataAsync(Document document, IEnumerable.Empty); + RaiseDiagnosticsUpdated(type, key, analyzer, solutionArgs, ImmutableArray.Empty); } catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) { @@ -918,7 +877,20 @@ private async Task RemoveCacheDataAsync(Document document, DiagnosticState state } } - private async Task RemoveCacheDataAsync(Project project, DiagnosticState state, ProviderId providerId, CancellationToken cancellationToken) + private async Task ClearProjectStatesAsync(Project project, IEnumerable states, CancellationToken cancellationToken) + { + foreach (var document in project.Documents) + { + await ClearDocumentStatesAsync(document, states, cancellationToken).ConfigureAwait(false); + } + + foreach (var state in states) + { + await ClearProjectStateAsync(project, state.Analyzer, state.GetState(StateType.Project), cancellationToken).ConfigureAwait(false); + } + } + + private async Task ClearProjectStateAsync(Project project, DiagnosticAnalyzer analyzer, DiagnosticState state, CancellationToken cancellationToken) { try { @@ -930,7 +902,7 @@ private async Task RemoveCacheDataAsync(Project project, DiagnosticState state, // raise diagnostic updated event var solutionArgs = new SolutionArgument(project); - RaiseDiagnosticsUpdated(StateType.Project, project.Id, providerId, solutionArgs, ImmutableArray.Empty); + RaiseDiagnosticsUpdated(StateType.Project, project.Id, analyzer, solutionArgs, ImmutableArray.Empty); } catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) { @@ -938,47 +910,39 @@ private async Task RemoveCacheDataAsync(Project project, DiagnosticState state, } } - private async Task RemoveCacheDataAsync(Project project, IEnumerable> states, CancellationToken cancellationToken) - { - foreach (var document in project.Documents) - { - await RemoveCacheDataAsync(document, states, cancellationToken).ConfigureAwait(false); - } - } - - private async Task HandleSuppressedAnalyzerAsync(Document document, StateType type, ProviderId providerId, DiagnosticAnalyzer provider, CancellationToken cancellationToken) + private async Task HandleSuppressedAnalyzerAsync(Document document, StateSet stateSet, StateType type, CancellationToken cancellationToken) { - var state = _analyzersAndState.GetOrCreateDiagnosticState(type, providerId, provider, document.Project.Id, document.Project.Language); + var state = stateSet.GetState(type); var existingData = await state.TryGetExistingDataAsync(document, cancellationToken).ConfigureAwait(false); if (existingData != null && existingData.Items.Length > 0) { - await RemoveCacheDataAsync(document, state, providerId, type, cancellationToken).ConfigureAwait(false); + await ClearDocumentStateAsync(document, stateSet.Analyzer, type, state, cancellationToken).ConfigureAwait(false); } } - private async Task HandleSuppressedAnalyzerAsync(Project project, ProviderId providerId, DiagnosticAnalyzer provider, CancellationToken cancellationToken) + private async Task HandleSuppressedAnalyzerAsync(Project project, StateSet stateSet, CancellationToken cancellationToken) { - var state = _analyzersAndState.GetOrCreateDiagnosticState(StateType.Project, providerId, provider, project.Id, project.Language); + var state = stateSet.GetState(StateType.Project); var existingData = await state.TryGetExistingDataAsync(project, cancellationToken).ConfigureAwait(false); if (existingData != null && existingData.Items.Length > 0) { - await RemoveCacheDataAsync(project, state, providerId, cancellationToken).ConfigureAwait(false); + await ClearProjectStateAsync(project, stateSet.Analyzer, state, cancellationToken).ConfigureAwait(false); } } - private static string GetSyntaxLogMessage(Document document, TextSpan? span, int providerId) + private static string GetSyntaxLogMessage(Document document, TextSpan? span, DiagnosticAnalyzer analyzer) { - return string.Format("syntax: {0}, {1}, {2}", document.FilePath ?? document.Name, span.HasValue ? span.Value.ToString() : "Full", providerId.ToString()); + return string.Format("syntax: {0}, {1}, {2}", document.FilePath ?? document.Name, span.HasValue ? span.Value.ToString() : "Full", analyzer.ToString()); } - private static string GetSemanticLogMessage(Document document, TextSpan? span, int providerId) + private static string GetSemanticLogMessage(Document document, TextSpan? span, DiagnosticAnalyzer analyzer) { - return string.Format("semantic: {0}, {1}, {2}", document.FilePath ?? document.Name, span.HasValue ? span.Value.ToString() : "Full", providerId.ToString()); + return string.Format("semantic: {0}, {1}, {2}", document.FilePath ?? document.Name, span.HasValue ? span.Value.ToString() : "Full", analyzer.ToString()); } - private static string GetProjectLogMessage(Project project, int providerId) + private static string GetProjectLogMessage(Project project, DiagnosticAnalyzer analyzer) { - return string.Format("project: {0}, {1}", project.FilePath ?? project.Name, providerId.ToString()); + return string.Format("project: {0}, {1}", project.FilePath ?? project.Name, analyzer.ToString()); } private static string GetResetLogMessage(Document document) diff --git a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs index 6980285eccc21..c33aa9017db9e 100644 --- a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs +++ b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs @@ -11,8 +11,6 @@ namespace Microsoft.CodeAnalysis.Diagnostics.EngineV1 { - using ProviderId = Int32; - internal partial class DiagnosticIncrementalAnalyzer { public override Task> GetSpecificCachedDiagnosticsAsync(Solution solution, object id, CancellationToken cancellationToken) @@ -61,12 +59,12 @@ public DiagnosticsGetter(DiagnosticIncrementalAnalyzer owner) this.Owner = owner; } - protected DiagnosticAnalyzersAndStates AnalyzersAndState + protected StateManager StateManager { - get { return this.Owner._analyzersAndState; } + get { return this.Owner._stateManger; } } - protected abstract Task AppendDiagnosticsFromKeyAsync(ProjectId projectId, object value, StateType stateType, string language, CancellationToken cancellationToken); + protected abstract Task AppendDiagnosticsFromKeyAsync(Project project, StateType stateType, object documentOrProject, CancellationToken cancellationToken); public async Task> GetDiagnosticsAsync(Solution solution, ProjectId projectId, DocumentId documentId, CancellationToken cancellationToken) { @@ -129,7 +127,7 @@ private Task AppendProjectDiagnosticsAsync(Project project, CancellationToken ca return SpecializedTasks.EmptyTask; } - return AppendDiagnosticsFromKeyAsync(project.Id, project, StateType.Project, project.Language, cancellationToken); + return AppendDiagnosticsFromKeyAsync(project, StateType.Project, project, cancellationToken); } private async Task AppendDiagnosticsAsync(Solution solution, CancellationToken cancellationToken) @@ -168,8 +166,8 @@ protected async Task AppendDiagnosticsAsync(Document document, CancellationToken return; } - await AppendDiagnosticsFromKeyAsync(document.Project.Id, document, StateType.Syntax, document.Project.Language, cancellationToken).ConfigureAwait(false); - await AppendDiagnosticsFromKeyAsync(document.Project.Id, document, StateType.Document, document.Project.Language, cancellationToken).ConfigureAwait(false); + await AppendDiagnosticsFromKeyAsync(document.Project, StateType.Syntax, document, cancellationToken).ConfigureAwait(false); + await AppendDiagnosticsFromKeyAsync(document.Project, StateType.Document, document, cancellationToken).ConfigureAwait(false); } protected void AppendDiagnostics(IEnumerable items) @@ -246,7 +244,13 @@ public async Task> GetSpecificDiagnosticsAsync(So return ImmutableArray.Empty; } - var state = this.AnalyzersAndState.GetDiagnosticState(key.StateTypeId, key.ProviderId, projectId, project.Language); + var stateSet = this.StateManager.GetOrCreateStateSet(project, key.Analyzer); + if (stateSet == null) + { + return ImmutableArray.Empty; + } + + var state = stateSet.GetState(key.StateTypeId); if (state == null) { return ImmutableArray.Empty; @@ -269,21 +273,16 @@ public async Task> GetSpecificDiagnosticsAsync(So return existingData.Items; } - protected override async Task AppendDiagnosticsFromKeyAsync( - ProjectId projectId, object value, StateType stateType, string language, CancellationToken cancellationToken) + protected override async Task AppendDiagnosticsFromKeyAsync(Project project, StateType stateType, object documentOrProject, CancellationToken cancellationToken) { - foreach (var stateProviderIdAndType in this.AnalyzersAndState.GetAllExistingDiagnosticStates(projectId, stateType, language)) + foreach (var stateSet in this.StateManager.GetStateSets(project)) { cancellationToken.ThrowIfCancellationRequested(); - var state = stateProviderIdAndType.Item1; - if (state == null) - { - continue; - } + var state = stateSet.GetState(stateType); // for now, it just use wait and get result - var existingData = await state.TryGetExistingDataAsync(value, cancellationToken).ConfigureAwait(false); + var existingData = await state.TryGetExistingDataAsync(documentOrProject, cancellationToken).ConfigureAwait(false); if (existingData == null) { continue; @@ -303,34 +302,28 @@ public LatestDiagnosticsGetter(DiagnosticIncrementalAnalyzer owner, ImmutableHas this.DiagnosticIds = diagnosticIds; } - protected abstract Task GetSpecificDiagnosticsAsync(Solution solution, DiagnosticAnalyzer provider, ProviderId providerId, StateType stateType, VersionArgument versions, DiagnosticAnalyzerDriver analyzerDriver); + protected abstract Task GetSpecificDiagnosticsAsync(Solution solution, DiagnosticAnalyzerDriver analyzerDriver, StateSet stateSet, StateType stateType, VersionArgument versions); protected abstract void FilterDiagnostics(AnalysisData analysisData); - protected override async Task AppendDiagnosticsFromKeyAsync( - ProjectId projectId, object value, StateType stateType, string language, CancellationToken cancellationToken) + protected override async Task AppendDiagnosticsFromKeyAsync(Project project, StateType stateType, object documentOrProject, CancellationToken cancellationToken) { - var solution = GetSolution(value); - var project = solution.GetProject(projectId); Contract.ThrowIfNull(project); + var solution = GetSolution(documentOrProject); - var driver = await GetDiagnosticAnalyzerDriverAsync(value, cancellationToken).ConfigureAwait(false); - var versions = await GetVersionsAsync(stateType, value, cancellationToken).ConfigureAwait(false); + var driver = await GetDiagnosticAnalyzerDriverAsync(documentOrProject, cancellationToken).ConfigureAwait(false); + var versions = await GetVersionsAsync(stateType, documentOrProject, cancellationToken).ConfigureAwait(false); - foreach (var providerAndId in await this.AnalyzersAndState.GetAllProviderAndIdsAsync(project, cancellationToken).ConfigureAwait(false)) + foreach (var stateSet in this.StateManager.GetOrCreateStateSets(project)) { cancellationToken.ThrowIfCancellationRequested(); - var provider = providerAndId.Key; - var providerId = providerAndId.Value; - - if (driver.IsAnalyzerSuppressed(provider) || - !this.Owner.ShouldRunProviderForStateType(stateType, provider, driver, this.DiagnosticIds)) + if (driver.IsAnalyzerSuppressed(stateSet.Analyzer) || + !this.Owner.ShouldRunProviderForStateType(driver, stateSet.Analyzer, stateType, this.DiagnosticIds)) { continue; } - var analysisData = await GetSpecificDiagnosticsAsync(solution, provider, providerId, stateType, versions, driver).ConfigureAwait(false); - + var analysisData = await GetSpecificDiagnosticsAsync(solution, driver, stateSet, stateType, versions).ConfigureAwait(false); FilterDiagnostics(analysisData); } } @@ -430,16 +423,16 @@ protected override void FilterDiagnostics(AnalysisData analysisData) } protected override async Task GetSpecificDiagnosticsAsync( - Solution solution, DiagnosticAnalyzer provider, int providerId, StateType stateType, VersionArgument versions, DiagnosticAnalyzerDriver analyzerDriver) + Solution solution, DiagnosticAnalyzerDriver analyzerDriver, StateSet stateSet, StateType stateType, VersionArgument versions) { // we don't care about result switch (stateType) { case StateType.Syntax: - await GetSyntaxDiagnosticsAsync(providerId, provider, analyzerDriver).ConfigureAwait(false); + await GetSyntaxDiagnosticsAsync(analyzerDriver, stateSet.Analyzer).ConfigureAwait(false); break; case StateType.Document: - await GetSemanticDiagnosticsAsync(providerId, provider, analyzerDriver).ConfigureAwait(false); + await GetSemanticDiagnosticsAsync(analyzerDriver, stateSet.Analyzer).ConfigureAwait(false); break; case StateType.Project: default: @@ -485,23 +478,14 @@ public async Task> GetSpecificDiagnosticsAsync(So var driver = await GetDiagnosticAnalyzerDriverAsync(documentOrProject, cancellationToken).ConfigureAwait(false); var versions = await GetVersionsAsync(key.StateTypeId, documentOrProject, cancellationToken).ConfigureAwait(false); - foreach (var providerAndId in await this.AnalyzersAndState.GetAllProviderAndIdsAsync(project, cancellationToken).ConfigureAwait(false)) + var stateSet = this.StateManager.GetOrCreateStateSet(project, key.Analyzer); + if (stateSet == null) { - cancellationToken.ThrowIfCancellationRequested(); - - var provider = providerAndId.Key; - var providerId = providerAndId.Value; - - if (key.ProviderId != providerId) - { - continue; - } - - var analysisData = await GetSpecificDiagnosticsAsync(solution, provider, providerId, key.StateTypeId, versions, driver).ConfigureAwait(false); - return analysisData.Items; + return ImmutableArray.Empty; } - return ImmutableArray.Empty; + var analysisData = await GetSpecificDiagnosticsAsync(solution, driver, stateSet, key.StateTypeId, versions).ConfigureAwait(false); + return analysisData.Items; } protected override void FilterDiagnostics(AnalysisData analysisData) @@ -510,16 +494,16 @@ protected override void FilterDiagnostics(AnalysisData analysisData) } protected override Task GetSpecificDiagnosticsAsync( - Solution solution, DiagnosticAnalyzer provider, ProviderId providerId, StateType stateType, VersionArgument versions, DiagnosticAnalyzerDriver analyzerDriver) + Solution solution, DiagnosticAnalyzerDriver analyzerDriver, StateSet stateSet, StateType stateType, VersionArgument versions) { switch (stateType) { case StateType.Syntax: - return this.AnalyzerExecutor.GetSyntaxAnalysisDataAsync(provider, providerId, versions, analyzerDriver); + return this.AnalyzerExecutor.GetSyntaxAnalysisDataAsync(analyzerDriver, stateSet, versions); case StateType.Document: - return this.AnalyzerExecutor.GetDocumentAnalysisDataAsync(provider, providerId, versions, analyzerDriver); + return this.AnalyzerExecutor.GetDocumentAnalysisDataAsync(analyzerDriver, stateSet, versions); case StateType.Project: - return this.AnalyzerExecutor.GetProjectAnalysisDataAsync(provider, providerId, versions, analyzerDriver); + return this.AnalyzerExecutor.GetProjectAnalysisDataAsync(analyzerDriver, stateSet, versions); default: return Contract.FailWithReturn>("Can't reach here"); } diff --git a/src/Features/Core/Diagnostics/EngineV1/MemberRangeMap.cs b/src/Features/Core/Diagnostics/EngineV1/MemberRangeMap.cs index a21b12bdd2c66..d5436551e0362 100644 --- a/src/Features/Core/Diagnostics/EngineV1/MemberRangeMap.cs +++ b/src/Features/Core/Diagnostics/EngineV1/MemberRangeMap.cs @@ -13,8 +13,6 @@ namespace Microsoft.CodeAnalysis.Diagnostics.EngineV1 { - using ProviderId = Int32; - internal class MemberRangeMap { private static readonly Func s_createMap = _ => new DictionaryData(); @@ -33,19 +31,19 @@ public void Remove(DocumentId documentId) _map.TryRemove(documentId, out unused); } - public void Touch(ProviderId providerId, Document document, VersionStamp version) + public void Touch(DiagnosticAnalyzer analyzer, Document document, VersionStamp version) { // only touch and updateMemberRange methods are allowed to update the dictionaries var data = _map.GetOrAdd(document.Id, s_createMap); lock (data) { - Touch_NoLock(data, providerId, document, version); + Touch_NoLock(data, analyzer, document, version); } } public void UpdateMemberRange( - ProviderId providerId, Document document, VersionStamp newVersion, int memberId, TextSpan span, MemberRanges oldRanges) + DiagnosticAnalyzer analyzer, Document document, VersionStamp newVersion, int memberId, TextSpan span, MemberRanges oldRanges) { // only touch and updateMemberRange methods are allowed to update the dictionaries var data = _map.GetOrAdd(document.Id, s_createMap); @@ -56,31 +54,31 @@ public void UpdateMemberRange( UpdateMemberRange_NoLock(data, document, newVersion, memberId, span, oldRanges.TextVersion); // save provider version information - Touch_NoLock(data, providerId, document, newVersion); + Touch_NoLock(data, analyzer, document, newVersion); ValidateMemberRangeMap(document, newVersion); } } - public MemberRanges GetSavedMemberRange(ProviderId providerId, Document document) + public MemberRanges GetSavedMemberRange(DiagnosticAnalyzer analyzer, Document document) { var data = _map.GetOrAdd(document.Id, s_createMap); lock (data) { - return GetSavedMemberRange_NoLock(data, providerId, document); + return GetSavedMemberRange_NoLock(data, analyzer, document); } } - private void Touch_NoLock(DictionaryData data, int providerId, Document document, VersionStamp version) + private void Touch_NoLock(DictionaryData data, DiagnosticAnalyzer analyzer, Document document, VersionStamp version) { VersionStamp oldVersion; - if (data.VersionMap.TryGetValue(providerId, out oldVersion)) + if (data.VersionMap.TryGetValue(analyzer, out oldVersion)) { DecreaseVersion_NoLock(data, document.Id, oldVersion); } IncreaseVersion_NoLock(data, document.Id, version); - data.VersionMap[providerId] = version; + data.VersionMap[analyzer] = version; ImmutableArray range; if (this.TryCreateOrGetMemberRange_NoLock(data, document, version, out range)) @@ -183,12 +181,12 @@ private void DecreaseVersion_NoLock(DictionaryData data, DocumentId documentId, } } - private MemberRanges GetSavedMemberRange_NoLock(DictionaryData data, int providerId, Document document) + private MemberRanges GetSavedMemberRange_NoLock(DictionaryData data, DiagnosticAnalyzer analyzer, Document document) { VersionStamp version; SyntaxNode root; ImmutableArray range; - if (!data.VersionMap.TryGetValue(providerId, out version)) + if (!data.VersionMap.TryGetValue(analyzer, out version)) { // it is first time for this provider Contract.ThrowIfFalse(document.TryGetSyntaxRoot(out root)); @@ -276,7 +274,7 @@ private void ValidateVersionTracking() private class DictionaryData { - public readonly Dictionary VersionMap = new Dictionary(); + public readonly Dictionary VersionMap = new Dictionary(); public readonly Dictionary> VersionTrackingMap = new Dictionary>(); public readonly Dictionary> MemberRangeMap = new Dictionary>(); } diff --git a/src/Features/Core/Diagnostics/HostAnalyzerManager.cs b/src/Features/Core/Diagnostics/HostAnalyzerManager.cs index b4dd193c8907c..89bb5dea196d3 100644 --- a/src/Features/Core/Diagnostics/HostAnalyzerManager.cs +++ b/src/Features/Core/Diagnostics/HostAnalyzerManager.cs @@ -82,8 +82,8 @@ public string GetAnalyzerReferenceIdentity(AnalyzerReference reference) /// public ImmutableArray GetDiagnosticDescriptors(DiagnosticAnalyzer analyzer) { - Func continueOnAnalyzerException = (ex, a) => !AnalyzerHelper.IsBuiltInAnalyzer(analyzer); - var analyzerExecutor = AnalyzerHelper.GetAnalyzerExecutorForSupportedDiagnostics(analyzer, _hostDiagnosticUpdateSource, continueOnAnalyzerException, CancellationToken.None); + Func continueOnAnalyzerException = (ex, a) => !analyzer.IsBuiltInAnalyzer(); + var analyzerExecutor = analyzer.GetAnalyzerExecutorForSupportedDiagnostics(_hostDiagnosticUpdateSource, continueOnAnalyzerException, CancellationToken.None); return AnalyzerManager.Instance.GetSupportedDiagnosticDescriptors(analyzer, analyzerExecutor); } @@ -95,15 +95,6 @@ public ImmutableDictionary> GetHostDi return _hostDiagnosticAnalyzersPerLanguageMap.GetOrAdd(language, CreateHostDiagnosticAnalyzers); } - /// - /// Get number of analyzers for the language - /// - public int GetHostAnalyzerCount(string language) - { - var map = GetHostDiagnosticAnalyzersPerReference(language); - return map.Values.Sum(v => v.Length); - } - /// /// Create identity and s map /// @@ -121,16 +112,26 @@ public ImmutableDictionary> CreateD } /// - /// Create identity and s map for given + /// Create identity and s map for given that + /// includes both host and project analyzers /// public ImmutableDictionary> CreateDiagnosticAnalyzersPerReference(Project project) { var hostAnalyzerReferences = GetHostDiagnosticAnalyzersPerReference(project.Language); - var projectAnalyzerReferences = CreateDiagnosticAnalyzersPerReferenceMap(CreateAnalyzerReferencesMap(project.AnalyzerReferences.Where(CheckAnalyzerReferenceIdentity)), project.Language); + var projectAnalyzerReferences = CreateProjectDiagnosticAnalyzersPerReference(project); return MergeDiagnosticAnalyzerMap(hostAnalyzerReferences, projectAnalyzerReferences); } + /// + /// Create identity and s map for given that + /// has only project analyzers + /// + public ImmutableDictionary> CreateProjectDiagnosticAnalyzersPerReference(Project project) + { + return CreateDiagnosticAnalyzersPerReferenceMap(CreateAnalyzerReferencesMap(project.AnalyzerReferences.Where(CheckAnalyzerReferenceIdentity)), project.Language); + } + /// /// Create s collection for given /// @@ -186,24 +187,6 @@ private ImmutableDictionary> CreateHo return builder.ToImmutable(); } - internal static ValueTuple GetUniqueIdForAnalyzer(DiagnosticAnalyzer analyzer) - { - // Get the unique ID for given diagnostic analyzer. - // note that we also put version stamp so that we can detect changed analyzer. - var type = analyzer.GetType(); - return ValueTuple.Create(type.AssemblyQualifiedName, GetProviderVersion(type.Assembly.Location)); - } - - private static VersionStamp GetProviderVersion(string path) - { - if (path == null || !File.Exists(path)) - { - return VersionStamp.Default; - } - - return VersionStamp.Create(File.GetLastWriteTimeUtc(path)); - } - private static string GetAnalyzerReferenceId(AnalyzerReference reference) { return reference.Display ?? FeaturesResources.Unknown; @@ -244,7 +227,7 @@ private static ImmutableDictionary CreateAnalyzerRefe var builder = ImmutableDictionary.CreateBuilder(); foreach (var reference in analyzerReferences) { - string key = GetAnalyzerReferenceId(reference); + var key = GetAnalyzerReferenceId(reference); // filter out duplicated analyzer reference if (builder.ContainsKey(key)) diff --git a/src/Features/Core/Diagnostics/Log/DiagnosticAnalyzerLogger.cs b/src/Features/Core/Diagnostics/Log/DiagnosticAnalyzerLogger.cs index 1969bf425267b..71fbdaf94536f 100644 --- a/src/Features/Core/Diagnostics/Log/DiagnosticAnalyzerLogger.cs +++ b/src/Features/Core/Diagnostics/Log/DiagnosticAnalyzerLogger.cs @@ -144,7 +144,7 @@ public static bool AllowsTelemetry(DiagnosticAnalyzerService service, Diagnostic private static bool CheckTelemetry(DiagnosticAnalyzerService service, DiagnosticAnalyzer analyzer) { - if (AnalyzerHelper.IsCompilerAnalyzer(analyzer)) + if (analyzer.IsCompilerAnalyzer()) { return true; } diff --git a/src/Features/Core/Features.csproj b/src/Features/Core/Features.csproj index a285b696475fe..de52ac7389097 100644 --- a/src/Features/Core/Features.csproj +++ b/src/Features/Core/Features.csproj @@ -180,6 +180,8 @@ + + @@ -201,7 +203,6 @@ - @@ -247,11 +248,8 @@ - - - diff --git a/src/Workspaces/Core/Portable/Utilities/SimpleTaskQueue.cs b/src/Workspaces/Core/Portable/Utilities/SimpleTaskQueue.cs index 743b4419dbfa9..e31fe71270917 100644 --- a/src/Workspaces/Core/Portable/Utilities/SimpleTaskQueue.cs +++ b/src/Workspaces/Core/Portable/Utilities/SimpleTaskQueue.cs @@ -48,28 +48,28 @@ private TTask ScheduleTaskWorker(Func taskCreator, Cancellati public Task ScheduleTask(Action taskAction, CancellationToken cancellationToken = default(CancellationToken)) { return ScheduleTaskWorker(delay => _latestTask.ContinueWithAfterDelay( - taskAction, cancellationToken, delay, TaskContinuationOptions.None, _taskScheduler), + taskAction, cancellationToken, delay, TaskContinuationOptions.None, _taskScheduler), cancellationToken); } public Task ScheduleTask(Func taskFunc, CancellationToken cancellationToken = default(CancellationToken)) { return ScheduleTaskWorker>(delay => _latestTask.ContinueWithAfterDelay( - t => taskFunc(), cancellationToken, delay, TaskContinuationOptions.None, _taskScheduler), + t => taskFunc(), cancellationToken, delay, TaskContinuationOptions.None, _taskScheduler), cancellationToken); } public Task ScheduleTask(Func taskFuncAsync, CancellationToken cancellationToken = default(CancellationToken)) { return ScheduleTaskWorker(delay => _latestTask.ContinueWithAfterDelayFromAsync( - t => taskFuncAsync(), cancellationToken, delay, TaskContinuationOptions.None, _taskScheduler), + t => taskFuncAsync(), cancellationToken, delay, TaskContinuationOptions.None, _taskScheduler), cancellationToken); } public Task ScheduleTask(Func> taskFuncAsync, CancellationToken cancellationToken = default(CancellationToken)) { return ScheduleTaskWorker>(delay => _latestTask.ContinueWithAfterDelayFromAsync( - t => taskFuncAsync(), cancellationToken, delay, TaskContinuationOptions.None, _taskScheduler), + t => taskFuncAsync(), cancellationToken, delay, TaskContinuationOptions.None, _taskScheduler), cancellationToken); } From 12a83529ab0649e55d850c9cfb8331e1e79fbde4 Mon Sep 17 00:00:00 2001 From: Heejae Chang Date: Fri, 27 Feb 2015 05:34:04 -0800 Subject: [PATCH 3/5] renamed workspaceAnalyzerManager to hostAnalyzerManager --- .../Core/Diagnostics/DiagnosticAnalyzerService.cs | 12 ++++++------ .../DiagnosticAnalyzerService_IncrementalAnalyzer.cs | 12 ++++++------ .../EngineV2/DiagnosticIncrementalAnalyzer.cs | 8 ++++---- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Features/Core/Diagnostics/DiagnosticAnalyzerService.cs b/src/Features/Core/Diagnostics/DiagnosticAnalyzerService.cs index 82f383064b2d8..65e64729ea8b1 100644 --- a/src/Features/Core/Diagnostics/DiagnosticAnalyzerService.cs +++ b/src/Features/Core/Diagnostics/DiagnosticAnalyzerService.cs @@ -17,7 +17,7 @@ namespace Microsoft.CodeAnalysis.Diagnostics [Shared] internal partial class DiagnosticAnalyzerService : IDiagnosticAnalyzerService { - private readonly HostAnalyzerManager _workspaceAnalyzerManager; + private readonly HostAnalyzerManager _hostAnalyzerManager; private readonly AbstractHostDiagnosticUpdateSource _hostDiagnosticUpdateSource; private readonly IAsynchronousOperationListener _listener; @@ -38,14 +38,14 @@ public DiagnosticAnalyzerService( private DiagnosticAnalyzerService(IEnumerable workspaceAnalyzerAssemblies, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource) : this() { - _workspaceAnalyzerManager = new HostAnalyzerManager(workspaceAnalyzerAssemblies, hostDiagnosticUpdateSource); + _hostAnalyzerManager = new HostAnalyzerManager(workspaceAnalyzerAssemblies, hostDiagnosticUpdateSource); _hostDiagnosticUpdateSource = hostDiagnosticUpdateSource; } // internal for testing purposes. internal DiagnosticAnalyzerService(ImmutableArray workspaceAnalyzers, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource = null) : this() { - _workspaceAnalyzerManager = new HostAnalyzerManager(workspaceAnalyzers, hostDiagnosticUpdateSource); + _hostAnalyzerManager = new HostAnalyzerManager(workspaceAnalyzers, hostDiagnosticUpdateSource); _hostDiagnosticUpdateSource = hostDiagnosticUpdateSource; } @@ -53,15 +53,15 @@ public ImmutableDictionary> GetDiag { if (projectOpt == null) { - return _workspaceAnalyzerManager.GetHostDiagnosticDescriptorsPerReference(); + return _hostAnalyzerManager.GetHostDiagnosticDescriptorsPerReference(); } - return _workspaceAnalyzerManager.CreateDiagnosticDescriptorsPerReference(projectOpt); + return _hostAnalyzerManager.CreateDiagnosticDescriptorsPerReference(projectOpt); } public ImmutableArray GetDiagnosticDescriptors(DiagnosticAnalyzer analyzer) { - return _workspaceAnalyzerManager.GetDiagnosticDescriptors(analyzer); + return _hostAnalyzerManager.GetDiagnosticDescriptors(analyzer); } public void Reanalyze(Workspace workspace, IEnumerable projectIds = null, IEnumerable documentIds = null) diff --git a/src/Features/Core/Diagnostics/DiagnosticAnalyzerService_IncrementalAnalyzer.cs b/src/Features/Core/Diagnostics/DiagnosticAnalyzerService_IncrementalAnalyzer.cs index 36d1fd7b609e1..9564ab47dd2fa 100644 --- a/src/Features/Core/Diagnostics/DiagnosticAnalyzerService_IncrementalAnalyzer.cs +++ b/src/Features/Core/Diagnostics/DiagnosticAnalyzerService_IncrementalAnalyzer.cs @@ -49,7 +49,7 @@ private BaseDiagnosticIncrementalAnalyzer CreateIncrementalAnalyzerCallback(Work { // subscribe to active context changed event for new workspace workspace.DocumentActiveContextChanged += OnDocumentActiveContextChanged; - return new IncrementalAnalyzerDelegatee(this, workspace, _workspaceAnalyzerManager, _hostDiagnosticUpdateSource); + return new IncrementalAnalyzerDelegatee(this, workspace, _hostAnalyzerManager, _hostDiagnosticUpdateSource); } private void OnDocumentActiveContextChanged(object sender, DocumentEventArgs e) @@ -60,7 +60,7 @@ private void OnDocumentActiveContextChanged(object sender, DocumentEventArgs e) // internal for testing internal class IncrementalAnalyzerDelegatee : BaseDiagnosticIncrementalAnalyzer { - private readonly HostAnalyzerManager _workspaceAnalyzerManager; + private readonly HostAnalyzerManager _hostAnalyzerManager; private readonly DiagnosticAnalyzerService _owner; // v1 diagnostic engine @@ -69,17 +69,17 @@ internal class IncrementalAnalyzerDelegatee : BaseDiagnosticIncrementalAnalyzer // v2 diagnostic engine - for now v1 private readonly EngineV2.DiagnosticIncrementalAnalyzer _engineV2; - public IncrementalAnalyzerDelegatee(DiagnosticAnalyzerService owner, Workspace workspace, HostAnalyzerManager workspaceAnalyzerManager, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource) + public IncrementalAnalyzerDelegatee(DiagnosticAnalyzerService owner, Workspace workspace, HostAnalyzerManager hostAnalyzerManager, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource) : base(workspace, hostDiagnosticUpdateSource) { - _workspaceAnalyzerManager = workspaceAnalyzerManager; + _hostAnalyzerManager = hostAnalyzerManager; _owner = owner; var v1CorrelationId = LogAggregator.GetNextId(); - _engineV1 = new EngineV1.DiagnosticIncrementalAnalyzer(_owner, v1CorrelationId, workspace, _workspaceAnalyzerManager, hostDiagnosticUpdateSource); + _engineV1 = new EngineV1.DiagnosticIncrementalAnalyzer(_owner, v1CorrelationId, workspace, _hostAnalyzerManager, hostDiagnosticUpdateSource); var v2CorrelationId = LogAggregator.GetNextId(); - _engineV2 = new EngineV2.DiagnosticIncrementalAnalyzer(_owner, v2CorrelationId, workspace, _workspaceAnalyzerManager, hostDiagnosticUpdateSource); + _engineV2 = new EngineV2.DiagnosticIncrementalAnalyzer(_owner, v2CorrelationId, workspace, _hostAnalyzerManager, hostDiagnosticUpdateSource); } #region IIncrementalAnalyzer diff --git a/src/Features/Core/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs b/src/Features/Core/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs index 7c2fe1c208c3b..24a57292b7539 100644 --- a/src/Features/Core/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs +++ b/src/Features/Core/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs @@ -15,14 +15,14 @@ internal class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncrementalAnalyzer { private readonly int _correlationId; private readonly DiagnosticAnalyzerService _owner; - private readonly HostAnalyzerManager _workspaceAnalyzerManager; + private readonly HostAnalyzerManager _hostAnalyzerManager; - public DiagnosticIncrementalAnalyzer(DiagnosticAnalyzerService owner, int correlationId, Workspace workspace, HostAnalyzerManager workspaceAnalyzerManager, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource) + public DiagnosticIncrementalAnalyzer(DiagnosticAnalyzerService owner, int correlationId, Workspace workspace, HostAnalyzerManager hostAnalyzerManager, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource) : base(workspace, hostDiagnosticUpdateSource) { _correlationId = correlationId; _owner = owner; - _workspaceAnalyzerManager = workspaceAnalyzerManager; + _hostAnalyzerManager = hostAnalyzerManager; } #region IIncrementalAnalyzer @@ -154,7 +154,7 @@ private async Task> GetProjectDiagnosticsAsync(Pr var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); - var analyzers = _workspaceAnalyzerManager.CreateDiagnosticAnalyzers(project); + var analyzers = _hostAnalyzerManager.CreateDiagnosticAnalyzers(project); var compilationWithAnalyzer = compilation.WithAnalyzers(analyzers, project.AnalyzerOptions, cancellationToken); From fa8f20c772fef745f8679a3434be2f9d9af36ff0 Mon Sep 17 00:00:00 2001 From: Heejae Chang Date: Fri, 27 Feb 2015 15:50:55 -0800 Subject: [PATCH 4/5] PR. Feedback some code clean up - rename and such --- .../AbstractHostDiagnosticUpdateSource.cs | 2 +- .../Core/Diagnostics/AnalyzerHelper.cs | 6 +- ...sticIncrementalAnalyzer.DiagnosticState.cs | 478 +++++++++--------- ...gnosticIncrementalAnalyzer.StateManager.cs | 6 +- .../EngineV1/DiagnosticIncrementalAnalyzer.cs | 38 +- ...osticIncrementalAnalyzer_GetDiagnostics.cs | 2 +- .../Diagnostics/EngineV1/MemberRangeMap.cs | 4 +- 7 files changed, 268 insertions(+), 268 deletions(-) diff --git a/src/Features/Core/Diagnostics/AbstractHostDiagnosticUpdateSource.cs b/src/Features/Core/Diagnostics/AbstractHostDiagnosticUpdateSource.cs index 6efa2eed81410..16a0c6b86a085 100644 --- a/src/Features/Core/Diagnostics/AbstractHostDiagnosticUpdateSource.cs +++ b/src/Features/Core/Diagnostics/AbstractHostDiagnosticUpdateSource.cs @@ -114,7 +114,7 @@ private void ClearAnalyzerDiagnostics(DiagnosticAnalyzer analyzer, ProjectId pro private DiagnosticsUpdatedArgs MakeArgs(DiagnosticAnalyzer analyzer, ImmutableHashSet items, Project project) { - var id = analyzer.GetUniqueIdForAnalyzer(); + var id = analyzer.GetUniqueId(); return new DiagnosticsUpdatedArgs( id: Tuple.Create(this, id, project?.Id), diff --git a/src/Features/Core/Diagnostics/AnalyzerHelper.cs b/src/Features/Core/Diagnostics/AnalyzerHelper.cs index 1e11334b8bbdc..a3bd555b72bea 100644 --- a/src/Features/Core/Diagnostics/AnalyzerHelper.cs +++ b/src/Features/Core/Diagnostics/AnalyzerHelper.cs @@ -36,12 +36,12 @@ public static bool IsCompilerAnalyzer(this DiagnosticAnalyzer analyzer) return false; } - public static ValueTuple GetUniqueIdForAnalyzer(this DiagnosticAnalyzer analyzer) + public static ValueTuple GetUniqueId(this DiagnosticAnalyzer analyzer) { // Get the unique ID for given diagnostic analyzer. // note that we also put version stamp so that we can detect changed analyzer. var type = analyzer.GetType(); - return ValueTuple.Create(type.AssemblyQualifiedName, GetProviderVersion(type.Assembly.Location)); + return ValueTuple.Create(type.AssemblyQualifiedName, GetAnalyzerVersion(type.Assembly.Location)); } public static Action GetAddExceptionDiagnosticDelegate(this DiagnosticAnalyzer analyzer, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource, Project project) @@ -83,7 +83,7 @@ public static AnalyzerExecutor GetAnalyzerExecutor( return AnalyzerExecutor.Create(compilation, analyzerOptions, addDiagnostic, addExceptionDiagnostic, continueOnAnalyzerException, cancellationToken); } - private static VersionStamp GetProviderVersion(string path) + private static VersionStamp GetAnalyzerVersion(string path) { if (path == null || !File.Exists(path)) { diff --git a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.DiagnosticState.cs b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.DiagnosticState.cs index daa47362109a7..e95a3a70ec6a5 100644 --- a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.DiagnosticState.cs +++ b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.DiagnosticState.cs @@ -16,303 +16,303 @@ namespace Microsoft.CodeAnalysis.Diagnostics.EngineV1 { - internal partial class DiagnosticIncrementalAnalyzer + internal partial class DiagnosticIncrementalAnalyzer + { + internal class DiagnosticState : AbstractAnalyzerState { - internal class DiagnosticState : AbstractAnalyzerState + private const int FormatVersion = 6; + + private readonly string _stateName; + private readonly VersionStamp _version; + private readonly string _language; + + public DiagnosticState(string stateName, VersionStamp version, string language) { - private const int FormatVersion = 6; + Contract.ThrowIfNull(stateName); - private readonly string _stateName; - private readonly VersionStamp _version; - private readonly string _language; + _stateName = stateName; + _version = version; + _language = language; + } - public DiagnosticState(string stateName, VersionStamp version, string language) - { - Contract.ThrowIfNull(stateName); + internal string Name + { + get { return _stateName; } + } - _stateName = stateName; - _version = version; - _language = language; - } + internal string Language + { + get { return _language; } + } - internal string Name + protected override object GetCacheKey(object value) + { + var document = value as Document; + if (document != null) { - get { return _stateName; } + return document.Id; } - internal string Language - { - get { return _language; } - } + var project = (Project)value; + return project.Id; + } - protected override object GetCacheKey(object value) + protected override Solution GetSolution(object value) + { + var document = value as Document; + if (document != null) { - var document = value as Document; - if (document != null) - { - return document.Id; - } - - var project = (Project)value; - return project.Id; + return document.Project.Solution; } - protected override Solution GetSolution(object value) - { - var document = value as Document; - if (document != null) - { - return document.Project.Solution; - } + var project = (Project)value; + return project.Solution; + } - var project = (Project)value; - return project.Solution; + protected override bool ShouldCache(object value) + { + var document = value as Document; + if (document != null) + { + return document.IsOpen(); } - protected override bool ShouldCache(object value) - { - var document = value as Document; - if (document != null) - { - return document.IsOpen(); - } + var project = (Project)value; + return project.Solution.Workspace.GetOpenDocumentIds(project.Id).Any(); + } - var project = (Project)value; - return project.Solution.Workspace.GetOpenDocumentIds(project.Id).Any(); + protected override Task ReadStreamAsync(IPersistentStorage storage, object value, CancellationToken cancellationToken) + { + var document = value as Document; + if (document != null) + { + return storage.ReadStreamAsync(document, _stateName, cancellationToken); } - protected override Task ReadStreamAsync(IPersistentStorage storage, object value, CancellationToken cancellationToken) - { - var document = value as Document; - if (document != null) - { - return storage.ReadStreamAsync(document, _stateName, cancellationToken); - } + var project = (Project)value; + return storage.ReadStreamAsync(project, _stateName, cancellationToken); + } - var project = (Project)value; - return storage.ReadStreamAsync(project, _stateName, cancellationToken); + protected override AnalysisData TryGetExistingData(Stream stream, object value, CancellationToken cancellationToken) + { + var document = value as Document; + if (document != null) + { + return TryGetExistingData(stream, document.Project, document, cancellationToken); } - protected override AnalysisData TryGetExistingData(Stream stream, object value, CancellationToken cancellationToken) + var project = (Project)value; + return TryGetExistingData(stream, project, null, cancellationToken); + } + + private AnalysisData TryGetExistingData(Stream stream, Project project, Document document, CancellationToken cancellationToken) + { + var list = SharedPools.Default>().AllocateAndClear(); + try { - var document = value as Document; - if (document != null) + using (var reader = new ObjectReader(stream)) { - return TryGetExistingData(stream, document.Project, document, cancellationToken); - } + var format = reader.ReadInt32(); + if (format != FormatVersion) + { + return null; + } - var project = (Project)value; - return TryGetExistingData(stream, project, null, cancellationToken); - } + // saved data is for same analyzer of different version of dll + var analyzerVersion = VersionStamp.ReadFrom(reader); + if (analyzerVersion != _version) + { + return null; + } - private AnalysisData TryGetExistingData(Stream stream, Project project, Document document, CancellationToken cancellationToken) - { - var list = SharedPools.Default>().AllocateAndClear(); - try - { - using (var reader = new ObjectReader(stream)) + var textVersion = VersionStamp.ReadFrom(reader); + var dataVersion = VersionStamp.ReadFrom(reader); + if (textVersion == VersionStamp.Default || dataVersion == VersionStamp.Default) { - var format = reader.ReadInt32(); - if (format != FormatVersion) - { - return null; - } - - // saved data is for same provider of different version of dll - var providerVersion = VersionStamp.ReadFrom(reader); - if (providerVersion != _version) - { - return null; - } - - var textVersion = VersionStamp.ReadFrom(reader); - var dataVersion = VersionStamp.ReadFrom(reader); - if (textVersion == VersionStamp.Default || dataVersion == VersionStamp.Default) - { - return null; - } - - AppendItems(reader, project, document, list, cancellationToken); - - return new AnalysisData(textVersion, dataVersion, list.ToImmutableArray()); + return null; } - } - catch (Exception) - { - return null; - } - finally - { - SharedPools.Default>().ClearAndFree(list); + + AppendItems(reader, project, document, list, cancellationToken); + + return new AnalysisData(textVersion, dataVersion, list.ToImmutableArray()); } } - - private void AppendItems(ObjectReader reader, Project project, Document document, List list, CancellationToken cancellationToken) + catch (Exception) { - var count = reader.ReadInt32(); + return null; + } + finally + { + SharedPools.Default>().ClearAndFree(list); + } + } - for (var i = 0; i < count; i++) - { - cancellationToken.ThrowIfCancellationRequested(); + private void AppendItems(ObjectReader reader, Project project, Document document, List list, CancellationToken cancellationToken) + { + var count = reader.ReadInt32(); - 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 warningLevel = reader.ReadInt32(); - - var start = reader.ReadInt32(); - var length = reader.ReadInt32(); - var textSpan = new TextSpan(start, length); - - 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 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, document != null ? document.Id : null, document != null ? textSpan : (TextSpan?)null, - mappedFile, mappedStartLine, mappedStartColumn, mappedEndLine, mappedEndColumn, - originalFile, originalStartLine, originalStartColumn, originalEndLine, originalEndColumn, - title: title, - description: description, - helpLink: helpLink)); - } + 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 warningLevel = reader.ReadInt32(); + + var start = reader.ReadInt32(); + var length = reader.ReadInt32(); + var textSpan = new TextSpan(start, length); + + 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 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, document != null ? document.Id : null, document != null ? textSpan : (TextSpan?)null, + mappedFile, mappedStartLine, mappedStartColumn, mappedEndLine, mappedEndColumn, + originalFile, originalStartLine, originalStartColumn, originalEndLine, originalEndColumn, + title: title, + description: description, + helpLink: helpLink)); } + } - private ImmutableDictionary GetProperties(ObjectReader reader, int count) + private ImmutableDictionary GetProperties(ObjectReader reader, int count) + { + if (count > 0) { - if (count > 0) + var properties = ImmutableDictionary.CreateBuilder(); + for (var i = 0; i < count; i++) { - var properties = ImmutableDictionary.CreateBuilder(); - for (var i = 0; i < count; i++) - { - properties.Add(reader.ReadString(), reader.ReadString()); - } - - return properties.ToImmutable(); + properties.Add(reader.ReadString(), reader.ReadString()); } - return ImmutableDictionary.Empty; + return properties.ToImmutable(); } - private static IReadOnlyList GetCustomTags(ObjectReader reader, int count) + return ImmutableDictionary.Empty; + } + + private static IReadOnlyList GetCustomTags(ObjectReader reader, int count) + { + if (count > 0) { - if (count > 0) + var tags = new List(count); + for (var i = 0; i < count; i++) { - var tags = new List(count); - for (var i = 0; i < count; i++) - { - tags.Add(reader.ReadString()); - } - - return new ReadOnlyCollection(tags); + tags.Add(reader.ReadString()); } - return SpecializedCollections.EmptyReadOnlyList(); + return new ReadOnlyCollection(tags); } - protected override Task WriteStreamAsync(IPersistentStorage storage, object value, Stream stream, CancellationToken cancellationToken) - { - var document = value as Document; - if (document != null) - { - return storage.WriteStreamAsync(document, _stateName, stream, cancellationToken); - } + return SpecializedCollections.EmptyReadOnlyList(); + } - var project = (Project)value; - return storage.WriteStreamAsync(project, _stateName, stream, cancellationToken); + protected override Task WriteStreamAsync(IPersistentStorage storage, object value, Stream stream, CancellationToken cancellationToken) + { + var document = value as Document; + if (document != null) + { + return storage.WriteStreamAsync(document, _stateName, stream, cancellationToken); } - protected override void WriteTo(Stream stream, AnalysisData data, CancellationToken cancellationToken) + var project = (Project)value; + return storage.WriteStreamAsync(project, _stateName, stream, cancellationToken); + } + + protected override void WriteTo(Stream stream, AnalysisData data, CancellationToken cancellationToken) + { + using (var writer = new ObjectWriter(stream, cancellationToken: cancellationToken)) { - using (var writer = new ObjectWriter(stream, cancellationToken: cancellationToken)) + writer.WriteInt32(FormatVersion); + _version.WriteTo(writer); + data.TextVersion.WriteTo(writer); + data.DataVersion.WriteTo(writer); + + writer.WriteInt32(data.Items.Length); + + foreach (var item in data.Items) { - writer.WriteInt32(FormatVersion); - _version.WriteTo(writer); - data.TextVersion.WriteTo(writer); - data.DataVersion.WriteTo(writer); + cancellationToken.ThrowIfCancellationRequested(); + + writer.WriteString(item.Id); + writer.WriteString(item.Category); + + writer.WriteString(item.Message); + writer.WriteString(item.MessageFormat); + 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.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); + } + + writer.WriteString(item.OriginalFilePath); + writer.WriteInt32(item.OriginalStartLine); + writer.WriteInt32(item.OriginalStartColumn); + writer.WriteInt32(item.OriginalEndLine); + writer.WriteInt32(item.OriginalEndColumn); - writer.WriteInt32(data.Items.Length); + writer.WriteString(item.MappedFilePath); + writer.WriteInt32(item.MappedStartLine); + writer.WriteInt32(item.MappedStartColumn); + writer.WriteInt32(item.MappedEndLine); + writer.WriteInt32(item.MappedEndColumn); + + writer.WriteInt32(item.CustomTags.Count); + foreach (var tag in item.CustomTags) + { + writer.WriteString(tag); + } - foreach (var item in data.Items) + writer.WriteInt32(item.Properties.Count); + foreach (var property in item.Properties) { - cancellationToken.ThrowIfCancellationRequested(); - - writer.WriteString(item.Id); - writer.WriteString(item.Category); - - writer.WriteString(item.Message); - writer.WriteString(item.MessageFormat); - 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.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); - } - - 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); - - 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); - } + writer.WriteString(property.Key); + writer.WriteString(property.Value); } } } } } } +} diff --git a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.cs b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.cs index a9d885fc5d942..24183b40c01ef 100644 --- a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.cs +++ b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.cs @@ -218,9 +218,9 @@ private static DiagnosticState[] CreateDiagnosticStates(string language, Diagnos } /// - /// Get the unique state name for the given {type, provider} tuple. + /// Get the unique state name for the given {type, analyzer} tuple. /// Note that this name is used by the underlying persistence stream of the corresponding to Read/Write diagnostic data into the stream. - /// If any two distinct {type, provider} tuples have the same diagnostic state name, we will end up sharing the persistence stream between them, leading to duplicate/missing/incorrect diagnostic data. + /// If any two distinct {type, analyzer} tuples have the same diagnostic state name, we will end up sharing the persistence stream between them, leading to duplicate/missing/incorrect diagnostic data. /// private static ValueTuple GetNameAndVersion(DiagnosticAnalyzer analyzer, StateType type) { @@ -228,7 +228,7 @@ private static ValueTuple GetNameAndVersion(DiagnosticAnal // 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.GetUniqueIdForAnalyzer(); + var tuple = analyzer.GetUniqueId(); return ValueTuple.Create(UserDiagnosticsPrefixTableName + "_" + type.ToString() + "_" + tuple.Item1, tuple.Item2); } } diff --git a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.cs b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.cs index 25c9d7b46b867..be2debc4cb601 100644 --- a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.cs +++ b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.cs @@ -72,7 +72,7 @@ public override Task DocumentOpenAsync(Document document, CancellationToken canc using (Logger.LogBlock(FunctionId.Diagnostics_DocumentOpen, GetOpenLogMessage, document, cancellationToken)) { // we remove whatever information we used to have on document open/close and re-calcuate diagnostics - // we had to do this since some diagnostic provider change its behavior based on whether the document is opend or not. + // we had to do this since some diagnostic analyzer change its behavior based on whether the document is opend or not. // so we can't use cached information. return ClearDocumentStatesAsync(document, _stateManger.GetStateSets(document.Project), cancellationToken); } @@ -86,7 +86,7 @@ public override Task DocumentResetAsync(Document document, CancellationToken can _memberRangeMap.Remove(document.Id); // we remove whatever information we used to have on document open/close and re-calcuate diagnostics - // we had to do this since some diagnostic provider change its behavior based on whether the document is opend or not. + // we had to do this since some diagnostic analyzer change its behavior based on whether the document is opend or not. // so we can't use cached information. return ClearDocumentStatesAsync(document, _stateManger.GetStateSets(document.Project), cancellationToken); } @@ -138,8 +138,8 @@ private async Task AnalyzeSyntaxAsync(Document document, ImmutableHashSet { await HandleSuppressedAnalyzerAsync(project, stateSet, cancellationToken).ConfigureAwait(false); } - else if (ShouldRunProviderForStateType(userDiagnosticDriver, stateSet.Analyzer, StateType.Project, diagnosticIds) && - (skipClosedFileChecks || ShouldRunProviderForClosedFile(openedDocument: false, analyzer: stateSet.Analyzer))) + else if (ShouldRunAnalyzerForStateType(userDiagnosticDriver, stateSet.Analyzer, StateType.Project, diagnosticIds) && + (skipClosedFileChecks || ShouldRunAnalyzerForClosedFile(openedDocument: false, analyzer: stateSet.Analyzer))) { var data = await _executor.GetProjectAnalysisDataAsync(userDiagnosticDriver, stateSet, versions).ConfigureAwait(false); if (data.FromCache) @@ -459,7 +459,7 @@ private async Task TryGetLatestDiagnosticsAsync( { bool supportsSemanticInSpan; if (!spanBasedDriver.IsAnalyzerSuppressed(stateSet.Analyzer) && - ShouldRunProviderForStateType(spanBasedDriver, stateSet.Analyzer, stateType, out supportsSemanticInSpan)) + ShouldRunAnalyzerForStateType(spanBasedDriver, stateSet.Analyzer, stateType, out supportsSemanticInSpan)) { var userDiagnosticDriver = supportsSemanticInSpan ? spanBasedDriver : documentBasedDriver; @@ -528,7 +528,7 @@ private async Task TryGetLatestDiagnosticsAsync( } } - private bool ShouldRunProviderForClosedFile(bool openedDocument, DiagnosticAnalyzer analyzer) + private bool ShouldRunAnalyzerForClosedFile(bool openedDocument, DiagnosticAnalyzer analyzer) { // we have opened document, doesnt matter if (openedDocument) @@ -539,20 +539,20 @@ private bool ShouldRunProviderForClosedFile(bool openedDocument, DiagnosticAnaly return _owner.GetDiagnosticDescriptors(analyzer).Any(d => d.DefaultSeverity != DiagnosticSeverity.Hidden); } - private bool ShouldRunProviderForStateType(DiagnosticAnalyzerDriver driver, DiagnosticAnalyzer provider, + private bool ShouldRunAnalyzerForStateType(DiagnosticAnalyzerDriver driver, DiagnosticAnalyzer analyzer, StateType stateTypeId, ImmutableHashSet diagnosticIds) { bool discarded; - return ShouldRunProviderForStateType(driver, provider, stateTypeId, out discarded, diagnosticIds, _owner.GetDiagnosticDescriptors); + return ShouldRunAnalyzerForStateType(driver, analyzer, stateTypeId, out discarded, diagnosticIds, _owner.GetDiagnosticDescriptors); } - private static bool ShouldRunProviderForStateType(DiagnosticAnalyzerDriver driver, DiagnosticAnalyzer provider, StateType stateTypeId, + private static bool ShouldRunAnalyzerForStateType(DiagnosticAnalyzerDriver driver, DiagnosticAnalyzer analyzer, StateType stateTypeId, out bool supportsSemanticInSpan, ImmutableHashSet diagnosticIds = null, Func> getDescriptor = null) { - Debug.Assert(!driver.IsAnalyzerSuppressed(provider)); + Debug.Assert(!driver.IsAnalyzerSuppressed(analyzer)); supportsSemanticInSpan = false; - if (diagnosticIds != null && getDescriptor(provider).All(d => !diagnosticIds.Contains(d.Id))) + if (diagnosticIds != null && getDescriptor(analyzer).All(d => !diagnosticIds.Contains(d.Id))) { return false; } @@ -560,13 +560,13 @@ private static bool ShouldRunProviderForStateType(DiagnosticAnalyzerDriver drive switch (stateTypeId) { case StateType.Syntax: - return provider.SupportsSyntaxDiagnosticAnalysis(driver); + return analyzer.SupportsSyntaxDiagnosticAnalysis(driver); case StateType.Document: - return provider.SupportsSemanticDiagnosticAnalysis(driver, out supportsSemanticInSpan); + return analyzer.SupportsSemanticDiagnosticAnalysis(driver, out supportsSemanticInSpan); case StateType.Project: - return provider.SupportsProjectDiagnosticAnalysis(driver); + return analyzer.SupportsProjectDiagnosticAnalysis(driver); default: throw ExceptionUtilities.Unreachable; diff --git a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs index c33aa9017db9e..51bb9097b4883 100644 --- a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs +++ b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs @@ -318,7 +318,7 @@ protected override async Task AppendDiagnosticsFromKeyAsync(Project project, Sta cancellationToken.ThrowIfCancellationRequested(); if (driver.IsAnalyzerSuppressed(stateSet.Analyzer) || - !this.Owner.ShouldRunProviderForStateType(driver, stateSet.Analyzer, stateType, this.DiagnosticIds)) + !this.Owner.ShouldRunAnalyzerForStateType(driver, stateSet.Analyzer, stateType, this.DiagnosticIds)) { continue; } diff --git a/src/Features/Core/Diagnostics/EngineV1/MemberRangeMap.cs b/src/Features/Core/Diagnostics/EngineV1/MemberRangeMap.cs index d5436551e0362..628665003b31a 100644 --- a/src/Features/Core/Diagnostics/EngineV1/MemberRangeMap.cs +++ b/src/Features/Core/Diagnostics/EngineV1/MemberRangeMap.cs @@ -53,7 +53,7 @@ public void UpdateMemberRange( // now update member range map UpdateMemberRange_NoLock(data, document, newVersion, memberId, span, oldRanges.TextVersion); - // save provider version information + // save analyzer version information Touch_NoLock(data, analyzer, document, newVersion); ValidateMemberRangeMap(document, newVersion); @@ -188,7 +188,7 @@ private MemberRanges GetSavedMemberRange_NoLock(DictionaryData data, DiagnosticA ImmutableArray range; if (!data.VersionMap.TryGetValue(analyzer, out version)) { - // it is first time for this provider + // it is first time for this analyzer Contract.ThrowIfFalse(document.TryGetSyntaxRoot(out root)); Contract.ThrowIfFalse(document.TryGetTextVersion(out version)); From 93435e00dc2b532a854dfd9e62d36c6388d4352a Mon Sep 17 00:00:00 2001 From: Heejae Chang Date: Fri, 27 Feb 2015 16:28:52 -0800 Subject: [PATCH 5/5] split some files to its own file - PR.feedback more clean up after feedback --- ...er.IncrementalAnalyzer.AnalyzerExecutor.cs | 8 +- ...rojectAnalyzerReferenceChangedEventArgs.cs | 30 +++++++ ...gnosticIncrementalAnalyzer.StateManager.cs | 80 ------------------- .../DiagnosticIncrementalAnalyzer.StateSet.cs | 80 +++++++++++++++++++ src/Features/Core/Features.csproj | 2 + 5 files changed, 116 insertions(+), 84 deletions(-) create mode 100644 src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.ProjectAnalyzerReferenceChangedEventArgs.cs create mode 100644 src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateSet.cs diff --git a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.IncrementalAnalyzer.AnalyzerExecutor.cs b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.IncrementalAnalyzer.AnalyzerExecutor.cs index 3303c3c3e36aa..65f4e5b10e3a7 100644 --- a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.IncrementalAnalyzer.AnalyzerExecutor.cs +++ b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.IncrementalAnalyzer.AnalyzerExecutor.cs @@ -50,12 +50,12 @@ public async Task GetSyntaxAnalysisDataAsync(DiagnosticAnalyzerDri } } - public async Task GetDocumentAnalysisDataAsync(DiagnosticAnalyzerDriver analyzerDrvier, StateSet stateSet, VersionArgument versions) + public async Task GetDocumentAnalysisDataAsync(DiagnosticAnalyzerDriver analyzerDriver, StateSet stateSet, VersionArgument versions) { try { - var document = analyzerDrvier.Document; - var cancellationToken = analyzerDrvier.CancellationToken; + var document = analyzerDriver.Document; + var cancellationToken = analyzerDriver.CancellationToken; var state = stateSet.GetState(StateType.Document); var existingData = await state.TryGetExistingDataAsync(document, cancellationToken).ConfigureAwait(false); @@ -65,7 +65,7 @@ public async Task GetDocumentAnalysisDataAsync(DiagnosticAnalyzerD return existingData; } - var diagnosticData = await GetSemanticDiagnosticsAsync(analyzerDrvier, stateSet.Analyzer).ConfigureAwait(false); + var diagnosticData = await GetSemanticDiagnosticsAsync(analyzerDriver, stateSet.Analyzer).ConfigureAwait(false); return new AnalysisData(versions.TextVersion, versions.DataVersion, GetExistingItems(existingData), diagnosticData.AsImmutableOrEmpty()); } catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) diff --git a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.ProjectAnalyzerReferenceChangedEventArgs.cs b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.ProjectAnalyzerReferenceChangedEventArgs.cs new file mode 100644 index 0000000000000..56b726e689a91 --- /dev/null +++ b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.ProjectAnalyzerReferenceChangedEventArgs.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Immutable; + +namespace Microsoft.CodeAnalysis.Diagnostics.EngineV1 +{ + internal partial class DiagnosticIncrementalAnalyzer + { + /// + /// EventArgs for + /// + /// this event args contains information such as the has changed + /// and what has changed. + /// + private class ProjectAnalyzerReferenceChangedEventArgs : EventArgs + { + public readonly Project Project; + public readonly ImmutableArray Added; + public readonly ImmutableArray Removed; + + public ProjectAnalyzerReferenceChangedEventArgs(Project project, ImmutableArray added, ImmutableArray removed) + { + Project = project; + Added = added; + Removed = removed; + } + } + } +} diff --git a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.cs b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.cs index 24183b40c01ef..ff020f80965dd 100644 --- a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.cs +++ b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.cs @@ -152,85 +152,5 @@ private static void VerifyDiagnosticStates(IEnumerable stateSets) } } } - - private class ProjectAnalyzerReferenceChangedEventArgs : EventArgs - { - public readonly Project Project; - public readonly ImmutableArray Added; - public readonly ImmutableArray Removed; - - public ProjectAnalyzerReferenceChangedEventArgs(Project project, ImmutableArray added, ImmutableArray removed) - { - Project = project; - Added = added; - Removed = removed; - } - } - - private class StateSet - { - private const string UserDiagnosticsPrefixTableName = ""; - - private readonly string _language; - private readonly DiagnosticAnalyzer _analyzer; - - private readonly DiagnosticState[] _state; - - public StateSet(string language, DiagnosticAnalyzer analyzer) - { - _language = language; - _analyzer = analyzer; - - _state = CreateDiagnosticStates(language, analyzer); - } - - public string Language => _language; - public DiagnosticAnalyzer Analyzer => _analyzer; - - public DiagnosticState GetState(StateType stateType) - { - return _state[(int)stateType]; - } - - public void Remove(object key) - { - for (var stateType = 0; stateType < s_stateTypeCount; stateType++) - { - _state[stateType].Remove(key); - } - } - - private static DiagnosticState[] CreateDiagnosticStates(string language, DiagnosticAnalyzer analyzer) - { - var states = new DiagnosticState[s_stateTypeCount]; - - for (int stateType = 0; stateType < s_stateTypeCount; stateType++) - { - var nameAndVersion = GetNameAndVersion(analyzer, (StateType)stateType); - - var name = nameAndVersion.Item1; - var version = nameAndVersion.Item2; - - states[stateType] = new DiagnosticState(name, version, language); - } - - return states; - } - - /// - /// Get the unique state name for the given {type, analyzer} tuple. - /// Note that this name is used by the underlying persistence stream of the corresponding to Read/Write diagnostic data into the stream. - /// If any two distinct {type, analyzer} tuples have the same diagnostic state name, we will end up sharing the persistence stream between them, leading to duplicate/missing/incorrect diagnostic data. - /// - private static ValueTuple GetNameAndVersion(DiagnosticAnalyzer analyzer, StateType type) - { - 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.GetUniqueId(); - return ValueTuple.Create(UserDiagnosticsPrefixTableName + "_" + type.ToString() + "_" + tuple.Item1, tuple.Item2); - } - } } } diff --git a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateSet.cs b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateSet.cs new file mode 100644 index 0000000000000..a395d13855c7a --- /dev/null +++ b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateSet.cs @@ -0,0 +1,80 @@ +// 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.EngineV1 +{ + internal partial class DiagnosticIncrementalAnalyzer + { + /// + /// this contains all states regarding a + /// + /// use to retrieve specific of + /// + private class StateSet + { + private const string UserDiagnosticsPrefixTableName = ""; + + private readonly string _language; + private readonly DiagnosticAnalyzer _analyzer; + + private readonly DiagnosticState[] _state; + + public StateSet(string language, DiagnosticAnalyzer analyzer) + { + _language = language; + _analyzer = analyzer; + + _state = CreateDiagnosticStates(language, analyzer); + } + + public string Language => _language; + public DiagnosticAnalyzer Analyzer => _analyzer; + + public DiagnosticState GetState(StateType stateType) + { + return _state[(int)stateType]; + } + + public void Remove(object key) + { + for (var stateType = 0; stateType < s_stateTypeCount; stateType++) + { + _state[stateType].Remove(key); + } + } + + private static DiagnosticState[] CreateDiagnosticStates(string language, DiagnosticAnalyzer analyzer) + { + var states = new DiagnosticState[s_stateTypeCount]; + + for (int stateType = 0; stateType < s_stateTypeCount; stateType++) + { + var nameAndVersion = GetNameAndVersion(analyzer, (StateType)stateType); + + var name = nameAndVersion.Item1; + var version = nameAndVersion.Item2; + + states[stateType] = new DiagnosticState(name, version, language); + } + + return states; + } + + /// + /// Get the unique state name for the given {type, analyzer} tuple. + /// Note that this name is used by the underlying persistence stream of the corresponding to Read/Write diagnostic data into the stream. + /// If any two distinct {type, analyzer} tuples have the same diagnostic state name, we will end up sharing the persistence stream between them, leading to duplicate/missing/incorrect diagnostic data. + /// + private static ValueTuple GetNameAndVersion(DiagnosticAnalyzer analyzer, StateType type) + { + 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.GetUniqueId(); + return ValueTuple.Create(UserDiagnosticsPrefixTableName + "_" + type.ToString() + "_" + tuple.Item1, tuple.Item2); + } + } + } +} diff --git a/src/Features/Core/Features.csproj b/src/Features/Core/Features.csproj index de52ac7389097..201ffc43f8caa 100644 --- a/src/Features/Core/Features.csproj +++ b/src/Features/Core/Features.csproj @@ -179,9 +179,11 @@ + +