DiagnosticIncrementalAnalyzer.StateManager.cs 8.5 KB
Newer Older
1 2 3 4 5
// 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;
6
using System.Diagnostics;
7 8 9 10 11 12 13
using System.Linq;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.Diagnostics.EngineV1
{
    internal partial class DiagnosticIncrementalAnalyzer
    {
14 15
        private const string RoslynLanguageServices = "Roslyn Language Services";

16
        private static readonly int s_stateTypeCount = Enum.GetNames(typeof(StateType)).Length;
17 18 19 20 21

        /// <summary>
        /// This is in charge of anything related to <see cref="DiagnosticState"/>
        /// </summary>
        private partial class StateManager
22
        {
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
            private readonly HostAnalyzerManager _analyzerManager;

            private readonly HostStates _hostStates;
            private readonly ProjectStates _projectStates;

            public StateManager(HostAnalyzerManager analyzerManager)
            {
                _analyzerManager = analyzerManager;

                _hostStates = new HostStates(this);
                _projectStates = new ProjectStates(this);
            }

            private HostAnalyzerManager AnalyzerManager { get { return _analyzerManager; } }

            /// <summary>
            /// This will be raised whenever <see cref="StateManager"/> finds <see cref="Project.AnalyzerReferences"/> change
            /// </summary>
            public event EventHandler<ProjectAnalyzerReferenceChangedEventArgs> ProjectAnalyzerReferenceChanged;

43 44 45 46 47 48 49 50
            /// <summary>
            /// Return <see cref="DiagnosticAnalyzer"/>s for the given <see cref="Project"/>.
            /// </summary>
            public IEnumerable<DiagnosticAnalyzer> GetAnalyzers(Project project)
            {
                return _hostStates.GetAnalyzers(project.Language).Concat(_projectStates.GetAnalyzers(project));
            }

51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
            /// <summary>
            /// Return <see cref="StateSet"/>s for the given <see cref="ProjectId"/>. 
            /// This will never create new <see cref="StateSet"/> but will return ones already created.
            /// </summary>
            public IEnumerable<StateSet> GetStateSets(ProjectId projectId)
            {
                return _hostStates.GetStateSets().Concat(_projectStates.GetStateSets(projectId));
            }

            /// <summary>
            /// Return <see cref="StateSet"/>s for the given <see cref="Project"/>.
            /// This will never create new <see cref="StateSet"/> but will return ones already created.
            /// Difference with <see cref="GetStateSets(ProjectId)"/> is that 
            /// this will only return <see cref="StateSet"/>s that have same language as <paramref name="project"/>.
            /// </summary>
            public IEnumerable<StateSet> GetStateSets(Project project)
            {
                return GetStateSets(project.Id).Where(s => s.Language == project.Language);
            }

            /// <summary>
            /// Return <see cref="StateSet"/>s for the given <see cref="Project"/>. 
            /// This will either return already created <see cref="StateSet"/>s for the specific snapshot of <see cref="Project"/> or
            /// It will create new <see cref="StateSet"/>s for the <see cref="Project"/> and update internal state.
            /// 
            /// since this has a side-effect, this should never be called concurrently. and incremental analyzer (solution crawler) should guarantee that.
            /// </summary>
            public IEnumerable<StateSet> GetOrUpdateStateSets(Project project)
            {
                return _hostStates.GetOrCreateStateSets(project.Language).Concat(_projectStates.GetOrUpdateStateSets(project));
            }

            /// <summary>
            /// Return <see cref="StateSet"/>s for the given <see cref="Project"/>. 
            /// This will either return already created <see cref="StateSet"/>s for the specific snapshot of <see cref="Project"/> or
            /// It will create new <see cref="StateSet"/>s for the <see cref="Project"/>.
            /// Unlike <see cref="GetOrUpdateStateSets(Project)"/>, this has no side effect.
            /// </summary>
            public IEnumerable<StateSet> GetOrCreateStateSets(Project project)
            {
                return _hostStates.GetOrCreateStateSets(project.Language).Concat(_projectStates.GetOrCreateStateSets(project));
            }

            /// <summary>
            /// Return <see cref="StateSet"/> for the given <see cref="DiagnosticAnalyzer"/> in the context of <see cref="Project"/>.
            /// This will either return already created <see cref="StateSet"/> for the specific snapshot of <see cref="Project"/> or
            /// It will create new <see cref="StateSet"/> for the <see cref="Project"/>.
            /// This will not have any side effect.
            /// </summary>
            public StateSet GetOrCreateStateSet(Project project, DiagnosticAnalyzer analyzer)
            {
                var stateSet = _hostStates.GetOrCreateStateSet(project.Language, analyzer);
                if (stateSet != null)
                {
                    return stateSet;
                }

                return _projectStates.GetOrCreateStateSet(project, analyzer);
            }
110

111 112 113 114 115 116 117
            /// <summary>
            /// Remove given <see cref="ProjectId"/>.
            /// </summary>
            public void RemoveStateSet(ProjectId projectId)
            {
                _projectStates.RemoveStateSet(projectId);
            }
118

119
            private void RaiseProjectAnalyzerReferenceChanged(ProjectAnalyzerReferenceChangedEventArgs args)
120
            {
121
                ProjectAnalyzerReferenceChanged?.Invoke(this, args);
122 123
            }

124
            private static ImmutableDictionary<DiagnosticAnalyzer, StateSet> CreateAnalyzerMap(
H
Heejae Chang 已提交
125
                HostAnalyzerManager analyzerManager, string language, IEnumerable<ImmutableArray<DiagnosticAnalyzer>> analyzerCollection)
126
            {
H
Heejae Chang 已提交
127 128
                var compilerAnalyzer = analyzerManager.GetCompilerDiagnosticAnalyzer(language);

129 130 131 132 133 134 135
                var builder = ImmutableDictionary.CreateBuilder<DiagnosticAnalyzer, StateSet>();
                foreach (var analyzers in analyzerCollection)
                {
                    foreach (var analyzer in analyzers)
                    {
                        // TODO: 
                        // #1, all de -duplication should move to HostAnalyzerManager
C
Charles Stoner 已提交
136 137
                        // #2, not sure whether de-duplication of analyzer itself makes sense. this can only happen
                        //     if user deliberately put same analyzer twice.
138 139 140 141 142
                        if (builder.ContainsKey(analyzer))
                        {
                            continue;
                        }

143 144
                        var buildToolName = analyzer == compilerAnalyzer ?
                            PredefinedBuildTools.Live : GetBuildToolName(analyzerManager, language, analyzer);
H
Heejae Chang 已提交
145

146
                        builder.Add(analyzer, new StateSet(language, analyzer, buildToolName));
147 148 149 150
                    }
                }

                return builder.ToImmutable();
151 152
            }

153
            private static string GetBuildToolName(HostAnalyzerManager analyzerManager, string language, DiagnosticAnalyzer analyzer)
H
Heejae Chang 已提交
154 155 156 157 158 159 160
            {
                var packageName = analyzerManager.GetDiagnosticAnalyzerPackageName(language, analyzer);
                if (packageName == null)
                {
                    return null;
                }

161 162
                if (packageName == RoslynLanguageServices)
                {
163
                    return PredefinedBuildTools.Live;
164 165
                }

H
Heejae Chang 已提交
166 167 168
                return $"{analyzer.GetAnalyzerAssemblyName()} [{packageName}]";
            }

169 170
            [Conditional("DEBUG")]
            private static void VerifyDiagnosticStates(IEnumerable<StateSet> stateSets)
171
            {
172 173 174 175 176 177 178 179 180
                // Ensure diagnostic state name is indeed unique.
                for (var i = 0; i < s_stateTypeCount; i++)
                {
                    var set = new HashSet<ValueTuple<string, string>>();

                    foreach (var stateSet in stateSets)
                    {
                        var state = stateSet.GetState((StateType)i);

181
                        if (!(set.Add(ValueTuple.Create(state.Language_TestingOnly, state.Name_TestingOnly))))
182 183 184
                        {
                            Contract.Fail();
                        }
185 186 187 188
                    }
                }
            }
        }
189 190
    }
}