File: SolutionExplorer\DiagnosticItem\CpsDiagnosticItemSource.cs
Web Access
Project: ..\..\..\src\VisualStudio\Core\Impl\Microsoft.VisualStudio.LanguageServices.Implementation_zmmkbl53_wpftmp.csproj (Microsoft.VisualStudio.LanguageServices.Implementation)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.ComponentModel;
using System.IO;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.Internal.VisualStudio.PlatformUI;
using Microsoft.VisualStudio.Shell;
 
namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplorer
{
    internal partial class CpsDiagnosticItemSource : BaseDiagnosticAndGeneratorItemSource, INotifyPropertyChanged
    {
        private readonly IVsHierarchyItem _item;
        private readonly string _projectDirectoryPath;
 
        /// <summary>
        /// The analyzer reference that has been found. Once it's been assigned a non-null value, it'll never be assigned null again.
        /// </summary>
        private AnalyzerReference? _analyzerReference;
 
        public event PropertyChangedEventHandler? PropertyChanged;
 
        public CpsDiagnosticItemSource(Workspace workspace, string projectPath, ProjectId projectId, IVsHierarchyItem item, IAnalyzersCommandHandler commandHandler, IDiagnosticAnalyzerService analyzerService)
            : base(workspace, projectId, commandHandler, analyzerService)
        {
            _item = item;
            _projectDirectoryPath = Path.GetDirectoryName(projectPath);
 
            _analyzerReference = TryGetAnalyzerReference(Workspace.CurrentSolution);
            if (_analyzerReference == null)
            {
                // The ProjectId that was given to us was found by enumerating the list of projects in the solution, thus the project must have already
                // been added to the workspace at some point. As long as the project is still there, we're going to assume the reason we don't have the reference
                // yet is because while we have a project, we don't have all the references added yet. We'll wait until we see the reference and then connect to it.
                if (workspace.CurrentSolution.ContainsProject(projectId))
                {
                    Workspace.WorkspaceChanged += OnWorkspaceChangedLookForAnalyzer;
                    item.PropertyChanged += IVsHierarchyItem_PropertyChanged;
 
                    // Now that we've subscribed, check once more in case we missed the event
                    var analyzerReference = TryGetAnalyzerReference(Workspace.CurrentSolution);
 
                    if (analyzerReference != null)
                    {
                        _analyzerReference = analyzerReference;
                        UnsubscribeFromEvents();
                    }
                }
            }
        }
 
        private void UnsubscribeFromEvents()
        {
            Workspace.WorkspaceChanged -= OnWorkspaceChangedLookForAnalyzer;
            _item.PropertyChanged -= IVsHierarchyItem_PropertyChanged;
        }
 
        private void IVsHierarchyItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            // IVsHierarchyItem implements ISupportDisposalNotification, which allows us to know when it's been removed
            if (e.PropertyName == nameof(ISupportDisposalNotification.IsDisposed))
            {
                UnsubscribeFromEvents();
            }
        }
 
        public IContextMenuController DiagnosticItemContextMenuController => CommandHandler.DiagnosticContextMenuController;
 
        public override object SourceItem => _item;
 
        public override AnalyzerReference? AnalyzerReference => _analyzerReference;
 
        private void OnWorkspaceChangedLookForAnalyzer(object sender, WorkspaceChangeEventArgs e)
        {
            // If the project has gone away in this change, it's not coming back, so we can stop looking at this point
            if (!e.NewSolution.ContainsProject(ProjectId))
            {
                UnsubscribeFromEvents();
                return;
            }
 
            // Was this a change to our project, or a global change?
            if (e.ProjectId == ProjectId ||
                e.Kind == WorkspaceChangeKind.SolutionChanged)
            {
                var analyzerReference = TryGetAnalyzerReference(e.NewSolution);
                if (analyzerReference != null)
                {
                    _analyzerReference = analyzerReference;
                    UnsubscribeFromEvents();
 
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(HasItems)));
                }
            }
        }
 
        private AnalyzerReference? TryGetAnalyzerReference(Solution solution)
        {
            var project = solution.GetProject(ProjectId);
 
            if (project == null)
            {
                return null;
            }
 
            var canonicalName = _item.CanonicalName;
            var analyzerFilePath = CpsUtilities.ExtractAnalyzerFilePath(_projectDirectoryPath, canonicalName);
 
            if (string.IsNullOrEmpty(analyzerFilePath))
            {
                return null;
            }
 
            return project.AnalyzerReferences.FirstOrDefault(r => string.Equals(r.FullPath, analyzerFilePath, StringComparison.OrdinalIgnoreCase));
        }
    }
}