File: Diagnostics\VisualStudioDiagnosticAnalyzerProvider.cs
Web Access
Project: src\src\VisualStudio\Core\Def\Microsoft.VisualStudio.LanguageServices_pxr0p0dn_wpftmp.csproj (Microsoft.VisualStudio.LanguageServices)
// 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.Generic;
using System.Collections.Immutable;
using System.Reflection;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Workspaces.ProjectSystem;
using Roslyn.Utilities;
 
namespace Microsoft.VisualStudio.LanguageServices.Implementation.Diagnostics;
 
/// <summary>
/// This service provides diagnostic analyzers from the analyzer assets specified in the manifest files of installed VSIX extensions.
/// These analyzers are used across this workspace session.
/// </summary>
internal partial class VisualStudioDiagnosticAnalyzerProvider : IHostDiagnosticAnalyzerProvider
{
    private const string AnalyzerContentTypeName = "Microsoft.VisualStudio.Analyzer";
 
    /// <summary>
    /// Loader for VSIX-based analyzers.
    /// </summary>
    public static readonly IAnalyzerAssemblyLoader AnalyzerAssemblyLoader = new Loader();
 
    private readonly object _extensionManager;
    private readonly Type _typeIExtensionContent;
 
    private readonly Lazy<ImmutableArray<(AnalyzerFileReference reference, string extensionId)>> _lazyAnalyzerReferences;
 
    // internal for testing
    internal VisualStudioDiagnosticAnalyzerProvider(object extensionManager, Type typeIExtensionContent)
    {
        Contract.ThrowIfNull(extensionManager);
        Contract.ThrowIfNull(typeIExtensionContent);
 
        _extensionManager = extensionManager;
        _typeIExtensionContent = typeIExtensionContent;
        _lazyAnalyzerReferences = new Lazy<ImmutableArray<(AnalyzerFileReference, string)>>(GetAnalyzerReferencesImpl);
    }
 
    public ImmutableArray<(AnalyzerFileReference reference, string extensionId)> GetAnalyzerReferencesInExtensions()
        => _lazyAnalyzerReferences.Value;
 
    private ImmutableArray<(AnalyzerFileReference reference, string extensionId)> GetAnalyzerReferencesImpl()
    {
        try
        {
            // dynamic is weird. it can't see internal type with public interface even if callee is
            // implementation of the public interface in internal type. so we can't use dynamic here
            var _ = PooledDictionary<AnalyzerFileReference, string>.GetInstance(out var analyzePaths);
 
            // var enabledExtensions = extensionManager.GetEnabledExtensions(AnalyzerContentTypeName);
            var extensionManagerType = _extensionManager.GetType();
            var extensionManager_GetEnabledExtensionsMethod = extensionManagerType.GetRuntimeMethod("GetEnabledExtensions", [typeof(string)]);
            var enabledExtensions = (IEnumerable<object>)extensionManager_GetEnabledExtensionsMethod.Invoke(_extensionManager, [AnalyzerContentTypeName]);
 
            foreach (var extension in enabledExtensions)
            {
                var extensionType = extension.GetType();
                var extensionType_HeaderProperty = extensionType.GetRuntimeProperty("Header");
                var extension_Header = extensionType_HeaderProperty.GetValue(extension);
                var extension_HeaderType = extension_Header.GetType();
                var extension_HeaderType_Identifier = extension_HeaderType.GetRuntimeProperty("Identifier");
                var identifier = (string)extension_HeaderType_Identifier.GetValue(extension_Header);
 
                var extensionType_ContentProperty = extensionType.GetRuntimeProperty("Content");
                var extension_Content = (IEnumerable<object>)extensionType_ContentProperty.GetValue(extension);
 
                foreach (var content in extension_Content)
                {
                    if (!ShouldInclude(content))
                    {
                        continue;
                    }
 
                    var extensionType_GetContentMethod = extensionType.GetRuntimeMethod("GetContentLocation", [_typeIExtensionContent]);
                    if (extensionType_GetContentMethod?.Invoke(extension, [content]) is not string assemblyPath ||
                        string.IsNullOrEmpty(assemblyPath))
                    {
                        continue;
                    }
 
                    analyzePaths.Add(new AnalyzerFileReference(assemblyPath, AnalyzerAssemblyLoader), identifier);
                }
            }
 
            // make sure enabled extensions are alive in memory
            // so that we can debug it through if mandatory analyzers are missing
            GC.KeepAlive(enabledExtensions);
 
            // Order for deterministic result.
            return analyzePaths.OrderBy((x, y) => string.CompareOrdinal(x.Key.FullPath, y.Key.FullPath)).SelectAsArray(entry => (entry.Key, entry.Value));
        }
        catch (TargetInvocationException ex) when (ex.InnerException is InvalidOperationException)
        {
            // this can be called from any thread, and extension manager could be disposed in the middle of us using it since
            // now all these are free-threaded and there is no central coordinator, or API or state is immutable that prevent states from
            // changing in the middle of others using it.
            //
            // fortunately, this only happens on disposing at shutdown, so we just catch the exception and silently swallow it. 
            // we are about to shutdown anyway.
            return ImmutableArray<(AnalyzerFileReference, string)>.Empty;
        }
    }
 
    private static bool ShouldInclude(object content)
    {
        // var content_ContentTypeName = content.ContentTypeName;
        var contentType = content.GetType();
        var contentType_ContentTypeNameProperty = contentType.GetRuntimeProperty("ContentTypeName");
        var content_ContentTypeName = contentType_ContentTypeNameProperty.GetValue(content) as string;
 
        return string.Equals(content_ContentTypeName, AnalyzerContentTypeName, StringComparison.InvariantCultureIgnoreCase);
    }
}