File: Workspace\ProjectSystem\VisualStudioAnalyzer.cs
Web Access
Project: ..\..\..\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// 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.Collections.Immutable;
using System.IO;
using Microsoft.CodeAnalysis.Diagnostics;
 
namespace Microsoft.CodeAnalysis.Workspaces.ProjectSystem
{
    // TODO: Remove. This is only needed to support Solution Explorer Analyzer node population. 
    // Analyzers should not be loaded in devenv process (see https://github.com/dotnet/roslyn/issues/43008).
    internal sealed class ProjectAnalyzerReference : IDisposable
    {
        // Shadow copy analyzer files coming from packages to avoid locking the files in NuGet cache.
        // NOTE: It is important that we share the same shadow copy assembly loader for all VisualStudioAnalyzer instances.
        // This is required to ensure that shadow copied analyzer dependencies are correctly loaded.
        private static readonly IAnalyzerAssemblyLoader s_analyzerAssemblyLoader =
            new ShadowCopyAnalyzerAssemblyLoader(Path.Combine(Path.GetTempPath(), "VS", "AnalyzerAssemblyLoader"));
 
        private readonly ProjectId _projectId;
        private readonly IProjectSystemDiagnosticSource _projectSystemDiagnosticSource;
        private readonly string _language;
 
        // these 2 are mutable states that must be guarded under the _gate.
        private readonly object _gate = new();
        private AnalyzerReference? _analyzerReference;
        private ImmutableArray<DiagnosticData> _analyzerLoadErrors = ImmutableArray<DiagnosticData>.Empty;
 
        public ProjectAnalyzerReference(string fullPath, IProjectSystemDiagnosticSource projectSystemDiagnosticSource, ProjectId projectId, string language)
        {
            FullPath = fullPath;
            _projectSystemDiagnosticSource = projectSystemDiagnosticSource;
            _projectId = projectId;
            _language = language;
        }
 
        public string FullPath { get; }
 
        public AnalyzerReference GetReference()
        {
            lock (_gate)
            {
                if (_analyzerReference == null)
                {
                    // TODO: ensure the file watcher is subscribed
                    // (tracked by https://devdiv.visualstudio.com/DevDiv/_workitems/edit/661546)
 
                    var analyzerFileReference = new AnalyzerFileReference(FullPath, s_analyzerAssemblyLoader);
                    analyzerFileReference.AnalyzerLoadFailed += OnAnalyzerLoadError;
                    _analyzerReference = analyzerFileReference;
                }
 
                return _analyzerReference;
            }
        }
 
        private void OnAnalyzerLoadError(object? sender, AnalyzerLoadFailureEventArgs e)
        {
            var data = _projectSystemDiagnosticSource.CreateAnalyzerLoadFailureDiagnostic(e, FullPath, _projectId, _language);
 
            lock (_gate)
            {
                _analyzerLoadErrors = _analyzerLoadErrors.Add(data);
                _projectSystemDiagnosticSource.UpdateDiagnosticsForProject(_projectId, this, _analyzerLoadErrors);
            }
        }
 
        public void Dispose()
        {
            ResetReferenceAndErrors(out var reference, out var loadErrors);
 
            if (reference is AnalyzerFileReference fileReference)
            {
                fileReference.AnalyzerLoadFailed -= OnAnalyzerLoadError;
 
                if (!loadErrors.IsEmpty)
                {
                    _projectSystemDiagnosticSource.ClearDiagnosticsForProject(_projectId, this);
                }
 
                _projectSystemDiagnosticSource.ClearAnalyzerReferenceDiagnostics(fileReference, _language, _projectId);
            }
        }
 
        private void ResetReferenceAndErrors(out AnalyzerReference? reference, out ImmutableArray<DiagnosticData> loadErrors)
        {
            lock (_gate)
            {
                loadErrors = _analyzerLoadErrors;
                reference = _analyzerReference;
 
                _analyzerLoadErrors = ImmutableArray<DiagnosticData>.Empty;
                _analyzerReference = null;
            }
        }
    }
}